|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2017-04-13 01:28 UTC] adrianrb93 at gmail dot com
Description:
------------
I added to my class a non-static method intentionally expecting that if it is called statically, it will direct to the __callStatic() magic method.
Use Case:
I am using Laravel. On its model classes:
* Model: calling a non-existing static method is handled by `__callStatic()`, for this example lets pretend its `->admin()`
* Model: `callStatic` creates a query builder class and attempts to run the method on that
* Query Builder: The query builder does not have this method so it is handled by `__call()`
* Query Builder: `__call()` will try to call the method on the Model prepended with 'scope': `$this->getModel()->scopeAdmin($this, ...$parameters)`
* Model: The `scopeAdmin()` method exists
In my test script example, I will be using a `scopeOpened()` and an `opened()` method, both which are non-static methods.
Test script:
---------------
<?php
/**
* ----------------------------------------------------------------------------
* Instructions:
* ----------------------------------------------------------------------------
* Install a new Laravel 5.4 app and place this test script in: `app/Shop.php`
*
* In the Laravel project run `composer install`. The `php artisan tinker`
* command will give you the same test environment to test the behaviour.
*/
/**
* ----------------------------------------------------------------------------
* Expected Result:
* ----------------------------------------------------------------------------
* $ php artisan tinker
* Psy Shell v0.8.3 (PHP 7.0.17 — cli) by Justin Hileman
* >>> App\Shop::opened('2017-04-13')
* => App\Shop {#970
* owner_id: null,
* day_of_the_week: 4,
* start_date: "2017-04-13",
* end_date: "2017-04-13",
* }
*/
/**
* ----------------------------------------------------------------------------
* Actual Result:
* ----------------------------------------------------------------------------
* $ php artisan tinker
* Psy Shell v0.8.3 (PHP 7.0.17 — cli) by Justin Hileman
* >>> App\Shop::opened('2017-04-13')
* ErrorException with message 'Non-static method App\Shop::opened() should not be called statically'
*/
namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model as BaseModel;
class Shop extends BaseModel
{
/**
* This is what is expected to be run when Shop::opened() is called.
*
* - Shop::opened()
* -> Model::__callStatic()
* -> $builder->opened()
* -> $builder->__call()
* -> $model->scopeOpened()
* -> $model->opened()
*
* The above will not work because within a static context, PHP attempts
* to call the non-static method `opened()` instead of directing the call
* to `__callStatic()`.
*
* @param Illuminate\Database\Eloquent\Builder $query
* @return $this Usually you return the query builder, but my intent is
* to have data pulled from the query builder, then
* $model->opened() be called.
*/
public function scopeOpened($query, $date)
{
return $query->getModel()->fill([
// ----------------------------------------------------------------
// This is an example, the query builder could have added
// `where owner_id = '1'`. This scope method is here to direct to
// the opened() method on the query builders model instance with
// the `owner_id` provided if given in the query.
//
// This is to show I have an actual need for the behaviour I'm
// requesting.
// ----------------------------------------------------------------
'owner_id' => self::extractWhereValueFromQuery($query, 'owner_id'),
])->opened($date);
}
/**
* This is intended to only be called in a non-static context. The
* behaviour I'm requesting is for PHP to see this isn't a static method
* and instead direct to the magic __callStatic() method.
*
* @param Carbon\Carbon|string $date
* @return $this
*/
public function opened($date)
{
$date = self::resolveDate($date);
return $this->fill([
'day_of_week' => $date->dayOfWeek,
'start_date' => $date->toDateString(),
'finish_date' => $date->toDateString(),
]);
}
/**
* Extract column value from query builders where clause.
*
* @param Illuminate\Database\Eloquent\Builder $query
* @param string $column
* @return mixed
*/
public static function extractWhereValueFromQuery($query, $column)
{
return collect( $query->getQuery()->wheres )
->filter(function ($where) use ($column) {
return $where['column'] === $column;
})
->pluck('value')
->first();
}
/**
* Resolve a variable to a Carbon instance.
*
* @param mixed $date
* @return Carbon\Carbon
*/
public static function resolveDate($date)
{
if (is_string($date)) {
$date = Carbon::parse($date);
}
if ($date instanceof Carbon === false) {
return Carbon::now();
}
return $date;
}
}
Expected result:
----------------
$ php artisan tinker
Psy Shell v0.8.3 (PHP 7.0.17 — cli) by Justin Hileman
>>> App\Shop::opened('2017-04-13')
=> App\Shop {#970
owner_id: null,
day_of_the_week: 4,
start_date: "2017-04-13",
end_date: "2017-04-13",
}
Actual result:
--------------
$ php artisan tinker
Psy Shell v0.8.3 (PHP 7.0.17 — cli) by Justin Hileman
>>> App\Shop::opened('2017-04-13')
ErrorException with message 'Non-static method App\Shop::opened() should not be called statically'
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sat Oct 25 21:00:01 2025 UTC |
First, thank you for explaining the magic methods, I learned something new. The example given is a dumbed down example for what I'm actually doing, I do understand the nausea you feel. In Laravel you know the `scopeOpened($query)` can be called statically as opened() with the query builder passed into it. The intent of the method I was making was "I'm making a new record", and to make it a bit more expressive, I wanted it to work from the query builder and model. I had solved the problem. This is what I had written to make it work: ``` public static function handleCallContext($staticCallback, $objectCallback) { $instance = collect( debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit = 3) )->pluck('object')->last(); if ($instance && get_class($instance) === __CLASS__) { return $objectCallback($instance); } return $staticCallback(new static); } ```