I have such model for my company with have method setOptions for creating object from array or Zend_DB_Table_Row
<?php
class Model_Company
{
protected $c_id;
protected $c_shortname;
protected $c_longname;
public function __constructor(array $options = null)
{
if(is_array($options)) {
$this->setOptions($options);
}
}
public function setOptions($data)
{
if($data instanceof Zend_Db_Table_Row_Abstract) {
$data = $data->toArray();
}
if(is_object($data)) {
$data = (array)$data;
}
if(!is_array($data)) {
throw new Exception('Initial data must be object or array');
}
$methods = get_class_methods($this);
foreach($data as $key => $value) {
$method = 'set'.ucfirst($key);
if(in_array($method, $methods)) {
$this->{'c_'.$key} = $value;
}
}
return $this;
}
I also have company manager with param like model and adapter for different db/soap/rest
class Model_CompanyManager
{
private $_adapter;
public function __construct(Model_Company $model,$adapter)
{
$this->_adapter = $adapter;
$this->_model = $model;
return $this;
}
/**
* Save data
* #var Model_Company $data
*/
public function save(array $data)
{
$data = $this->_model->setOptions($data);
$this->_adapter->save($data);
}
}
And DBTable in DBTable
class Model_DbTable_Company extends Zend_Db_Table_Abstract
{
protected $_name = 'company';
protected $_id = 'c_id';
public function save(Model_Company $data)
{
try {
if(isset($data['c_id'])) {
$this->update($data, 'c_id = :c_id',array('c_id' => $data['c_id']));
} else {
$this->insert($data);
}
} catch (Zend_Db_Exception $e) {
throw new Exception($e->getMessage());
}
}
}
How can I insert into db because model properties are protected and I can't do (array)$data
$this->_companyManager = new Model_CompanyManager(new Model_Company,new Model_DbTable_Company);
$this->_companyManager->save($values);
I don't what to create array with fields name inside save like this:
$data = array(
'c_shortname' => $company->getShortname(),
'c_longname' => $company->getLongName(),
'c_description' => $company->getDescription(),
'c_salt' => $company->getSalt(),
'c_password' => $company->getPassword(),
'c_updated_at' => date('YmdHis')
);
Because when I gonna change model fields names and other stuff I have to remember also to change here... Is there is simple approach , pattern that model keep everything and it clean
If you simply want to get a list of all your objects properties with reflection you could use get_class_vars. With a recent version of PHP this will return all the class variables regardless of scope. But since you are tossing in this Manager, usually called a Data Mapper, I assume you expect your model objects will not align exactly to a database table. Otherwise you should just stick to the table-row-gateway pattern of the Zend_Db_Table classes.
I personally am a big fan of the Data Mapper pattern in conjunction with ZF applications. Only the most simple apps are going to line up to relational database tables. It encourages richer objects and writing models and business logic before database schemas.
The job of the mapper is exactly what it suggests, to map your entity to a persistence layer, so handling in the mappers save() method, the actual assignments for the SQL statement (probably building an array with your tables field names and assigning the values, maybe even saving to multiple tables) is perfectly acceptable.
If some of your objects are more simple and do align better with a table, as will certainly be the case, what I like to do is have a __toArray() method for the object. It can be responsible for returning a representation suitable for building an insert/update statement. Also useful for needing a JSON representation for serving it via AJAX. You can be as lazy as you like in writing these - use that get_class_vars function or other reflection. I usually have two mapper base classes. One with CRUD functions that essentially do what Zend_Db_Table does, and another more skeletal where I am responsible for writing more of the code. They should follow a common interface. Kinda gets you the best of both worlds.
I found this link to be a good resource for some ideas.
Related
Following from my previous post about removing ServiceLocatorAwareInterface's from my zf2 app, i am now faced with a puzzle involving object creation when using data mappers.
The current implementation of my data mapper uses a tablegateway to find specific rows, calls the service manager to obtain a domain object, then populates and returns the full object.
public function findById($userId){
$rowset = $this->gateway->select(array('id' => $userId));
$row = $rowset->current();
if (!$row) {
throw new \DomainException("Could not find user with id of $userId in the database");
}
$user = $this->createUser($row);
return $user;
}
public function createUser($data){
$userModel = $this->getServiceManager()->get('Model\User');
$hydrator = $this->getHydrator();
if($data instanceof \ArrayObject){
$hydrator->hydrate($data->getArrayCopy(), $userModel);
}else{
$hydrator->hydrate($data, $userModel);
}
return $userModel;
}
The model needs to be called from the service manager because it has other dependencies, so calling $user = new App\Model\User() from within the mapper is not an option.
However, now i am removing instances of the servicemanager from my code, i am unsure of the best way to get the model into the mapper. The obvious answer is to pass it in the constructor and save the instance as a property of the mapper:
public function __construct(TableGateway $gateway, \App\Model\User $userModel){
$this->_gateway = $gateway;
$this->_userModel= $userModel;
}
public function createUser($data){
$userModel = $this->_userModel;
//....snip....
}
This works to a degree, but then multiple calls to createUser (such as when finding all users, for instance) over writes each instance with the last objects data (as to be expected, but not what i want)
So i need a "new" object returned each time i call createUser, but the dependency being passed into the constructor. With the model passed into the constructor I can clone the object eg.
public function createUser($data){
$userModel = clone $this->_userModel
//....snip....
}
...but something about it doesn't seem right, code smell?
You are right, it doesn't smell good.
Designing an ORM isn't easy. There is and probably always will be discussion about the way an ORM should be designed. Now, when I'm trying to understand your design I noticed you are pointing out that your models contain the data but also have "other" dependencies. This is wrong, the models containing your data should work without any layer in your application.
Entities should work without the ORM
In my opinion you should separate your business logic (dependencies) from your data. This will have many advantages:
More expressive
Easier to test
Less coupling
More flexible
Easier to refactor
For more information about how to design your ORM layer I highly recommend browsing through these slides.
DataMaper
Lets make the UserMapper responsible for separating the in-memory objects (containing only data) from the database.
class UserMapper
{
protected $gateway;
protected $hydrator;
public function __construct(TableGateway $gateway, HydratorInterface $hydrator)
{
$this->gateway = $gateway;
$this->hydrator = $hydrator;
}
public function findOneById($id)
{
$rowset = $this->_gateway->select(array('id' => $id));
$row = $rowset->current();
if(!$row) {
throw new \DomainException("Could not find user with id of $id in the database.");
}
$user = new User;
$this->hydrator->hydrate($row, $user);
return $user;
}
public function findManyBy(array $criteria)
{
// $criteria would be array('colum_name' => 'value')
}
public function save(User $user)
{
$data = $this->hydrator->extract($user);
// ... and save it using the $gateway.
}
}
For more information about the responsibility of data mappers check out Martin Fowler's definition.
Buniness Logic
It's recommended not to place any model related business logic directly into the Controller. Therefor lets just create a simple UserService which will handle validation. If your fond of form objects you could also use Zend\Form\Form in this process.
class UserService
{
protected $inputFilter;
protected $hydrator;
public function __construct(InputFilter $inputFilter, HydratorInterface $hydrator)
{
$this->inputFilter = $inputFilter;
$this->hydrator = $hydrator;
}
protected function validate(array $data)
{
// Use the input filter to validate the data;
}
public function createUser(array $data)
{
$validData = $this->validate($data);
$user = new User;
$this->hydrator->hydrate($validData, $user);
return $user;
}
}
Data Object
Now lets make the objects containing the data Plain Old PHP Objects, not bound by any restriction. This means they are not coupled with any logic and we could use them anywhere. For instance if we decide to replace our ORM with another like Doctrine.
class User
{
protected $name;
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
More information about the concept of Plain Old PHP Objects can be found on Wikipedia's explanation of POJO.
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;
Can anyone tell me how to determine query type i.e. select, update, delete or insert before it is executed over MySQL.
I strongly believe internally Zend Framework might be using mysql*_query function to execute them.
I need a centralized function which will return the query type before the execution.
I am using three files for every database table
I am giving you the example.
Say I want to create model for categories table.
So I will create following files
DbTable/Categories.php
class Application_Model_DbTable_Categories extends Zend_Db_Table_Abstract {
protected $_name = 'categories';
protected $_dependentTables = array('Application_Model_DbTable_Videos');
}
Categories.php
class Application_Model_Categories extends Application_Model_CommonGetterSetter {
protected $_type = array('id' => 'int', 'name' => 'string', 'slug' => 'string', 'status' => 'int');
public function __construct(array $options = null) {
parent::__construct($options, __CLASS__);
}
}
CategoriesMapper.php
class Application_Model_CategoriesMapper {
protected $_dbTable;
public function setDbTable($dbTable) {
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTable = $dbTable;
return $this;
}
public function getDbTable() {
if (null === $this->_dbTable) {
$this->setDbTable('Application_Model_DbTable_Categories');
}
return $this->_dbTable;
}
public function save(Application_Model_Categories $categories) {
$data = array(
'name' => $categories->name,
'slug' => $categories->slug,
'status' => $categories->status,
);
if (#$categories->id <= 0) {
return $this->getDbTable()->insert($data);
} else {
$this->getDbTable()->update($data, array('id = ?' => $categories->id));
return $categories->id;
}
}
CommonGetterSetter.php
class Application_Model_CommonGetterSetter {
protected $_data = array();
private $_class_name;
public function __construct(array $options = null, $class_name = null) {
if (is_array($options)) {
foreach ($options as $key => $value) {
$this->$key = $value;
}
$this->_class_name = $class_name;
}
}
public function __set($name, $value) {
if (!array_key_exists($name, $this->_type)) {
throw new Exception("Invalid {$this->_class_name} property". $name);
}
else {
settype($value, $this->_type[$name]);
$this->_data[$name] = $value;
}
}
public function __get($name) {
if (!array_key_exists($name, $this->_type)) {
throw new Exception("Invalid {$this->_class_name} property". $name);
}
else {
return $this->_data[$name];
}
}
}
So I want to find out which query was executed , where and what should i write?
Mny thanks in advance.
I know the code is bit long but that was to give a complete idea.
I strongly believe internally Zend Framework might be using mysql*_query function to execute them.
ZF's Database class does not have support for the decrepit mysql extension. It has adapters for PDO and mysqli. You'll know which of these you are using because you have to expressly specify one in your code or configuration.
So I want to find out which query was executed
I think that you're going to be better served by rethinking your design. It's not entirely clear what is going on here, nor is it clear why you think you need to know the query type, nor is it clear where you expect to need to know the query type.
Please review the Zend_Db_Table documentation, as it looks like you might have missed the point somewhere along the way.
In specific, there are already methods provided by Zend_Db_Table for inserts, deletes and updates. If you need to change the behavior of the appropriate method, you should do so by simply overriding it in your class.
Further, it looks like you're trying to build some sort of smart column type definition system. You don't need to do that, as again, Zend_Db_Table already provides this information to you, and even lets you hard-code it if you'd rather it not determine this information automatically.
ZF is a complicated beast. If you're going to use it, I suggest that you fully understand everything it can do. Building non-standard, redundant architecture is only going to complicate things for you later on.
EDIT : I was looking at the code for the Zend_Db_Table and Zend_Db_Table_Abstract and my second solution seems much more difficult now.
Hey guys,
If you want to give yourself a bit of a headache, what you can do is keep using the multidb resource to create two connections, one to the slave and one for the master.
Set the default connection to the one that will be used the most.
Then, with the other connection you can fetch it manually every time you need to use it and deal directly with the adapter instead of going through the DbTable class.
For example, create another method for the mapper classes called getOtherDbAdapter() that gets the other adapter. Then you can use the adapter from there to perform selects or inserts.
OH!
Another solution is that you can extend extend the class Zend_Db_Table or Zend_Db_Table_Abstract and create another variable for the second db connection.
Then, copy and paste the implementation code for all the fetch/select/insert/update methods from the Zend_Db_Table_Abstract class and have it use the the other adapter whenever you see the code performing the operations you want to have redirected to the other db.
I have a PHP MVC application using Zend Framework. As presented in the quickstart, I use 3 layers for the model part :
Model (business logic)
Data mapper
Table data gateway (or data access object, i.e. one class per SQL table)
The model is UML designed and totally independent of the DB.
My problem is : I can't have multiple instances of the same "instance/record".
For example : if I get, for example, the user "Chuck Norris" with id=5, this will create a new model instance wich members will be filled by the data mapper (the data mapper query the table data gateway that query the DB). Then, if I change the name to "Duck Norras", don't save it in DB right away, and re-load the same user in another variable, I have "synchronisation" problems... (different instances for the same "record")
Right now, I use the Multiton / Identity Map pattern : like Singleton, but multiple instances indexed by a key (wich is the user ID in our example). But this is complicating my developpement a lot, and my testings too.
How to do it right ?
Identity Map
Edit
In response to this comment:
If I have a "select * from X", how can I skip getting the already loaded records ?
You can't in the query itself, but you can in the logic that loads the rows into entity objects. In pseudo-code:
class Person {}
class PersonMapper {
protected $identity_map = array();
function load($row) {
if (!isset($this->identity_map[$row['id']])) {
$person = new Person();
foreach ($row as $key => $value) {
$person->$key = $value;
}
$this->identity_map[$row['id']] = $person;
}
return $this->identity_map[$row['id']];
}
}
class MappingIterator {
function __construct($resultset, $mapper) {
$this->resultset = $resultset;
$this->mapper = $mapper;
}
function next() {
$row = next($this->resultset);
if ($row) {
return $this->mapper->load($row);
}
}
}
In practice, you'd probably want your MappingIterator to implement Iterator, but I skipped it for brevity.
Keep all loaded model instances in "live model pool". When you load/query a model, first check if it has been already loaded into pool (use primary key or similar concept). If so, return the object (or a reference) from pool. This way all your references point to the same object. My terminology may be incorrect but hopefully you get the idea. Basically the pool acts as a cache between business logic and database.
Multiton
Best option if you want to use a variety of singletons in your project.
<?php
abstract class FactoryAbstract {
protected static $instances = array();
public static function getInstance() {
$className = static::getClassName();
if (!(self::$instances[$className] instanceof $className)) {
self::$instances[$className] = new $className();
}
return self::$instances[$className];
}
public static function removeInstance() {
$className = static::getClassName();
if (array_key_exists($className, self::$instances)) {
unset(self::$instances[$className]);
}
}
final protected static function getClassName() {
return get_called_class();
}
protected function __construct() { }
final protected function __clone() { }
}
abstract class Factory extends FactoryAbstract {
final public static function getInstance() {
return parent::getInstance();
}
final public static function removeInstance() {
parent::removeInstance();
}
}
// using:
class FirstProduct extends Factory {
public $a = [];
}
class SecondProduct extends FirstProduct {
}
FirstProduct::getInstance()->a[] = 1;
SecondProduct::getInstance()->a[] = 2;
FirstProduct::getInstance()->a[] = 3;
SecondProduct::getInstance()->a[] = 4;
print_r(FirstProduct::getInstance()->a);
// array(1, 3)
print_r(SecondProduct::getInstance()->a);
// array(2, 4)
I'm working on creating a domain layer in Zend Framework that is separate from the data access layer. The Data Access Layer is composed to two main objects, a Table Data Gateway and a Row Data Gateway. As per Bill Karwin's reply to this earlier question I now have the following code for my domain Person object:
class Model_Row_Person
{
protected $_gateway;
public function __construct(Zend_Db_Table_Row $gateway)
{
$this->_gateway = $gateway;
}
public function login($userName, $password)
{
}
public function setPassword($password)
{
}
}
However, this only works with an individual row. I also need to create a domain object that can represent the entire table and (presumably) can be used to iterate through all of the Person's in the table and return the appropriate type of person (admin, buyer, etc) object for use. Basically, I envision something like the following:
class Model_Table_Person implements SeekableIterator, Countable, ArrayAccess
{
protected $_gateway;
public function __construct(Model_DbTable_Person $gateway)
{
$this->_gateway = $gateway;
}
public function current()
{
$current = $this->_gateway->fetchRow($this->_pointer);
return $this->_getUser($current);
}
private function _getUser(Zend_Db_Table_Row $current)
{
switch($current->userType)
{
case 'admin':
return new Model_Row_Administrator($current);
break;
case 'associate':
return new Model_Row_Associate($current);
break;
}
}
}
Is this is good/bad way to handle this particular problem? What improvements or adjustments should I make to the overall design?
Thanks in advance for your comments and criticisms.
I had in mind that you would use the Domain Model class to completely hide the fact that you're using a database table for persistence. So passing a Table object or a Row object should be completely under the covers:
<?php
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
$db = Zend_Db::factory('mysqli', array('dbname'=>'test',
'username'=>'root', 'password'=>'xxxx'));
Zend_Db_Table_Abstract::setDefaultAdapter($db);
class Table_Person extends Zend_Db_Table_Abstract
{
protected $_name = 'person';
}
class Model_Person
{
/** #var Zend_Db_Table */
protected static $table = null;
/** #var Zend_Db_Table_Row */
protected $person;
public static function init() {
if (self::$table == null) {
self::$table = new Table_Person();
}
}
protected static function factory(Zend_Db_Table_Row $personRow) {
$personClass = 'Model_Person_' . ucfirst($personRow->person_type);
return new $personClass($personRow);
}
public static function get($id) {
self::init();
$personRow = self::$table->find($id)->current();
return self::factory($personRow);
}
public static function getCollection() {
self::init();
$personRowset = self::$table->fetchAll();
$personArray = array();
foreach ($personRowset as $person) {
$personArray[] = self::factory($person);
}
return $personArray;
}
// protected constructor can only be called from this class, e.g. factory()
protected function __construct(Zend_Db_Table_Row $personRow) {
$this->person = $personRow;
}
public function login($password) {
if ($this->person->password_hash ==
hash('sha256', $this->person->password_salt . $password)) {
return true;
} else {
return false;
}
}
public function setPassword($newPassword) {
$this->person->password_hash = hash('sha256',
$this->person->password_salt . $newPassword);
$this->person->save();
}
}
class Model_Person_Admin extends Model_Person { }
class Model_Person_Associate extends Model_Person { }
$person = Model_Person::get(1);
print "Got object of type ".get_class($person)."\n";
$person->setPassword('potrzebie');
$people = Model_Person::getCollection();
print "Got ".count($people)." people objects:\n";
foreach ($people as $i => $person) {
print "\t$i: ".get_class($person)."\n";
}
"I thought static methods were bad
which is why I was trying to create
the table level methods as instance
methods."
I don't buy into any blanket statement that static is always bad, or singletons are always bad, or goto is always bad, or what have you. People who make such unequivocal statements are looking to oversimplify the issues. Use the language tools appropriately and they'll be good to you.
That said, there's often a tradeoff when you choose one language construct, it makes it easier to do some things while it's harder to do other things. People often point to static making it difficult to write unit test code, and also PHP has some annoying deficiencies related to static and subclassing. But there are also advantages, as we see in this code. You have to judge for yourself whether the advantages outweigh the disadvantages, on a case by case basis.
"Would Zend Framework support a Finder
class?"
I don't think that's necessary.
"Is there a particular reason that you
renamed the find method to be get in
the model class?"
I named the method get() just to be distinct from find(). The "getter" paradigm is associated with OO interfaces, while "finders" are traditionally associated with database stuff. We're trying to design the Domain Model to pretend there's no database involved.
"And would you use continue to use the
same logic to implement specific getBy
and getCollectionBy methods?"
I'd resist creating a generic getBy() method, because it's tempting to make it accept a generic SQL expression, and then pass it on to the data access objects verbatim. This couples the usage of our Domain Model to the underlying database representation.