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
Related
I'm trying to make some very rudimental database mapping conversion where I'm fetching data from the database and then trying to convert that array to an instance of an arbitrary class. This should be dynamic so that I can use the same function no matter the output object class/properties.
Let's say I have CASE1:
$student = [
'name' => 'Jhon',
'surname' => 'Wick',
'age' => 40
];
class Student{
private string $name;
private string $surname;
private int $age;
... getter and setters
}
CASE2:
$car = [
'manufacturer' => 'Toyota',
'model' => 'one of their model',
'commercialName' => 'the name'
];
class Car{
private $manufacturer;
private $model;
private $commercialName;
// Getter and Setter
}
And I want something that transforms the $student array var to a Student instance or the $car to Car. How can I do that?
I know I can do that using something like:
$funcName = 'get' . ucfirst($camelCaseName);
if (method_exists($this, $funcName)) {
$funcValue = $this->$funcName();
}
But I'm searching for something a little more modern like using Reflection.
What is the best approach to this? What could be an efficient solution?
To give further info, this is needed for an extension of the WordPress $wpdb object. If possible I wouldn't like to use public class properties because I may need to actually call the class setter in some case to change some other class value. Let's say something like giving birth date should calculate age
As I stated out in the comments already, all that you need is the process of hydration. The boys and girls from Laminas got a good maintained package called laminas/laminas-hydrator which can do the job for you.
An easy example:
<?php
declare(strict_types=1);
namespace Marcel;
use Laminas\Hydrator\ClassMethodsHydrator;
use Marcel\Model\MyModel;
$hydrator = new ClassMethodsHydrator();
// hydrate arry data into an instance of your entity / data object
// using setter methods
$object = $hydrator->hydrate([ ... ], new MyModel());
// vice versa using the getter methods
$array = $hydrator->extract($object);
Your approach is not so wrong. It is at least going in the right direction. In my eyes you should not use private properties. What kind of advantage do you expect from using private properties? They only bring disadvantages. Think of extensions of your model. Protected properties do the same job for just accessing the properties via getters and setters. Protected properties are much more easy to handle.
<?php
declare(strict_types=1);
namespace Marcel;
class MyDataObject
{
public ?string $name = null;
public ?int $age = null;
public function getName(): ?string
{
return $name;
}
public function setName(?string $name): void
{
$this->name = $name;
}
public function getAge(): ?int
{
return $this->age;
}
public function setAge(?int $age): void
{
$this->age = $age;
}
}
class MyOwnHydrator
{
public function hydrate(array $data, object $object): object
{
foreach ($data as $property => $value) {
// beware of naming your properties in the data array the right way
$setterName = 'set' . ucfirst($property);
if (is_callable([$object, $setterName])) {
$object->{$setterName}($value);
}
}
return $object;
}
}
// example
$data = [
'age' => 43,
'name' => 'Marcel',
'some_other_key' => 'blah!', // won 't by hydrated
];
$hydrator = new MyOwnHydrator();
$object = new MyDataObject();
$object = $hydrator->hydrate($data, $object);
This is the simplest hydrator you can get. It iterates through the data array, creates setter method names and checks if they are callable. If so the value will be hydrated into the object by calling the setter method. At the end you 'll get a hydrated object.
But beware. There are some stumbling blocks with this solution that need to be considered. The keys of your data array must be named exactly like the properties of your data object or entity. You have to use some naming strategies, when you want to use underscore separated keys in your array but your object properties are camel cased, e.g. Another problem could be type hints. What if the example had a birthday and only accepts DateTime instances and your data array only contains the birhtday as string "1979-12-19"? What if your data array contains sub arrays, that should be hydrated in n:m relations?
All that is done already in the menetiond laminas package. If you don 't need all that fancy stuff, follow your first thought and build your own hydrator. ;)
I would say drop setters/getters and use readonly properties:
class Car{
public function __construct(
public readonly string $manufacturer,
public readonly string $model,
public readonly string $commercialName,
);
}
...
new Car(...$car);
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.
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.
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'm using PHPUnit but find it difficult to make it create good mocks and stubs for objects used as datastore.
Example:
class urlDisplayer {
private $storage;
public function __construct(IUrlStorage $storage) { $this->storage = $storage; }
public function displayUrl($name) {}
public function displayLatestUrls($count) {}
}
interface IUrlStorage {
public function addUrl($name, $url);
public function getUrl($name);
}
class MysqlUrlStorage implements IUrlStorage {
// saves and retrieves from database
}
class NonPersistentStorage implements IUrlStorage {
// just stores for this request
}
Eg how to have PHPUnit stubs returning more than one possible value on two calls with different $names?
Edit: example test:
public function testUrlDisplayerDisplaysLatestUrls {
// get mock storage and have it return latest x urls so I can test whether
// UrlDisplayer really shows the latest x
}
In this test the mock should return a number of urls, however in the documentation I only how to return one value.
Your question is not very clear - but I assume you are asking how to use phpunit's mock objects to return a different value in different situations?
PHPUnit's mock classes allow you specify a custom function (ie: a callback function/method) - which is practically unlimited in what it can do.
In the below example, I created a mock IUrlStorage class that will return the next url in its storage each time it is called.
public function setUp()
{
parent::setUp();
$this->fixture = new UrlDisplayer(); //change this to however you create your object
//Create a list of expected URLs for testing across all test cases
$this->expectedUrls = array(
'key1' => 'http://www.example.com/url1/'
, 'key2' => 'http://www.example.net/url2/'
, 'key3' => 'http://www.example.com/url3/'
);
}
public function testUrlDisplayerDisplaysLatestUrls {
//Init
$mockStorage = $this->getMock('IUrlStorage');
$mockStorage->expects($this->any())
->method('getUrl')
->will( $this->returnCallback(array($this, 'mockgetUrl')) );
reset($this->expectedUrls); //reset array before testing
//Actual Tests
$this->assertGreaterThan(0, count($this->expectedUrls));
foreach ( $this->expectedUrls as $key => $expected ) {
$actual = $this->fixture->displayUrl($key);
$this->assertEquals($expected, $actual);
}
}
public function mockGetUrl($name)
{
$value = current($this->expectedUrls);
next($this->expectedUrls);
//Return null instead of false when end of array is reached
return ($value === false) ? null : $value;
}
Alternatively, sometimes it is easier to simply create a real class that mocks up the necessary functionality. This is especially easy with well defined and small interfaces.
In this specific case, I would suggest using the below class instead:
class MockStorage implements IUrlStorage
{
protected $urls = array();
public function addUrl($name, $url)
{
$this->urls[$name] = $url;
}
public function getUrl($name)
{
if ( isset($this->urls[$name]) ) {
return $this->urls[$name];
}
return null;
}
}
?>
Then in your unit test class you simply instantiate your fixture like below:
public function setUp() {
$mockStorage = new MockStorage();
//Add as many expected URLs you want to test for
$mockStorage->addUrl('name1', 'http://example.com');
//etc...
$this->fixture = new UrlDisplayer($mockStorage);
}