Req #74425 [Opn->Wfx]: Behaviour Change: Non-static method should not be called statically

From: Date: Thu, 13 Apr 2017 01:53:36 +0000
Subject: Req #74425 [Opn->Wfx]: Behaviour Change: Non-static method should not be called statically
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-208504@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=74425&edit=1

 ID:                 74425
 Updated by:         requinix@php.net
 Reported by:        adrianrb93 at gmail dot com
 Summary:            Behaviour Change: Non-static method should not be
                     called statically
-Status:             Open
+Status:             Wont fix
 Type:               Feature/Change Request
 Package:            Class/Object related
 Operating System:   MACOS 10.12.14
 PHP Version:        7.0.17
 Block user comment: N
 Private report:     N

 New Comment:

> 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.
That's not how methods work. There is only one with a given name in a class and it is an
instance method or a static method. Calling the method incorrectly will result in an error.

Meanwhile __call and __callStatic intercept calls to methods that do not exist at all, or that
don't exist from the perspective of the caller (ie, accounting for visibility). Static vs.
instance is not a factor.

I just gave you a hint for how you could make this thing work - but only a hint because the whole
scheme of making Shop::opened() result in $shop->opened() makes me nauseous.


Previous Comments:
------------------------------------------------------------------------
[2017-04-13 01:28:09] 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'



------------------------------------------------------------------------



--
Edit this bug report at https://bugs.php.net/bug.php?id=74425&edit=1


Thread (3 messages)

« previous php.bugs (#208504) next »