If I have a fairly complex User model that I would like to use the Data Mapping pattern to load, how would I lazily load some of the more intensive bits of user info without allowing the User to be aware of the UserMapper?
For example - if the User model allows for an array of Address objects (and the User might have many of them, but not necessarily needed up front), how would I load those object if/when needed?
Do I make the User model aware of the AddressMapper?
Do I pass the User model BACK into the UserMapper which then hydrates only the Addresses?
Is there a better option?
Well, I have found the following clever pattern at one time, courtesy of Ben Scholzen, developer for the Zend Framework. It goes something like this:
class ModelRelation
implements IteratorAggregate
{
protected $_iterator;
protected $_mapper;
protected $_method;
protected $_arguments;
public function __construct( MapperAbstract $mapper, $method, array $arguments = array() )
{
$this->_mapper = $mapper;
$this->_method = $method;
$this->_arguments = $arguments;
}
public function getIterator()
{
if( $this->_iterator === null )
{
$this->_iterator = call_user_func_array( array( $this->_mapper, $this->_method ), $this->_arguments );
}
return $this->_iterator;
}
public function __call( $name, array $arguments )
{
return call_user_func_array( array( $this->getIterator(), $name ), $arguments );
}
}
Ben Scholzen's actual implementation is here.
The way you would use it, is something like this:
class UserMapper
extends MapperAbstract
{
protected $_addressMapper;
public function __construct( AddressMapper $addressMapper )
{
$this->_addressMapper = $addressMapper;
}
public function getUserById( $id )
{
$userData = $this->getUserDataSomehow();
$user = new User( $userData );
$user->addresses = new ModelRelation(
$this->_addressesMapper,
'getAddressesByUserId',
array( $id )
);
return $user;
}
}
class AddressMapper
extends MapperAbstract
{
public function getAddressesByUserId( $id )
{
$addressData = $this->getAddressDataSomehow();
$addresses = new SomeAddressIterator( $addressData );
return $addresses;
}
}
$user = $userMapper->getUserById( 3 );
foreach( $user->addresses as $address ) // calls getIterator() of ModelRelation
{
// whatever
}
The thing is though; this could get very slow, if the object graphs get very complex and deeply nested at some point, because the mappers all have to query their own data (presuming you are using a database for persistence). I experienced this when I used this pattern for a CMS to get nested Pages objects (arbitrarily deep child Pages).
It could probably be tweaked with some caching mechanism, to speed things up considerably though.
Related
I have recently moved a large php application from using mssql_ functions to the PDO function using the mssql driver.
I wrote a simple library that allows drop in replacement. It all seems to work pretty well considering.
However one thing that is a bit annoying is default format of numbers and particularly numbers defined as money in the database.
Most of my smarty template pages previous simply output the number as it came from the database so someones balance might be show as
125.00
however since changing to PDO this is returned as
125.0000
This is a little annoying and off putting, but obviously not the end of the world.
My Question. Is there a workaround / trick / formatting Constant or method that I can use to get PDO to format values differently, or do I need to go an manually set the format for every number in every template throughout my app?
So basically, what I'd do is create models that represent a result-set for each table, and use PDO::FETCH_CLASS to load the data into instances of the corresponding class. For example:
class UserTable //extends AbstractTable <-- see below
{
protected $id = null;
protected $name = null;
protected $email = null;
protected $money = null;
}
Then add getters and setters that format/validate the data accordingly eg:
public function getMoney()
{
return sprintf('%.2f', $this->money);//check if not null first, obviously
}
Next, have an abstract class for these models, and implement the ArrayAccess interface in there. For example, using a simple mapping array:
protected $getterMap = [
'email' => 'getEmail',
'id' => 'getId',
'money' => 'getMoney',
];
Define a tailor-made map in each child, then have the abstract class use it like so:
//in abstract class AbstracTable implements ArrayAccess
public function offsetGet($offset)
{
if (!isset($this->getterMap[$offset])) {
throw new RuntimeException(
sprintf('%s not a member of %s', $offset, get_class($this));
);
}
$getter = $this->getterMap[$offset];
return $this->{$getter}();//use the getter, it formats the data!
}
Do something similar for all 4 methods in the interface, and now you can use this:
$row = $stmt->fetch(PDO::FETCH_CLASS, 'User');
$row['money'];//will call getMoney, and return the formatted number
A more complete example:
abstract class AbstractTable implements ArrayAccess
{
protected $id = null;//very likely to be defined in all tables
protected $getterMap = [
'id' => 'getId',
];
protected $setterMap = [
'id' => 'setId',
];
//force child classes to define a constructor, which sets up the getter/setter maps
abstract public function __construct();
public offsetExists($offset)
{
return isset($this->getterMap[$offset]);
//optionally, check if value if not null: isset($arr['keyWithNullVal']) returns null, too:
return isset($this->getterMap[$offset]) && $this->{$offset} !== null;
}
public offsetGet ( mixed $offset )
{
if (!isset($this->getterMap[$offset])) {
throw new RuntimeException('member does not exist');
}
$getter = $this->getterMap[$offset];
return $this->{$getter}();
}
public offsetSet($offset, $value )
{
if (!isset($this->setterMap[$offset])) {
throw new RuntimeException('Trying to set non-existing member');
}
$setter = $this->setterMap[$offset];
$this->{$setter}($value);
}
public offsetUnset ($offset)
{
//same as setter, but call:
//or just leave blank
$this->{$setter}(null);
}
}
class UserTable extends AbstractTable
{
//protected $id = null; in parent already
protected $name = null;
protected $email = null;
protected $money = null;
public function __construct()
{
$fields = [
'name' => 'etName',
'email' => 'etEmail',
'money' => 'etMoney',
];
foreach ($fields as $name => $method) {
$this->getterMap[$name] = 'g' . $method;
$this->setterMap[$name] = 's' . $method;
}
}
}
Obviously, you'll have to write the getters and setters for all the fields. Not to worry, though: most IDE's will helpfully generate the getters and setters for predefined properties at the click of a button
I have been working over an year with Magento and have learned it good enough. Now I want to learn Zend, and I'm stuck with models.
I'm used to have entities and collection of entities in Magento, and it's likely that I'll want to use Zend_Db_Table, Zend_Db_Table_Row and/or Zend_Db_Table_Rowset. What I am confused of is the role each class.
I know that I can extend each class, and I understand that in my Product_Table class (that extends Zend_Db_Table_Abstract) it's possible to have private methods that will tell Zend what classes to use for rows and rowsets, however I'm not feeling comfortable with it.
Having this code in Magento:
Example 1
// I understand that maybe I'll use the `new` keyword instead
// Mage::getModel() is only for exemplification
$product = Mage::getModel('catalog/product');
$product->setName('product name');
$product->setPrice(20);
$product->save();
if($id = $product->getId()){
echo 'Product saved with id' . $id;
}
else{
echo 'Error saving product';
}
Example 2
$collection = Mage::getModel('catalog/product')->getCollection();
// this is the limit, I'm ok with other method's name
$collection->setPageSize(10);
$collection->load()
foreach($collection as $product){
echo $product->getName() . ' costs ' . $product->getPrice() . PHP_EOL;
}
How I can implement something similar in Zend Framework? Alternatively if this is a really a bad idea, what are the best practices to implement models in Zend Framework?
Thanks
The Zend team, as mentioned elsewhere, thinks differently about the Model layer than most other PHP Framework creators. Their current thoughts on "the best" way to use their raw tools to provide a Database backed Entity Model can be found in the quick start guide.
That said, most people's solution to Models in Zend Framework is bootstrapping Doctrine.
Here is how I, personally, implement models. I'll use a real life example: my User model.
Whenever I create a model, I use two files and two classes: the model itself (e.g. Application_Model_User) and a mapper object (e.g. Application_Model_UserMapper). The model itself obviously contains the data, methods for saving, deleting, modifying, etc. The mapper object contains methods for fetching model objects, finding objects, etc.
Here are the first few lines of the User model:
class Application_Model_User {
protected $_id;
protected $_name;
protected $_passHash;
protected $_role;
protected $_fullName;
protected $_email;
protected $_created;
protected $_salt;
// End protected properties
For each property, I have a getter and setter method. Example for id:
/* id */
public function getId() {
return $this->_id;
}
public function setId($value) {
$this->_id = (int) $value;
return $this;
}
I also use some standard "magic methods" for exposing public getters and setters (at the bottom of each model):
public function __set($name, $value) {
$method = 'set' . $name;
if (('mapper' == $name) || !method_exists($this, $method)) {
throw new Exception('Invalid user property');
}
$this->$method($value);
}
public function __get($name) {
$method = 'get' . $name;
if (('mapper' == $name) || !method_exists($this, $method)) {
throw new Exception('Invalid user property');
}
return $this->$method();
}
public function setOptions(array $options) {
$methods = get_class_methods($this);
foreach ($options as $key => $value) {
$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
}
}
return $this;
}
Example save method:
I validate inside the save() method, using exceptions when the information fails to validate.
public function save() {
// Validate username
if (preg_match("/^[a-zA-Z](\w{6,15})$/", $this->_name) === 0) {
throw new Application_Exception_UserInfoInvalid();
}
// etc.
$db = Zend_Registry::get("db");
// Below, I would check if $this->_id is null. If it is, then we need to "insert" the data into the database. If it isn't, we need to "update" the data. Use $db->insert() or $db->update(). If $this->_id is null, I might also initialize some fields like 'created' or 'salt'.
}
For the mapper object, I have at least two methods: a method that returns a query object for selecting objects, and one that executes the query, initializes and returns objects. I use this so I can manipulate the query in my controller for sorting and filtering.
EDIT
Like I said in my comments, this post: http://weierophinney.net/matthew/archives/202-Model-Infrastructure.html was the inspiration for my current Model implementation.
More options
You can also use Zend_Form to do validation, instead of rolling your own: http://weierophinney.net/matthew/archives/200-Using-Zend_Form-in-Your-Models.html. I personally don't like this option since I think that Zend_Form is awkward to use and hard to precisely control.
When most people first learn Zend Framework, they learn to subclass Zend_Db related classes. Here is an article that demonstrates this: http://akrabat.com/zend-framework/on-models-in-a-zend-framework-application/
I mentioned that I don't like doing this. Here are a few reasons why:
It's difficult to create models that involve derived/calculated fields (i.e. data populated from other tables)
I found it impossible to incorporate access control (populated from my database)
I like having full control over my models
EDIT 2
For your second example: You can use Zend_Paginator for this. I mentioned that, in your wrapper, you create a method that returns a database query object for selecting objects. Here's my simplified but working user mapper:
class Application_Model_UserMapper {
public function generateSelect() {
$db = Zend_Registry::get("db");
$selectWhat = array(
"users_id",
"name",
"role",
"full_name",
"email",
"DATE_FORMAT(created, '%M %e, %Y at %l:%i:%s %p') as created",
"salt",
"passhash"
);
return $db->select()->from(array("u" => "users"), $selectWhat);
}
public function fetchFromSelect($select) {
$rows = $select->query()->fetchAll();
$results = array();
foreach ($rows as $row) {
$user = new Application_Model_User();
$user->setOptions(array(
"id" => $row["users_id"],
"name" => $row["name"],
"role" => $row["role"],
"fullName" => $row["full_name"],
"email" => $row["email"],
"created" => $row["created"],
"salt" => $row["salt"],
"passHash" => $row["passhash"]
));
$results[] = $user;
}
return $results;
}
}
To handle the paginator, I write a custom Paginator plugin and save it to library/Application/Paginator/Adapter/Users.php. Be sure you have your appnamespace and autoloaderNamespaces[] setup correctly in application.ini. Here is the plugin:
class Application_Paginator_Adapter_Users extends Zend_Paginator_Adapter_DbSelect {
public function getItems($offset, $itemCountPerPage) {
// Simply inject the limit clause and return the result set
$this->_select->limit($itemCountPerPage, $offset);
$userMapper = new Application_Model_UserMapper();
return $userMapper->fetchFromSelect($this->_select);
}
}
In my controller:
// Get the base select statement
$userMapper = new Application_Model_UserMapper();
$select = $userMapper->generateSelect();
// Create our custom paginator instance
$paginator = new Zend_Paginator(new Application_Paginator_Adapter_Users($select));
// Set the current page of results and per page count
$paginator->setCurrentPageNumber($this->_request->getParam("page"));
$paginator->setItemCountPerPage(25);
$this->view->usersPaginator = $paginator;
Then render the paginator in your view script.
I do something similar to SimpleCode's way. My style derives from Pádraic Brady. He has multiple blog posts but the best and quickest resource of his is a online book he wrote: Survive the Deep End!. This link should take you straight to his chapter on Models, Data Mappers, and other cool goodies such as Lazy Loading. The idea is the following:
You have entities such as a User with The properties are defined in an array. All your entities extend an abstract class with magic getter/setters that get from or update this array.
class User extends Entity
{
protected $_data = array(
'user_id' => 0,
'first_name' => null,
'last_name' => null
);
}
class Car extends Entity
{
protected $_data = array(
'car_id' => 0,
'make' => null,
'model' => null
);
}
class Entity
{
public function __construct($data)
{
if(is_array($data))
{
$this->setOptions($data);
}
}
public function __get($key)
{
if(array_key_exists($key, $this->_data)
{
return $this->_data[$key];
}
throw new Exception("Key {$key} not found.");
}
public function __set($key, $value)
{
if(array_key_exists($key, $this->_data))
{
$this->_data[$key] = $value;
}
throw new Exception("Key {$key} not found.");
}
public function setOptions($data)
{
if(is_array($data))
{
foreach($data as $key => $value)
{
$this->__set($key, $value);
}
}
}
public function toArray()
{
return $this->_data;
}
}
$user = new User();
$user->first_name = 'Joey';
$user->last_name = 'Rivera';
echo $user->first_name; // Joey
$car = new Car(array('make' => 'chevy', 'model' => 'corvette'));
echo $car->model; // corvette
Data Mappers to me are separate from the Entities, their job is to do the CRUD (create, read, update, and delete) to the db. So, if we need to load an entity from the db, I call a mapper specific to that entity to load it. For example:
<?php
class UserMapper
{
$_db_table_name = 'UserTable';
$_model_name = 'User';
public function find($id)
{
// validate id first
$table = new $this->_db_table_name();
$rows = $table->find($id);
// make sure you get data
$row = $rows[0]; // pretty sure it returns a collection even if you search for one id
$user = new $this->_model_name($row); // this works if the naming convention matches the user and db table
//else
$user = new $this->_model_name();
foreach($row as $key => $value)
{
$user->$key = $value;
}
return $user;
}
}
$mapper = new UserMapper();
$user = $mapper->find(1); // assuming the user in the previous example was id 1
echo $user->first_name; // Joey
This code is to give an idea of how to architect the code in this way. I didn't test this so I may have created some typos/syntax errors as I wrote it. Like others have mentioned, Zend lets you do what you want with Models, there is no right and wrong it's really up to you. I usually create a table class for every table in the db that I want to work with. So if I have a user table, I usually have a User entity, User Mapper, and a User Table class. The UserTable would extend Zend_Db_Table_Abstract and depending on what I'm doing won't have any methods inside or sometimes I'll overwrite methods like insert or delete depending on my needs. I end up with lots of files but I believe the separation of code makes it much easier to quickly get to where I need to be to add more functionality or fix bug since I know where all the parts of the code would be.
Hope this helps.
Folder Structure
application
--models
----DbTable
------User.php
--controllers
----IndexController.php
--forms
----User.php
--views
----scripts
------index
--------index.phtml
application/models/DbTable/User.php
class Application_Model_DbTable_User extends Zend_Db_Table_Abstract
{
protected $_name = 'users';
protected $_primary = 'user_id';
}
application/forms/User.php
class Form_User extends Zend_Form
{
public function init()
{
$this->setAction('')
->setMethod('post');
$user_name = new Zend_Form_Element_Text('user_name');
$user_name->setLabel("Name")->setRequired(true);
$user_password = new Zend_Form_Element_Text('user_password');
$user_password->setLabel("Password")->setRequired(true);
$submit = new Zend_Form_Element_Submit('submit');
$submit->setLabel('Save');
$this->addElements(array(
$user_name,
$user_password,
$submit
));
}
}
application/controllers/IndexController.php
class IndexController extends Zend_Controller_Action
{
public function init()
{
}
public function indexAction()
{
$form = new Form_User();
if($this->getRequest()->isPost() && $form->isValid($this->getRequest()->getPost()))
{
$post = $this->getRequest()->getPost();
unlink($post['submit']);
$ut = new Application_Model_DbTable_User();
if($id = $ut->insert($post))
{
$this->view->message = "User added with id {$id}";
} else {
$this->view->message = "Sorry! Failed to add user";
}
}
$this->view->form = $form;
}
}
application/views/scripts/index/index.phtml
echo $this->message;
echo $this->form;
I had:
in a class implementing Validator:
an $errormessages property
an isCorrect() method
In the isCorrect method, I had:
switch ($type):
case 'email':
isEmailCorrect();
case 'password':
isPasswordCorrect();
case 'x':
isXCorrect();
isEmailCorrect(), isPasswordCorrect() and isXCorrect() had access to the same property with all error messages
Now, I have:
in Validator:
an $errormessages property
in an EmailValidator class extending Validator:
an isCorrect() method
in a PasswordValidator class extending Validator:
an isCorrect() method
in a XValidator class extending Validator:
an isCorrect() method
Now, in a file calling the isCorrect() methods, I have:
$EmailValidator = new EmailValidator();
$PasswordValidator = new PasswordValidator();
$XValidator = new XValidator();
$EmailValidator->isCorrect(), $PasswordValidator->isCorrect() and $XValidator->isCorrect() don't have access to the same property with all error messages
$errormessages are in different instances of different classes. They should be one, but are three.
What now?
I think you should develop another class: a ValidatorChain, which takes an arbitrary amount of validators, and that aggregates the errormessages of all validators that it has tested
For reference see the docs on Zend Framework's Validator Chain
EDIT
Now that I re-evaluate your question (thanks to Bryan M's comment); why do you want each individual Validator to have access to other Validators' error messages? I would say that collecting each individual Validators' error messages is the responsibility of an object higher in the hierarchy.
If, however, you want individual Validators to be able to act based on context, in other words, based on what the results of other Validators are, then I suppose you could add a $context parameter to the isCorrect method. This could for instance accept an arbitrary amount of Validators or something similar.
Something like:
interface ValidatorInterface
{
public function isCorrect( array $context );
public function getMessages();
}
abstract class ValidatorContextOptions
{
const SHOULD_BE_PRESENT = 'shouldBePresent';
const SHOULD_NOT_BE_PRESENT = 'shouldNotBePresent';
const SHOULD_BE_VALID = 'shouldBeValid';
}
class EmailValidator implements ValidatorInterface
{
protected $_field;
protected $_contextOptions = array();
protected $_messages = array();
public function __construct( $field, array $contextOptions )
{
$this->_field = $field;
$this->_contextOptions = $contextOptions;
}
public function isCorrect( array $context = null )
{
foreach( $this->_contextOptions as $field => $options )
{
foreach( $options as $option )
{
switch( $option )
{
case ValidatorContextOptions::SHOULD_NOT_BE_PRESENT:
if( isset( $context[ $field ] )
&& $context[ $field ] instanceof ValidatorInterface )
{
$this->_messages[] = $field . ' should not be present';
return false;
}
break;
case ValidatorContextOptions::SHOULD_BE_PRESENT:
if( !isset( $context[ $field ] )
|| !$context[ $field ] instanceof ValidatorInterface )
{
$this->_messages[] = $field . ' should be present';
return false;
}
break;
case ValidatorContextOptions::SHOULD_BE_VALID:
if( !isset( $context[ $field ] )
|| !$context[ $field ] instanceof ValidatorInterface
|| !$context[ $field ]->isCorrect() )
{
$this->_messages[] = $field . ' should be valid';
return false;
}
break;
}
}
}
// some dummy function which you should replace with real validation
return isAnEmailAddress( $this->_field );
}
public function getMessages()
{
return $this->_messages;
}
}
Usage:
$emailValidatorContextOptions = array
(
'phone' => array(
ValidatorContextOptions::SHOULD_BE_PRESENT,
ValidatorContextOptions::SHOULD_BE_VALID
)
);
$phoneValidator = new PhoneValidator( $phoneString );
$emailValidator = new EmailValidator( $emailString, $emailValidatorContextOptions );
if( !$emailValidator->isCorrect( array( 'phone' => $phoneValidator ) ) )
{
print_r( $emailValidator->getMessages() );
}
What I've shown here, needs a lot more thinking (and I really mean A LOT), is buggy as hell and definately not bulletproof. But I hope you catch my drift of where I'm going with this.
Moreover, where do you insert the values in your validator that need to be validated anyway?
Well you could make an external properties factory to control access to your property, assuming you are talking about properties files that is the approach I usually take.
If you are referring to a shared field then you can place it in your base class and access it that way.
I'll often use Zend_Validate classes to perform the validation, and aggregate any error message to a property on the object that's being validated (as well as a flag that control valid status).
My setup would be similar to this:
class User {
public $email;
protected $_errorMessages = array();
public function validate()
{
$valid = true;
$emailValidator = new EmailValidator();
if (!$emailValidator->isCorrect($this->email)) {
$valid = false;
// validation message are added to the $errormessages property in
// the validator class upon failure of isCorrect()
$this->_errorMessages[] = $emailValidator->getMessages();
}
// repeat this for all your validators
return $valid
}
public function getErrorMessages()
{
return $this->_errorMessages();
}
}
// in your page....
if (!$user->validate()) {
$messages = $user->getErrorMessages();
}
If I read you right, you want multiple instances to share the same error messages property, such that you can instantiate several validators and have them all contribute to a single array.
If this is the case, there are a few ways to do it. One would be to create a validator manager class which has responsibility for instantiating and registering validators. Then once validation is complete you could call $validator_manager->getErrors() which would aggregate the errors present in all the validators registered with it.
Another way you could do it would be to use a singleton error store class, which you acquire in the constructor of each validator. Each validator's addError() method would then delegate the job to the singleton.
There are other methods still, but basically you're going to have to use another object, either for managing the validators or storing the errors.
Someone below mentioned using a singleton for this.
I am not convinced that it's a great use of that design pattern, especially since it's commonly held that singletons are the "anti-pattern" and often over/mis-used.
Nonetheless, keeping that in mind, here's an example along those lines:
<?php
//Error Class implemented as a Singleton
class ErrorClass
{
static private $instance = false;
static private $errorMessages;
function getInstance() {
if (!self::$instance) {
self::$instance = new ErrorClass();
self::$errorMessages = "No errors;";
}
return self::$instance;
}
public function setError($errorMessage){
self::$instance->errorMessages .= $errorMessage;
}
public function getError(){
return self::$instance->errorMessages;
}
}
abstract class AbstractClass
{
// Force Extending class to define this method
abstract protected function isCorrect($b);
// Common Method for setting error
public function setError($errorMessage) {
ErrorClass::getInstance()->setError($errorMessage);
}
// Common Method for getting error
public function getError() {
return ErrorClass::getInstance()->getError();
}
}
class EmailValidator extends AbstractClass
{
public function isCorrect($b) {
if(!$b) {
$this->setError('EmailValidator->isCorrect();');
}
}
}
class PasswordValidator extends AbstractClass
{
public function isCorrect($b) {
if(!$b) {
$this->setError('PasswordValidator->isCorrect();');
}
}
}
// Then in your code
$errorState = 1; // used for testing purposes
$EmailValidator = new EmailValidator();
$EmailValidator->isCorrect($errorState);
$PasswordValidator = new PasswordValidator();
$PasswordValidator->isCorrect($errorState);
echo $EmailValidator->getError();
echo $PasswordValidator->getError();
I have been designing a site locally in PHP 5, but have come across a few design issues I'd like advice now.
Currently there are three features of the site and each feature has a class . These features are as follows :
a blog
a friends list
a set of images
I have a class for each but in each class I basically define a similar method that gets all [blogs | Friends | images]. I was wondering if any of you know how I could reduce these classes to be much thinner and probably have one class that is generic between all three features for all methods that are the same for each feature. (i.e getAllById($feature, $id)).
An example function for my existing blog class is as follows:
function getBlogsByUserId($userId) {
global $db;
$blogs = array();
$db->where(array("userId"=>$userId));
$rows = $db->get("blog")->fetch(0);
foreach($rows as $row) {
$blog = new Blog();
$blog->id = $row['id'];
$blog->userId = $row['userId'];
$blog->content = $row['content'];
$blogs[] = $blog;
}
return $blogs;
}
Note: I have defined my own class for the DB stuff so don't worry about that.
I've looked at the gateway design pattern but haven't yet found a solution.
I also want this to be reusable so if I increase the features to seven or more then I won't have to change much of the class.
Thanks,
Matt
You could create a parent class called, say, Model, like so:
abstract class Model {
protected static $_featureTable;
static public function getAllById($id) {
global $db;
$items = array();
$db->where(array("userId"=>$userId));
$rows = $db->get(self::$_featureTable)->fetch(0);
foreach($rows as $row) {
$item = self::getInstance();
$item->setValues($row);
$items[] = $item;
}
return $items;
}
abstract static protected function getInstance();
abstract protected function setValues($row);
}
class Blog extends Model {
protected static $_featureTable = 'blogs';
protected static function getInstance() {
$self = __CLASS__;
return new $self();
}
protected function setValues($row) {
$this->content = $row['content'];
// etc.
}
}
Then, to get a list of blogs:
$blogs = Blog::getAllById($id);
Maybe you should have a look at some ORM systems like Doctrine or Propel. This will help you a lot with your database <-> object mapping.
And I know that at least Doctrine supports inheritances of tables and of course maps this structure also to a class hierarchy (which enables you to implement common methods in the parent class).
You could create a parameterizeable Factory object/function. It would aggregate a 'row_to_object' function and a 'query' object:
function row_to_blog( $row ) {
return new Blog( $row["id"], $row["title"] );
}
function create_from_query( $query, $row_to_object ) {
$objects=array();
foreach( $row as $db->fetch( $query ) ) {
$objects[]=$row_to_object( $row );
}
return $objects;
}
$query=new Query( "blogs", new Where("userid",$id) );
$blogs=create_from_query( $query, row_to_blog );
I am working with PHP classes and objects now. In this question the names of fields and methods are made up just so you get an idea of what I am talking about.
It is related to using the singleton and registry design patterns.
Now lets say I need to access a Database object, Cache Object, Core Settings object, Session object in almost every other class I will need to have access to these. SO I would use a registry to store all 4 of those object into 1 registry class object. I could then easiyl just pass in my 1 object into any other object that needs to access these. So that sounds great so far but what if I have some classes that do not need all 4 of those objects, what If I ONLY need to access the Database object or the Session object in some of my other classes? For perfromance would it be best to just use a singleton inside these other objects or would it be the same to go ahead and use my registry in these?
I do not know well enough how the objects work in PHP to know if there would be any sort of performnce gain (less memory usage, cpu usage, load time).
So anyone with experience in this maybe can tell me if there would be any gain using one or the other, I am at the stage where I can go ether way without it affecting production time or anything. I would like to use the best method if I can now.
That depends on your application. If you still need 3 out of the 4 classes, then it'd be more ideal to use the Registry than to handle the 3 independently only because you don't need the fourth. Loading the classes lazily would be one approach to reduce memory footprint, but then you need to instruct the registry when to create the objects and that's not much different than handling singletons. Alternatively, you could create an n-parameter constructor or use an array to instruct your Registry which classes to instantiate during construction.
class Registry {
public $class1;
public $class2;
function __construct($uses) {
foreach($uses as $class) {
$this->{$class} = new {$class}();
}
}
}
Then instantiate your Registry by specifying which classes to instantiate.
$reg = new Registry(array('class1'));
You would obviously want your constructor to handle zero parameters to account for instantiating all classes by default.
You can implement lazy loading to only load the objects you really need:
class Registry
{
private static $database = null;
private static function connectDatabase($key)
{
[... do connection stuff ...]
}
public static function getDatabase($key)
{
if (Registry::$database == null)
{
Registry::connectDatabase($key);
}
return Registry::$database;
}
}
The code to register the database connection parameters is left as exercise to the reader.
Perhaps this is the proper Singleton-Registry pattern. OFC, you can implement different things, SplFixedArray, ArrayAccess interface, and others. Also it is not bad to add the destruct and destroy inner objects to ensure no leak possible.
class oRegistry{
private static $instance = null;
private $storage = array();
private function __construct(){}
private function __clone(){}
public static function getInstance(){
if( self::$instance === null ){
self::$instance = new self();
}
return self::$instance;
}
public function attach($name, $o) {
if( true === isset($this->storage[$name]) ) {
throw new Exception('The instance with name '.$name.' already exists in registry.');
}
if( !empty( $name ) ) {
$this->storage[ $name ] = $o;
}
}
public function detach( $name ){
if( isset( $this->storage[ $name ] ) ) {
$this->storage[ $name ] = null;
unset( $this->storage[ $name ] );
}
}
public function get( $name ){
if( false === isset( $this->storage[$name] ) ) {
throw new Exception('Invalid instance requested');
}
return $this->storage[ $name ];
}
}
// usage example
$storage = oRegistry::getInstance();
$obj = new stdClass;
$obj2 = new stdClass;
$obj->test1 = 'test';
$obj2->test2 = 't2';
$storage->attach( 'test1', $obj );
$storage->attach( 'test2', $obj2 );
$got = $storage->get( 'test2' );
var_dump($got); // object(stdClass)#3 (1) { ["test2"]=> string(2) "t2" }
$storage->detach( 'test2' );
$got = $storage->get( 'test2' );
var_dump($got); // bool(false)