How do you use namespacing with php packages and phpunit? - php

EDITED to add code for MainClass as requested in comments.
I'm trying to learn how to make php packages and how to use phpunit at the same time, I may be using all the wrong terminology here and doing everything wrong..
Everything works as expected when I import the package with composer. I decided to add some unit tests as I finally started to see how they could be useful to me however I am having trouble and I suspect it has something to do with namespaces but I'm not sure.
I have put the tests in their own directory and used use statements at the top of the test classes for importing the main src classes. So, for example, in a test class I have the following:
use myname\Package\MainClass;
use myname\Package\Resources\Resource;
use myname\Package\Resources\ExtendedResource;
class ResourceTest extends PHPUnit_Framework_TestCase {
public function testArtistIsResource() {
$resource = '\\myname\\Package\\Resources\\Resource';
$main = new MainClass('Artist');
$artist = $main->find(1383508);
$this->assertTrue($artist instanceof $resource);
}
}
This test passes. I am running the same code outside of the test directory and displaying values in the browser to see what happens. I am running the package in laravel and just running the comparison code on the homepage. For example the output of the following code is printed directly in the browser
$main = new MainClass('Artist');
$artist = $main->find(1383508);
echo get_class($main);
echo get_class($artist);
This shows that $main is a myname\Package\MainClass and $artist is a myname\Package\Resources\ExtendedResource. If I change the second echo statement above to this:
echo get_class($artist->get());
Then the result is that stdClass is echoed to the screen, which is correct. However in the test class $artist->get() returns an instance of ResourceTest and I don't understand why it is not another stdClass.
What am I missing?
My directory structure is as follows:
myname
|- Package
|- composer.json
|- src
|- MainClass.php
|- Resources
|- Resource.php
|- ExtendsResource.php
|- tests
|- Resources
|- ResourceTest.php
Below is the content of the autoload part of composer.json
"autoload": {
"psr-4": {
"myname\\Package\\": "src/"
}
}
I tried adding another namespace in there - "myname\Package\tests\": "tests/" - but that didn't seem to help.
The namespace for MainClass.php is myname\Package.
The namespace for Resources.php and ExtendsResources.php is myname\Package\Resources.
Here is the code for MainClass.php
<?php
namespace myname\Package;
class MainClass {
private $resource;
public function __construct($resource = null) {
Container::setup();
if ($resource) {
$this->resource = Container::get($resource);
}
return $this->resource;
}
public function find($id) {
if (isset ($this->resource)) {
$this->resource->find($id);
return $this->resource;
}
else { throw new \Exception('Resource is not set'); }
}
public function setResource($resource) {
$this->resource = Container::get($resource);
return $this->resource;
}
}
Below is the code for myname\Package\Resources\Reource
<?php
namespace myname\Package\Resources;
use myname\Package\Contracts\ConfigInterface;
use myname\Package\Contracts\GrabberInterface;
use myname\Package\Contracts\ResourceInterface;
use myname\Package\Http\Grabber;
use myname\Package\Http\Poster;
abstract class Resource {
protected $config = null;
protected $url = null;
protected $resource = null;
protected $response = null;
protected $grabber = null;
protected $perPage = null;
protected $page = null;
protected $params = null;
protected $token = null;
protected $identifier = null;
protected $update = array();
protected $appendTokenTo = array(
'myname\Package\Resources\Artist',
'myname\Package\Resources\Listing',
'myname\Package\Resources\Release',
'myname\Package\Resources\Search'
);
public function __construct($config, $grabber) {
if ($config instanceof ConfigInterface) {
$this->config = $config;
} else { throw new \Exception('The supplied $config is not an instance of ConfigInterface'); }
if ($grabber instanceof GrabberInterface) {
$this->grabber = $grabber;
} else { throw new \Exception('The supplied $grabber is not an instance of GrabberInterface'); }
$this->url .= $this->config->getApiUrl() . $this->resource;
$this->token = $this->config->getApiToken();
return $this;
}
public function addParam($param, $value) {
$value= $this->formatParamValue($value);
$this->params .= "&". "$param=$value";
return $this;
}
protected function addToken(){
$this->addParam('token', $this->token);
//$this->params .= "&". "token=$this->token";
return $this;
}
public function addUpdate($update, $value) {
$this->update[$update] = $value;
return $this;
}
protected function checkIfTokenIsRequired() {
if (in_array(get_class($this), $this->appendTokenTo)) {
$this->addToken();
}
}
public function find($identifier) {
if (empty ($this->identifier)) {
$this->identifier = $identifier;
$this->url .= "/$identifier";
}
return $this;
}
protected function formatParamValue($value) {
$value = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
$value = str_replace(' ', '+', $value);
return $value;
}
public function get() {
return json_decode($this->getResponse());
}
protected function getResponse() {
if (!isset($this->response)) {
$this->checkIfTokenIsRequired();
$this->_prepare();
}
return $this->response;
}
public function json() {
return $this->getResponse();
}
public function page($pageNumber) {
$this->page = $pageNumber;
return $this;
}
public function perPage($resultsPerPage) {
$this->perPage = $resultsPerPage;
return $this;
}
protected function _prepare() {
$params = 0;
if (isset($this->page) OR isset($this->perPage) OR isset($this->params)){
$this->url .= '?';
}
if (isset($this->params)) {
$this->url .= "$this->params";
$params++;
}
if (isset($this->perPage)) {
if ($params > 0) {
$this->url .= '&';
}
$this->url .= "per_page=$this->perPage";
$params++;
}
if (isset($this->page)) {
if ($params > 0) {
$this->url .= '&';
}
$this->url .= "page=$this->page";
$params++;
}
$this->grabber->setUrl($this->url);
$this->response = $this->grabber->grab();
}
public function setGrabber($grabber) {
if ($grabber instanceof GrabberInterface) {
$this->grabber = $grabber;
} else { throw new \Exception($grabber . " is not an instance of GrabberInterface"); }
}
public function setUrl($url) {
$this->url = $url;
}
public function update() {
//$this->update['token'] = $this->token;
$this->grabber->setMethod('POST');
$this->grabber->setUpdates(json_encode($this->update));
$this->_prepare();
return $this;
}
}
Below is the code for the ExtendedResource
<?php
namespace myname\Package\Resources;
class ExtendedResource extends Resource {
protected $resource = 'artists';
/**
* Returns the releases associated with an artist
*
* #return $this
*/
public function releases() {
$this->url .= '/releases';
return $this;
}
}

Related

PHPUnit - Mockery::mock vs Mockery::namedMocks

I'm writing PHPUnit Test with Mockery, (PHP v5.6.32, PHPUnit 3.7.21, Mockery dev-master) and found something which I can't understand about using Mockery::mock and Mockery::namedMocks.
My code is below, and the questions are:
Am I correct to use in LegendTest.php the Mockery::namedMock() instead of Mockery::mock() for SignalsCollection object?
Regarding to documentation about namedMock, I expect that frist argument is the Class name (SignalsCollection) and the second argument should be the extends statement (\ArrayObject) - but in my case I'm getting an error: Mockery\Exception\BadMethodCallException : Received Charts\SignalsCollection::getIterator(), but no expectations were specified, so I'm giving only one argument and this works fine. Why? What am I doing wrong? I'm confused.
Did I missed something in this test case or should I do something different to make tests better?
Signal.php:
class Signal
{
protected $id = 0;
protected $colName = '';
protected $tableName = '';
public function getId()
{
return $this->id;
}
public function setColName($colName)
{
$this->colName = $colName;
return $this;
}
public function setTableName($tableName)
{
$this->tableName = $tableName;
return $this;
}
}
SignalsCollection.php:
class SignalsCollection extends \ArrayObject
{
}
Legend.php
class Legend
{
protected $signalsCollection = null;
protected $graphModel = null;
public function __construct(SignalsCollection $signalsCollection, GraphModel $graphModel)
{
$this->signalsCollection = $signalsCollection;
$this->graphModel = $graphModel;
}
public function getSignalsCollection()
{
return $this->signalsCollection;
}
public function removeSignal(Signal $signal)
{
foreach ($this->signalsCollection as $key => $item) {
if ($item->getId() === $signal->getId()) {
$this->signalsCollection->offsetUnset($key);
break;
}
}
}
}
LegendTest.php:
class LegendTest extends \PHPUnit_Framework_TestCase
{
protected function tearDown()
{
parent::tearDown();
Mockery::close();
}
public function testRemoveSignal()
{
$testSignal = Mockery::mock('\Charts\Signal')
->shouldReceive('setColName', 'setTableName')
->andReturn(Mockery::self())
->mock();
$testSignal
->setColName('testColumnName')
->setTableName('testTableName');
$testSignalSecond = Mockery::mock('\Charts\Signal')
->shouldReceive('setId', 'setColName', 'setTableName')
->andReturn(Mockery::self())
->mock();
$testSignalSecond
->setId(1)
->setColName('testColumnName')
->setTableName('testTableName');
$signalsCollection = Mockery::namedMock('\Charts\SignalsCollection')
->shouldReceive('append', 'offsetUnset')
->andReturn(Mockery::self())
->mock();
$signalsCollection
->append($testSignal)
->append($testSignalSecond);
$legend = new Legend($signalsCollection, Mockery::mock('\Charts\GraphModel'));
$this->assertEquals($signalsCollection, $legend->getSignalsCollection());
$legend->removeSignal($testSignalSecond);
$signalsCollection->offsetUnset(1);
$this->assertEquals( $signalsCollection, $legend->getSignalsCollection() );
}
}

Laravel 5.5 Storage chain call add dir file filter class

i'am not very well OO, and search a few web site to write this class
now my problem is how to use this class just like the Laravel 5.5 build in Storage class
i want use like this
MyStorage::disk('dropbox')
->addDirFilter('(school|travel)')
->addDirFilter('\d{4}-\d{2}-\d{2}')
->addFileFilter('.*\.jpg')
->getMatch();
here is MyStorage class
<?php
namespace App\MySupport;
class MyStorage
{
private $flag;
private $disk;
private $matches;
public function __construct()
{
$this->flag = false;
$this->disk = '';
$this->matches = [];
}
public function disk($disk)
{
$this->disk = $disk;
return $this;
}
public function addDirFilter($filter)
{
if (! $this->flag)
{
$this->flag = true;
$subDirs[] = \Storage::disk($this->disk)->directories();
}
else
{
foreach ($this->matches as $dir)
{
$subDirs[] = \Storage::disk($this->disk)->directories($dir);
}
}
$this->findMatch($subDirs, $filter);
return $this;
}
public function addFileFilter($filter)
{
if (! $this->flag)
{
$this->flag = true;
$subFiles[] = \Storage::disk($this->disk)->files();
}
else
{
foreach ($this->matches as $dir)
{
$subFiles[] = \Storage::disk($this->disk)->files($dir);
}
}
$this->findMatch($subFiles, $filter);
return $this;
}
public function findMatch($subItems, $filter)
{
// set empty before update
$this->matches = [];
// if call this method by addDirFilter() , $subItem contain the Dir path, eg, DirA/DirB
// if call this method by addFileFilter() , $subItem contain the File path, eg, DirX/File.txt
foreach (array_collapse($subItems) as $subItem)
{
// get the last str, eg, DirB OR File.txt
$lastStr = #end(explode('/', $subItem));
if ( preg_match('/^' . $filter . '$/u', $lastStr) )
{
// update new matches
$this->matches[] = $subItem;
}
}
}
public function getMatch()
{
return $this->matches;
}
}
Usage
<?php
namespace App\Http\Controllers;
use App\MySupport\MyStorage;
class TestController extends Controller
{
public function storageTest()
{
$MyStorage = new MyStorage();
// for example in my dropbox disk have
//
// school/2018-01-01/emails.txt
// school/2018-02-02/Peter.jpg
// travel/2017-06-06/TW.jpg
$folders = $MyStorage->disk('dropbox')
->addDirFilter('school')
->addDirFilter('\d{4}-\d{2}-\d{2}')
->getMatch();
// $folders result is:
// school/2018-01-01
// school/2018-02-02
$files = $MyStorage->disk('dropbox')
->addDirFilter('(school|travel)')
->addDirFilter('\d{4}-\d{2}-\d{2}')
->addFileFilter('.*\.jpg')
->getMatch();
// $files result is:
// school/2018-02-02/Peter.jpg
// travel/2017-06-06/TW.jpg
}
}
i tested seems all fine
can anyone point me to the direction, how to use MyStorage class just like Laravel 5.5 build in Storage class, thanks
You should use static function.
Try replace your function as code below.
public static function disk($disk)
{
$instance = new MyStorage();
$instance->disk = $disk;
return $instance;
Furthermore, I would suggest you learn singleton design pattern

php dynamic class methods - scope issue

Hello I'm trying to implement a url router in php something familiar to express.js
Here is the code I have so far.
class Router{
private $request;
private $request_params;
private $params_num;
private $paths;
public function __construct(){
$this->paths = array();
$this->request = strtolower($this->hookRequest());
if ($this->request != false){
$this->request_params = $this->hookRequestParams();
} else {
$this->request_params = array('home');
}
}
public function __destruct(){
foreach($this->paths as $key => $value){
if($this->getRequest() == $key){
$value();
}
}
}
public function get($path, $func){
$this->paths[$path] = $func;
}
private function hookRequest(){
return isset($_GET['req']) ? rtrim($_GET['req'], '/') : false;
}
private function hookRequestParams(){
$params = explode('/', $this->request);
$this->params_num = count($params);
return $params;
}
public function getRequest(){
return $this->request;
}
public function getRequestParams(){
return $this->request_params;
}
public function getPage(){
return $this->request_params[0];
}
public function getAction(){
if($this->params_num > 1){
return $this->request_params[1];
}
return false;
}
public function getActionParams(){
if($this->params_num > 2){
return $this->request_params[2];
}
return false;
}
}
This is used like this as you can imagine:
$router = new Router();
$router->get('index', function(){
echo 'index'; //index is being shown to the browser as expectd
echo $this->getPage(); // This does not work apparently
})
My issue is how could I execute $router methods from within the anonymous function?
As shown in this example with $this->getPAge();
Use a closure..
$router->get('index', function() use ($router) {
echo 'index';
echo $router->getPage();
})
If you define your Closure within your class, $this should be work.

get set properties in php

I'm from the C# environment and I'm starting to learn PHP in school.
I'm used to set my properties in C# like this.
public int ID { get; set; }
What's the equivalent to this in php?
Thanks.
There is none, although there are some proposals for implementing that in future versions.
For now you unfortunately need to declare all getters and setters by hand.
private $ID;
public function setID($ID) {
$this->ID = $ID;
}
public function getID() {
return $this->ID;
}
for some magic (PHP likes magic), you can look up __set and __get magic methods.
Example
class MyClass {
private $ID;
private function setID($ID) {
$this->ID = $ID;
}
private function getID() {
return $this->ID;
}
public function __set($name,$value) {
switch($name) { //this is kind of silly example, bt shows the idea
case 'ID':
return $this->setID($value);
}
}
public function __get($name) {
switch($name) {
case 'ID':
return $this->getID();
}
}
}
$object = new MyClass();
$object->ID = 'foo'; //setID('foo') will be called
Thanks for your answers everyone. It helped me to create something like this:
In my parent class:
public function __get($name){
if (ObjectHelper::existsMethod($this,$name)){
return $this->$name();
}
return null;
}
public function __set($name, $value){
if (ObjectHelper::existsMethod($this,$name))
$this->$name($value);
}
ObjectHelper::existsMethod is a method which just check if given protected method exists.
private $_propertyName = null;
protected function PropertyName($value = ""){
if (empty($value)) // getter
{
if ($this-> _propertyName != null)
return $this->_propertyName;
}
else // setter
{
$this-> _propertyName = $value;
}
return null;
}
So I can use something like this in any class:
$class = new Class();
$class->PropertyName = "test";
echo $class->PropertyName;
I was inspired by C# :)
What do you think about this, guys?
Here is my ObjectHelper if someone would like to use it:
namespace Helpers;
use ReflectionMethod;
class ObjectHelper {
public static function existsMethod($obj, $methodName){
$methods = self::getMethods($obj);
$neededObject = array_filter(
$methods,
function ($e) use($methodName) {
return $e->Name == $methodName;
}
);
if (is_array($neededObject))
return true;
return false;
}
public static function getMethods($obj){
$var = new \ReflectionClass($obj);
return $var->getMethods(ReflectionMethod::IS_PROTECTED);
}
}
Mchi is right, but there is another way of doing it by using single function
private $ID;
public function ID( $value = "" )
{
if( empty( $value ) )
return $this->ID;
else
$this->ID = $value;
}
But yeah this approach is pretty much inline with what you do in c#. but this is only an alternative
Or try using php's __set and __get in your class more info here
http://php.net/manual/en/language.oop5.overloading.php
Another exampled using Variable function name
class MyClass {
private $ID;
protected $ID2;
private function setID($ID) {
$this->ID = $ID;
}
private function getID() {
return $this->ID;
}
private function setID2($ID2) {
$this->ID2 = $ID2;
}
private function getID2() {
return $this->ID2;
}
public function __set($name,$value) {
$functionname='set'.$name;
return $this->$functionname($value);
}
public function __get($name) {
$functionname='get'.$name;
return $this->$functionname();
}
}
$object = new MyClass();
$object->ID = 'foo'; //setID('foo') will be called
$object->ID2 = 'bar'; //setID2('bar') will be called
private $ID;
public function getsetID($value = NULL)
{
if ($value === NULL) {
return $this->ID;
} else {
$this->ID = $value;
}
}
I know I am a bit late to the party on this question, but I had the same question/thought myself. As a C# developer who does PHP, when the job requires, I want to have a simple way to create properties just I would be able to in C#.
I whipped up a first draft this afternoon which allows you to create the backing fields and specify their accessors or have pure accessors with no backing field. I will update my answer as the code evolves and provide a link when I get it to the state where it can be imported as a composer package.
For simplicity, I created the functionality as a PHP trait so you can drop it in to any class you want instead of having to extend a base class. Eventually I hope to extend this functionality to discern between external public calls to the properties and protected/private calls.
Here is the code for the trait itself:
trait PropertyAccessorTrait
{
private static $__propertyAccessors = [];
/* #property string $__propertyPrefix */
public function __get($name)
{
$this->__populatePropertyAcessors($name);
return $this->__performGet($name);
}
public function __set($name, $value)
{
$this->__populatePropertyAcessors($name);
$this->__performSet($name, $value);
}
public function __isset($name)
{
// TODO: Implement __isset() method.
}
public function __unset($name)
{
// TODO: Implement __unset() method.
}
protected function __getBackingFieldName($name)
{
if (property_exists(self::class, '__propertyPrefix')) {
$prefix = $this->__propertyPrefix;
} else {
$prefix = '';
}
return $prefix . $name;
}
protected function __canget($name)
{
$accessors = $this->__getPropertyAccessors($name);
return $accessors !== null && isset($accessors['get']);
}
protected function __canset($name)
{
$accessors = $this->__getPropertyAccessors($name);
return $accessors !== null && isset($accessors['set']);
}
protected function __performGet($name)
{
if (!$this->__canget($name)) {
throw new \Exception('Getter not allowed for property: ' . $name);
}
$accessors = $this->__getPropertyAccessors($name)['get'];
/* #var \ReflectionMethod $method */
$method = $accessors['method'];
if (!empty($method)) {
return $method->invoke($this);
}
return $this->{$this->__getBackingFieldName($name)};
}
protected function __performSet($name, $value)
{
if (!$this->__canset($name)) {
throw new \Exception('Setter not allowed for property: ' . $name);
}
$accessors = $this->__getPropertyAccessors($name)['set'];
/* #var \ReflectionMethod $method */
$method = $accessors['method'];
if (!empty($method)) {
return $method->invoke($this, $value);
}
$this->{$this->__getBackingFieldName($name)} = $value;
}
protected function __getPropertyAccessors($name)
{
return isset(self::$__propertyAccessors[$name])
? self::$__propertyAccessors[$name]
: null
;
}
protected function __getAccessorsFromDocBlock($docblock)
{
$accessors = [];
if (!empty(trim($docblock))) {
$doclines = null;
if (!empty($docblock)) {
$doclines = explode("\n", $docblock);
}
if (!empty($doclines)) {
foreach ($doclines as $line) {
if (preg_match('/#(get|set)\\s+(public|private|protected)/', $line, $matches)) {
$accessors[$matches[1]]['visibility'] = $matches[2];
}
}
}
}
return $accessors;
}
protected function __populatePropertyAcessors($name)
{
if ($this->__getPropertyAccessors($name) !== null) return;
try {
$property = new \ReflectionProperty(self::class, $this->__getBackingFieldName($name));
} catch (\ReflectionException $ex) {
$property = null;
}
$accessors = [];
if ($property != null) {
$accessors = $this->__getAccessorsFromDocBlock($property->getDocComment());
}
try {
$methodName = 'get' . ucfirst($name);
$method = new \ReflectionMethod(self::class, $methodName);
$method->setAccessible(true);
$accessors = array_merge($accessors, $this->__getAccessorsFromDocBlock($method->getDocComment()));
} catch (\ReflectionException $ex) {
$method = null;
}
if ($method !== null || isset($accessors['get'])) {
$accessors['get']['method'] = $method;
}
try {
$methodName = 'set' . ucfirst($name);
$method = new \ReflectionMethod(self::class, $methodName);
$method->setAccessible(true);
$accessors = array_merge($accessors, $this->__getAccessorsFromDocBlock($method->getDocComment()));
} catch (\ReflectionException $ex) {
$method = null;
}
if ($method !== null || isset($accessors['set'])) {
$accessors['set']['method'] = $method;
}
self::$__propertyAccessors[$name] = $accessors;
}
}
Here is a quick unit test I created using the Codeception format:
<?php
class PropertyAssesorTraitTestClass
{
use PropertyAccessorTrait;
private $__propertyPrefix = '_';
/**
* #get public
* #set public
*/
private $_integer = 1;
/**
* #get public
*/
private $_getonly = 100;
/**
* #set public
*/
private $_setonly;
private $_customDoubler;
private function getCustomDoubler()
{
return $this->_customDoubler * 2;
}
private function setCustomDoubler($value)
{
$this->_customDoubler = $value * 2;
}
public $publicField = 1234;
/**
* #return int
* #get public
*/
private function getPureAccessor()
{
return $this->publicField;
}
/**
* #param $value
* #set public
*/
private function setPureAccessor($value)
{
$this->publicField = $value;
}
private $_purePrivate = 256;
}
$I = new UnitTester($scenario);
$I->wantTo('Ensure properties are accessed correctly');
$instance = new PropertyAssesorTraitTestClass();
$I->assertSame(1, $instance->integer);
$instance->integer = 2;
$I->assertSame(2, $instance->integer);
$instance->integer = $instance->integer + 1;
$I->assertSame(3, $instance->integer);
$instance->integer++;
$I->assertSame(4, $instance->integer);
$I->assertSame(100, $instance->getonly);
$I->expectException('Exception', function () use ($instance) { $instance->getonly = 50; });
$instance->setonly = 50;
$I->expectException('Exception', function () use ($instance) { $a = $instance->setonly; });
$instance->customDoubler = 100;
$I->assertSame(400, $instance->customDoubler);
$I->assertSame(1234, $instance->publicField);
$instance->pureAccessor = 1000;
$I->assertSame(1000, $instance->publicField);
$instance->publicField = 1234;
$I->assertSame(1234, $instance->publicField);
$I->assertSame(1234, $instance->pureAccessor);
$I->expectException('Exception', function () use ($instance) { return $instance->purePrivate; });
I like to use this pattern:
class foo
{
//just add p as prefix to be different than method name.
protected $pData;
public funtion __construct() {}
public funtion __destruct() {}
public funtion __clone() {}
public function Data($value == "")
{
if ($value != "") {
$this->pData = $value;
}
return $this->pData;
}
}
$myVar = new foo();
//for SET
$myVar->Data("A Value");
//for GET
$item = $myVar->Data();
class MyClass
{
private $name = null;
public function __construct($name = null)
{
$this->name = $name;
}
public function __set($name, $value)
{
if (property_exists($this, $name)) {
$this->name = $value;
}
return $this;
}
public function __get($name)
{
if (property_exists($this, $name)) {
return $this->$name;
}
return null;
}
}
this is PHP ; you don't need get set
class MyClass {
public $ID;
}
$object = new MyClass();
$object->ID = 'foo';
echo $object->ID;
will work

Refactoring To Remove Static Methods Code Smell

I have the current basic structure for each domain object that I need to create:
class Model_Company extends LP_Model
{
protected static $_gatewayName = 'Model_Table_Company';
protected static $_gateway;
protected static $_class;
public static function init()
{
if(self::$_gateway == null)
{
self::$_gateway = new self::$_gatewayName();
self::$_class = get_class();
}
}
public static function get()
{
self::init();
$param = func_get_arg(0);
if($param instanceof Zend_Db_Table_Row_Abstract)
{
$row = $param;
}
elseif(is_numeric($param))
{
$row = self::$_gateway->find($param)->current();
}
return new self::$_class($row);
}
public static function getCollection()
{
self::init();
$param = func_get_arg(0);
if($param instanceof Zend_Db_Table_Rowset_Abstract)
{
$rowset = $param;
}
elseif(!$param)
{
$rowset = self::$_gateway->fetchAll();
}
$array = array ();
foreach ($rowset as $row)
{
$array[] = new self::$_class($row);
}
return $array;
}
}
I initially tried to refactor the static methods into the parent LP_Model class only to learn finally what "late static binding" means in the php world.
I'm just wondering if anyone has suggestions on how to refactor this code so that I don't have to redeclare the same three functions in every domain object that I create?
How about this:
<?php
abstract class Model_Abstract
{
protected $_gatewayName = null;
protected $_gateway = null;
protected function _init()
{
$this->_gateway = new $this->_gatewayName();
}
protected function __construct($row = null)
{
$this->_init();
if ($row) {
$this->_data = $row;
}
}
public static function getAbstract($class, $param)
{
$model = new $class();
if($param instanceof Zend_Db_Table_Row_Abstract)
{
$row = $param;
}
elseif(is_numeric($param))
{
$row = $model->_gateway->find($param)->current();
}
return new $class($row);
}
public static function getAbstractCollection($class, $param = null)
{
$model = new $class();
if($param instanceof Zend_Db_Table_Rowset_Abstract)
{
$rowset = $param;
}
elseif($param === null)
{
$rowset = $model->_gateway->fetchAll();
}
$array = array ();
foreach ($rowset as $row)
{
$array[] = new $class($row);
}
return $array;
}
abstract public static function get($param);
abstract public static function getCollection($param = null);
}
class Model_Company extends Model_Abstract
{
protected $_gatewayName = 'Model_Table_Company';
public static function get($param) {
return self::getAbstract(__CLASS__, $param);
}
public static function getCollection($param = null) {
return self::getAbstractCollection(__CLASS__, $param);
}
}
class Model_Table_Company extends Zend_Db_Table_Abstract
{
protected $_name = 'company';
}
$model = Model_Company::get(1);
print "Got an object of type ".get_class($model)."\n";
$models = Model_Company::getCollection();
print "Got ".count($models)." objects of type ".get_class($models[0])."\n";
?>
Unfortunately, to make the functions easy to call, you have to duplicate get() and getCollection() in each subclass. The other option is to call the function in the parent class:
$model = Model_Abstract::getAbstract('Model_Company', 1);
print "Got an object of type ".get_class($model)."\n";
$models = Model_Abstract::getAbstractCollection('Model_Company');
print "Got ".count($models)." objects of type ".get_class($models[0])."\n";
You can rename the base class and its function names if you want to go that route. But the point is that you must name the child class in one place or the other: either make a boilerplate function in the child class as in my first example, or else name the class in a string as in my second example.

Categories