1

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;
}
6
  • The problem is that $_schema is only defined in User and not in Post so the same variable will always be used. Commented May 17, 2014 at 20:23
  • 1
    One way to solve it is to make _schema and array and you make the key get_class($this). Commented May 17, 2014 at 20:26
  • So it wouldn't matter if I used self or static in this scenario? I as under the assumption static was bound to the called class. Commented May 17, 2014 at 20:32
  • Your first statement is true. static just changes where it look up the thing you want. If you have a function in User and uses self:: it will look in User. If you use static it will first look in Post. But if _schema is not defined there it will fallback to User. Commented May 17, 2014 at 20:37
  • But User and Post both extend Model which is where the _schema variable and getSchema function is defined. So I assumed through inheritance that each would get its own instance of the schema variable, but I guess they are both just falling back to Model, which is being set by user's schema since that is what I instantiated first? Commented May 17, 2014 at 20:39

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.