Simple Model CRUD with PHP 5.3
Wednesday, September 2nd, 2009I’ve been wanting to mess around with some of the new features in PHP 5.3, so I took the opportunity to write a base model class that can be used for simple db based models. This isn’t really production type code, more of an example of “look what php can do now”, although with some error handling and tweaking, it might be a good start to a lightweight active record type base class.
One of the biggest new changes to 5.3 is late static binding. It has never been possible in PHP to get the name of the calling class when you called a static method inherited from a parent. Now with late static binding, this is possible. This is what works the “magic” in this base class.
The Assumptions
Models represent an entity stored in the database (think active record):
- In one table per class named the same as the model class, but all lower case
- With one column for each property
- With a primary key column named ‘id’
In addition, a global variable $db should contain a PHP PDO object.
The Base Class
One of the things I’d like to be able to do is create an object from an array of parameters, whether they come from a database or not. I might want to create from a set of parameters and be able to act on it immediately, without having to reload it. I use reflection here to ensure that all the class properties are set, throwing an exception if any are missing.
Note also in the get and getAll methods, the calls to new static(). What this does is create an object of the class you are calling, rather than the class that the method exists in. This wasn’t possible until PHP 5.3, and is really the key to being able to use static inheritance.
In addition, there is a lambda function in the save method, this is new to 5.3 also. And there are a few examples of a new php function “get_called_class()” which will return the name of the class that was called.
<?php /** * Base class for all models * * @author Zachary Fox */ abstract class Base { /** * Pass properties to construct * * @param mixed[] $properties The object properties * * @throws Exception */ protected function __construct(Array $properties) { $reflect = new ReflectionObject($this); foreach ($reflect->getProperties() as $property) { if (!array_key_exists($property->name, $properties)) { throw new Exception("Unable to create object. Missing property: {$property->name}"); } $this->{$property->name} = $properties[$property->name]; } } /** * Get all class properties * * @return string[] */ protected static function getFields() { static $fields = array(); $called_class = get_called_class(); if (!array_key_exists($called_class, $fields)) { $reflection_class = new ReflectionClass($called_class); $properties = array(); foreach ($reflection_class->getProperties() as $property) { $properties[] = $property->name; } $fields[$called_class] = $properties; } return $fields[$called_class]; } /** * Get the select statement * * @return string */ protected static function getSelect() { return "SELECT " . implode(', ', self::getFields()) . " FROM " . strtolower(get_called_class()); } /** * Save this object * * @return void */ protected function save() { global $db; $fields = self::getFields(); $replace = "REPLACE INTO " . strtolower(get_called_class()) . "(" . implode(',', $fields) . ")"; $function = function ($value) { return ':' . $value; }; $replace .= " VALUES (" . implode(',', array_map($function, $fields)) . ")"; $statement = $db->prepare($replace); foreach ($fields as $field) { $statement->bindParam($field, $this->$field); } $statement->execute(); } /** * Get a single object by id * * @param integer $id * @return Object */ public static function get($id) { global $db; $select = self::getSelect() . " WHERE `id` = :id"; $statement = $db->prepare($select); $statement->bindParam(':id', $id, PDO::PARAM_INT); $statement->execute(); $result = $statement->fetchAll(PDO::FETCH_ASSOC); return new static($result[0]); } /** * Get all objects * * @return Object[] */ public static function getAll() { global $db; $return = array(); foreach ($db->query(self::getSelect(), PDO::FETCH_ASSOC) as $row) { $return[] = new static($row); } return $return; } /** * Create a new object * * @param mixed[] $properties Properties * * @return Object */ public static function create(Array $properties) { global $db; $properties['id'] = ($db->query('SELECT MAX(id) FROM ' . strtolower(get_called_class()))->fetchColumn() + 1); $object = new static($properties); $object->save(); return $object; } /** * Update an object * * @param mixed[] $properties Properties * * @return void */ public function update(Array $properties) { foreach ($properties as $key => $value) { if (property_exists($this, $key)) { $this->$key = $value; } } $this->save(); } /** * Update a single property * * @param string $key Property name * @param mixed $value Property value * * @return void */ public function updateProperty($key, $value) { if (property_exists($this, $key)) { $this->$key = $value; } $this->save(); } /** * Delete an object * * @return void */ public function delete() { global $db; $delete = "DELETE FROM " . strtolower(get_called_class()) . " WHERE `id` = :id"; $statement = $db->prepare($statement); $statement->bindParam(':id', $this->id, PDO::PARAM_INT); $statement->execute(); } } ?>
HTML code generated by vim-color-improved v.0.4.0.Download this code: base.php
Now, you’ll probably notice that this is an abstract class, meaning that we can’t create any Base objects, but must extend the class. Here is an example of a child class that holds name / value pairs:
<?php /** * Extends the base class * */ class Extend_Base extends Base { protected $id; protected $name; protected $value; } ?>
HTML code generated by vim-color-improved v.0.4.0.Download this code: extend_base.php
Once I’ve created the matching db table, I’m ready to get some CRUD done.
Creating an object
To create an object, I simply pass the parameters as an associative array of properties and their values.
<?php $properties = array( 'name' => 'First Object', 'value' => 'This is the first object' ); $extend_base = Extend_Base::create($properties); ?>
HTML code generated by vim-color-improved v.0.4.0.Download this code: create_extend_base_object.php
Getting objects
Getting is simple. Either get by ID or get an array of all the objects. It would be fairly trivial to add a find method.
<?php // Single object $extend_object = Extend_Base::get(1); // All objects $extend_objects = Extend_Base::getAll(); ?>
HTML code generated by vim-color-improved v.0.4.0.Download this code: get_extend_base_object.php
Updating
You can update multiple parameters at once by passing an associative array as you did in the create method, or update a single parameter by using the updateProperty method and passing the name and value.
<?php $extend_base = Extend_Base::get(1); $properties = array( 'name' => 'Updated Name', 'value' => 'Updated value' ); $extend_base->update($properties); $extend_base->updateProperty('value', 'Updated value again'); ?>
HTML code generated by vim-color-improved v.0.4.0.Download this code: update_extend_base_object.php
Deleting
<?php $extend_base = Extend_Base::get(1); $extend_base->delete(); ?>
HTML code generated by vim-color-improved v.0.4.0.Download this code: delete_extend_base_object.php