简体   繁体   中英

Static / Non-Static Method Issue

I am working on a simple ORM solution and have run into a tricky situation. Ideally, I'd like to be able to use methods in both a static context, and object context depending on how it is called. I am not sure if this is possible, but here is what I mean:

Say a User model wants to call where() statically, this currently works fine, for example:

$user = User::where('id = ?', 3);

Now, I also support relationships, for example a user can have messages. When this relationship is established I simply store a blank copy of a message model in the user model and set a foreign key. For example:

$user -> messages = new Message();
$user -> messages -> foreign_key = 'user_id';

Now, ideally, I would like to be able to call:

$user -> messages -> where('unread = ?', 1);

In a non-static context and make use of $this -> foreign_key when in this context so as to only pull messages where the foreign key matches the user's id. Is this type of context-switching possible in PHP? Any reference to $this from static context throws an error as its a static method and should not rely on $this (for obvious reasons, when being called from a static context, $this won't exist)

Are there any clever ways around this? I tried overloading the method to have two different prototypes, both with and without the static keyword but this threw a re-declaration error.

After quite a bit of playing around, I have found a way to make this workable without the Strict Standards error mentioned by @drew010. I don't like it, it feels horrible, but it does work so I shall post this anyway.

Basically the idea is to make the method you want to be accessible private and static . You then define the __call() and __callStatic() magic methods, so that they will call the private static method. Now you may think "this doesn't solve the problem, I'm still stuck in a static context" - which you are but for a minor addition, you can append $this to the arguments passed to the actual method in __call() and fetch this as the last argument to the method. So instead of referencing $this in an object context, you reference the third argument to get a reference to your own instance.

I'm probably not explaining this very well, just have a look at this code :

<?php

class test_class {

    private $instanceProperty = 'value';

    private static function where ($arg1, $arg2, $obj = NULL) {
        if (isset($obj)) {
            echo "I'm in an object context ($arg1, $arg2): I can access the instance variable: $obj->instanceProperty<br>\n";
        } else {
            echo "I'm in a static context ($arg1, $arg2)<br>\n";
        }
    }

    public function __call ($method, $args) {
        $method = "self::$method";
        if (is_callable($method)) {
            $args[] = $this;
            return call_user_func_array($method, $args);
        }
    }

    public static function __callStatic ($method, $args) {
        $method = "self::$method";
        if (is_callable($method)) {
            return call_user_func_array($method, $args);
        }
    }

}

test_class::where('unread = ?', 1);

$obj = new test_class();
$obj->where('unread = ?', 2);

I can't think of any way to do this without going against PHP standards and using the language in a way it wasn't meant to be used.

A function is either static or not. Yes PHP allows you to call it either way but this does violate strict standards and the only reason you can get away with doing this is for backwards compatibility with older PHP 4 code where static didn't exist as a keyword.

Consider this code:

<?php

class Test {
    protected $_userId;

    public function find()
    {
        if (isset($this)) {
            echo "Not static.<br />\n";
        } else {
            echo "Static.<br />\n";
        }
    }
}

$t = new Test();
$t->find();

Test::find();

The output is:

Not static.
Static.

But with error reporting turned on, this is the actual output:

Not static.

Strict Standards: Non-static method Test::find() should not be called statically in test.php on line 19
Static.

If you declare the method as static, then it is static no matter which way it was called.

So I suppose the answer is Yes, you can do it using this workaround, but I wouldn't recommend it. If you want to have it both ways, I would suggest adding two methods, public function find() and public static function findStatic() .

Since your code is either going to be written as $obj->find() or Class::find() , you can just as easily use the static vs. non-static methods in my opinion rather than having one method behave statically. To adhere to DRY, I suppose one method would leverage the other one for doing the actual finding.

Sorry for not answering your question, but I have some comments that don't fit in a... comment. What you're doing is a bit illogical.

$user->messages = new Message();

You're creating a single message inside a variable called messages .
Do you mean $user->messages[] = new Message(); ?
Also, protect your class variables.

$user->messages->where('unread = ?', 1);

Here you're trying to SELECT from your user messages, it's nonsense.
What you should do is simply the same as you did for the User class: get the messages statically and then assign them to your user:

$user->messages = Message::where('unread = ?', 1);

If you need to look up for messages that have a specific primary key, pass it as a parameter to the where method, which can be enhanced to take many clauses:

$messages = Message::where(array(
    array('unread = ?', 1),
    array('id = ?',     $message->getID()),
));

I'd also like to add a personal note: creating an ORM is a great way to learn, but if you're looking for something more serious , I recommend you to give Doctrine or Propel a look.

From the theoretical and practical point of view, it is not a good idea to have a class method which can be called both from the static and non-static context.

If you want to achieve accessibility of your class/methods throughout your application, maybe it would be a good start to read about Dependency Injection, Service Container and dependency injection oriented programming.

By implementing DI in your application, you will most probably loose any need for what you have mentioned.

I would suggest investigating trough web and you will find that static calls in context that you are working with is avoided and flagged as bad practice. Static/shared state in object oriented programming is something which should be avoided (as well as singleton pattern).

Dont get me wrong -> static methods have their purpose and benefits, however, the way you are using it is kinda obsolete (even though some frameworks, like Laravel, promotes this bad practice - eg "Facades" and Eloquent).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM