Reputation: 868
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;
}
Upvotes: 1
Views: 393
Reputation: 26534
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]
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.
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.
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:
User
is-not-a Model
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
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
.
Upvotes: 3