简体   繁体   中英

PHP static vs self variables and inheritance

I have a PHP Model class that is extended by User and Post. They both share a constructor. I have a static variable for the 'schema' of each object so that I get the 'describe' from the database only once per object type, when the first is instantiated. I thought I could use static::_schema to reference the variable when each object is created, but if I create user and then post, post is referencing the user _schema variable. It does this if I use self:: OR static::. Am I wrong in understanding the difference? What am I doing wrong to get the outcome I want?

Below is the constructor function and getSchema function inside Model (the class Post and User both extend). But if I call getSchema on Post AFTER a User is created, it returns the User schema.

public function __construct($params = array())
{
    $this->_tableName = static::getTableName();
    $this->_className = static::getClassName();

    $this->getSchema();
}

public function getSchema()
{
    if (!static::$_schema) {
        $query = "DESCRIBE {$this->_tableName};";
        $sth = self::$db->prepare($query);
        $sth->execute(static::$bindings);
        $fields = $sth->fetchAll();

        foreach ($fields as $info) {
            static::$_schema[$info->Field] = array(
                'type' => $info->Type,
                'null' => $info->Null,
                'key' => $info->Key,
                'default' => $info->Default,
                'extra' => $info->Extra
            );
            if ($info->Key === 'PRI') {
                $this->_idField = $info->Field;
            }
        }
    }
    return static::$_schema;
}

Late Static Binding

PHP's Late Static Binding is a pretty awesome concept when you get your head around it. In effect it allows you to code an object to handle the data contained within a child (extending) class. You can then change out the child class and as a result the parent (base) class will do something differently.

In short, late static binding is an inheritance-aware self call . [My quote, feel free to use it elsewhere]

The 'Model'

Just a quick recap. The model is a layer . It is the layer in MVC. Anyone who tells you that they have a "user model", or a "Post Model" does not know what they are talking about.

Feel free to re-cap on how should a model be structured in MVC and start referring to these (what should be) "dumb objects" as Entities . You have a User entity and a Post entity.

Extending

When you extend an object, you are saying ExtendingObject is-a ParentObject . By extending User from Model , you are saying that the User is-a Model , which we have already established by definition is invalid. You are better having a generic DB object and passing that in via Dependency Injection .

Constructors

Your constructors should have no business logic . When you create an object you are creating it in a specific state. This can be enforced by constructor parameters, typehints and exceptions for invalid scalar parameters. Once the object is created, then you run a method on it like getSchema() .

So, to re-cap:

  • A User is-not-a Model
  • The model is a layer, not an object
  • Pass in other objects via dependency injection and adhere to SRP
  • Your constructors should have no business logic

LSB for aphillips

We're going to ignore the best practices listed above for educational purposes only , as you will of course re-factor this after learning how it works.

Your goal (from what I can gather): to have the correct table name available within the base class Model , depending on which object is currently extending it ( User or Post ).

abstract class Model
{
    /** Child classes MUST override this (you can enforce this if/however you like) **/
    const TABLE_NAME = "";

    public function __construct()
    {
        printf('Created table: %s', static::TABLE_NAME);
    }
}

So far, you have a Model object that can't be instantiated directly because it is abstract. Therefore, it must be extended to be used. Cool, let's create your POST object.

class Post extends Model
{
    const TABLE_NAME = "POST TABLE";
}

When you create the Post object, it'll use the inherited constructor from Model . In the Model constructor, it uses static:: instead of self:: to echo the table name. If it had used self:: , it would have echoed 'Created table: '. Instead, because static:: , the inheritance-aware version of self:: is used, it uses the inherited var instead.

So the result of calling $post = new Post; will be "Created table: post".

Assuming you create a User object too:

class User extends Model
{
    const TABLE_NAME = 'User';
}

Each object will have it's own table name echoed when you create them:

$x = new User; // echoes User Table
$x = new Post; // echoes Post Table

Conclusion

You don't have to do this with constants (although, I would). You can do it with class properties. But don't make them public (global mutable state is bad).

With the above information, you should be able to easily have a getSchema() method that uses static::SCHEMA_NAME in the getSchema() method for both Post and User .

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