How to inherit parent class array properties by merging array? - php

I often use properties in my classes that store an array of options. I'd like to be able to somehow merge those options from defaults declared in a parent class.
I demonstrated with some code.
class A
{
public $options = array('display'=>false,'name'=>'John');
}
class B extends A
{
public $options = array('name'=>'Mathew');
}
Now when I create B, then I'd like $options to contain a merged array from A::options
What happens now is this.
$b = new B();
print_r($b);
array('name'=>'Mathew');
I would like something like this using array_merge_recursive().
array('display'=>false,'name'=>'Mathew');
Maybe it's something I could do in the constructor?
Is it possible to make this a behavior of class A? So that I don't always have to implement the same code in all subclasses.
Could I use reflection to auto find array properties in both classes and merge them?

In addition to the previous answers, another approach that may be suited for certain cases would be to use PHP Reflection or built-in class functions. Here is a basic example using the latter:
class Organism
{
public $settings;
public $defaults = [
'living' => true,
'neocortex' => false,
];
public function __construct($options = [])
{
$class = get_called_class();
while ($class = get_parent_class($class)) {
$this->defaults += get_class_vars($class)['defaults'];
}
$this->settings = $options + $this->defaults;
}
}
class Animal extends Organism
{
public $defaults = [
'motile' => true,
];
}
class Mammal extends Animal
{
public $defaults = [
'neocortex' => true,
];
}
$fish = new Animal();
print_r($fish->settings); // motile: true, living: true, neocortex: false
$human = new Mammal(['speech' => true]);
print_r($human->settings); // motile: true, living: true, neocortex: true, speech: true

I realize I changed your interface from a public variable to a method, but maybe it works for you. Beware, adding a naive setOps($ops) method may work unexpected if you allow the parent ops to continue to be merged in.
class A
{
private $ops = array('display'=>false, 'name'=>'John');
public function getops() { return $this->ops; }
}
class B extends A
{
private $ops = array('name'=>'Mathew');
public function getops() { return array_merge(parent::getOps(), $this->ops); }
}
class c extends B
{
private $ops = array('c'=>'c');
public function getops() { return array_merge(parent::getOps(), $this->ops); }
}
$c = new C();
print_r($c->getops());
out:
Array
(
[display] =>
[name] => Mathew
[c] => c
)

You can use a simple pattern like so:
abstract class Parent {
protected $_settings = array();
protected $_defaultSettings = array(
'foo' => 'bar'
);
public __construct($settings = array()) {
$this->_settings = $settings + $this->_defaultSettings;
}
}
In this way it's easily possible to modify the defaults applied in child classes:
class Child extends Parent {
protected $_defaultSettings = array(
'something' => 'different';
);
}
Or to apply something more complex:
class OtherChild extends Parent {
function __construct($settings = array()) {
$this->_defaultSettings = Configure::read('OtherChild');
return parent::__construct($settings);
}
}
Merging variables
Cake does come with a function for merging variables. It's used for controller properties such as components, helpers etc. But be careful applying this function to none trivial arrays - you may find that it doesn't quite do what you want/expect.

Related

Instantiating one instance each of parent's class children?

For testing my stuff i would like to automaticaly instantiate one instance each per child of a parent class i name, without calling the class name manually (its too many).
For example:
Class Animal {
public weight;
}
Class Dog extends Animal {
public weight = 20;
}
Class SmallDog extends Dog {
public weight = 18;
}
Class Cat extends Animal {
public weight = 10;
}
function xy(){
$classes = array();
foreach parent->child as child(){
$classes[] = new child()
}
//$classes = [Dog, SmallDog, Cat]
}
Is something like (tm) doable and if so, how ?
As further information, i have an autoload_register that holds each class, but it also holds various classes i dont want to instantiate.
Also, relative to the above example, i have Animal, Dog and Cat all assembled in a single file, lets call it animal.php.
In the code above, i would except to get an instance of anything below Animal, including SmallDog. If its not possible that way, i would be okay with getting Dog and Cat and calling the function with parent being Dog (to get SmallDog instantiated).
thanks for your advice,
This is not really useful in real case scenarios but using #axiac comment you can implement ChildrenResolver class:
class ChildrenResolver
{
protected $defaultClasses = [
0 => 'stdClass',
1 => 'Exception',
2 => 'ErrorException',
// ...
];
protected $declaredClasses;
public function __construct(array $declaredClasses = null)
{
if (is_null($declaredClasses)) {
$declaredClasses = array_diff(
get_declared_classes(),
$this->defaultClasses
);
}
$this->declaredClasses = $declaredClasses;
}
public function getChildClasses($class)
{
return array_filter(
$this->declaredClasses,
function ($declaredClassName) use ($class) {
$declaredClass = new ReflectionClass($declaredClassName);
while(($parent = $declaredClass->getParentClass())) {
if ($parent->name === $class) {
return true;
}
$declaredClass = $parent;
}
return false;
}
);
}
public function getDirectChildClasses($class)
{
return array_filter(
$this->declaredClasses,
function ($declaredClassName) use ($class) {
$declaredClass = new ReflectionClass($declaredClassName);
$parent = $declaredClass->getParentClass();
return $parent ? $parent->name === $class : false;
}
);
}
}
The idea is pretty simple. We loop over declared classes (obtained via get_declared_classes), reflect every class using ReflectionClass and compare parent, which we get from ReflectionClass::getParentClass, with given parent. For the sake of convinience I used array_filter function.
Having this class you can resolve children like this:
$childrenResolver = new ChildrenResolver;
var_dump($childrenResolver->getChildClasses('Animal'));
var_dump($childrenResolver->getDirectChildClasses('Animal'));
var_dump($childrenResolver->getChildClasses('Dog'));
Here is working demo.

Dynamically instantiating PHP classes contained in an array

I'm trying to instantiate some classes in an array. This is the scenario: I have many classes, e.g.:
class Class0 extends NumberedClasses{...}
class Class1 extends NumberedClasses{...}
class Class2 extends NumberedClasses{...}
They will increase over the time, so instead of instantiating them this way:
$instances0 = new Class0();
$instances1 = new Class1();
$instances2 = new Class2();
I want to have a getter method, like this one:
function getStrategies(){
return array(Class0, Class1, Class2);
}
So I can just add classses in that array in the future, and call it this way:
$strategies = $this->getStrategies();
for ($strategies as $strategy) {
$instance = new $strategy();
}
I'm used to do something similar with js, where I have the window object, so I can instantiate classes even just with a string (new window["Class1"]). But I'm new with PHP and can't find the way to do that.
Thanks in advance,
It may be suggested that using an Intermediary Class to manage these instances will be much ideal. Consider this:
<?php
class NumberedClasses{}
class Class0 extends NumberedClasses{}
class Class1 extends NumberedClasses{}
class Class2 extends NumberedClasses{}
class InstanceHelper {
/**
* #var array
*/
protected $instances = array();
public function addInstance($instanceKey, $instance) {
if(!array_key_exists($instanceKey, $this->instances)){
$this->instances[$instanceKey] = $instance ;
}
}
public function addInstances(array $arrayOfInstances) {
foreach($arrayOfInstances as $instanceKey=>$instance){
if(!array_key_exists($instanceKey, $this->instances)){
$this->instances[$instanceKey] = $instance ;
}
}
}
public function removeInstance($instanceKey) {
if(array_key_exists($instanceKey, $this->instances)){
unset($this->instances[$instanceKey]);
}
return $this->instances;
}
public function getAllInstances() {
return $this->instances;
}
}
Now; with the Class InstanceHelper, You can easily manage your instances... even add Instances, remove Instance and so on.... Again consider this tests:
<?php
$insHelper = new InstanceHelper();
$instances = array(
'c0' => new Class0(),
'c1' => new Class1(),
'c2' => new Class2(),
);
$insHelper->addInstances($instances);
var_dump($insHelper->getAllInstances());
var_dump($insHelper->removeInstance('c1'));
Here are the results of both var_dumps:
::VAR_DUMP 1::
array (size=3)
'c0' =>
object(Class0)[2]
'c1' =>
object(Class1)[3]
'c2' =>
object(Class2)[4]
::VAR_DUMP 2::
array (size=2)
'c0' =>
object(Class0)[2]
'c2' =>
object(Class2)[4]

Formatting of Numbers with PDO

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

ZF2 Autoloader: Use factory for base class on extended classes as well

If I wanted to use the same factory for any class that extended my base class, would I want to turn the base class factory into a named function, or is there a better way to do this?
$serviceManager => array(
'factories' => array(
'someBaseClass' => function($thisServiceManager) {
$db = $thisServiceManager->get('db');
$thisBaseClass = new \myNamespace\thisBaseClass($db);
return $thisBaseClass;
},
),
);
EDIT
Further to the answer I accepted, here's the code I've tested that works.
Class File
use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class baseClassFactory implements \Zend\ServiceManager\AbstractFactoryInterface
{
public function canCreateServiceWithName (ServiceLocatorInterface $locator, $name, $requestedName = '')
{
return ('baseClass' === $name || is_subclass_of($name, 'baseClass'));
}
public function createServiceWithName (ServiceLocatorInterface $locator, $name, $requestedName = '')
{
$db = $locator->get('db');
$query = new $name($db);
return $query;
}
}
Configuration
$serviceManager => array(
'abstract_factories' => array(
'baseClassFactory',
),
);
If I'm following your question, you have a bunch of classes that extend someBaseClass, and you want them all to be manufactured in the same way?
If so, then that sounds like a good opportunity to use the abstract_factories component in the ServiceManager. The pseudo-code would be like:
// warning: untested pseudo-code
class MyAbstractFactory implements \Zend\ServiceManager\AbstractFactoryInterface {
public function canCreateServiceWithName($_, $_, $name) {
// if I'm asked for someBaseClass or a child of someBaseClass, return true: false otherwise
return ('someBaseClass' === $name || is_subclass_of($name, 'someBaseClass'));
}
public function createServiceWithName($locator, $_, $name) {
// your factory logic here
// $object = ...
return $object;
}
}
And your config would look like:
$serviceManager = [ 'abstract_factories' => [ 'MyAbstractFactory' ] ];
Using the concrete factories, you'd have to repeat the same function for each extended class (AFAIK), which would be a pain to maintain (IMO). The abstract factory pattern above basically says: give me a class, I'll reflect it to see if it's the base class I care about and, if so, I'll run my factory to get it.
More info on abstract_factories if you've never used it before.

Referencing a class programatically in PHP

I receive an object during some process and this object needs to figure out its coloring scheme.
For example, I have a coloring scheme that is stored like this:
class FirstScheme {
public static $COLORS = array('1' => 'green', '2' => 'red', ...);
}
class SecondScheme {
public static $COLORS = array('1' => 'red', '2' => 'green', ...);
}
I know all the coloring schemes names in advance; they can only change when the code changes.
But the coloring scheme to be used for each object needs to be determined at run-time by matching the attribute of this object.
And here I don't know what to do. In python I would define a dict holding the mappings of color schemes to names like this:
d = {'attr_value1': FirstScheme, 'attr_value2': SecondScheme, 'attr_value3': FirstScheme, ...}
And then just access the "COLORS" variable, because every class should have it. But in PHP there is not way to reference a class in a such way, so what is the right way to do it?
Note that more than one attribute can map to the same coloring scheme.
If every class should have the colors, define the interface that allows to get them:
interface ColorsProvider {
function getColors();
}
class FirstScheme implements ColorsProvider {
public static COLORS = array('1' => 'green', '2' => 'red', ...);
public function getColors() {
return self::COLORS;
}
}
class SecondScheme implements ColorsProvider {
public static COLORS = array('1' => 'red', '2' => 'green', ...);
public function getColors() {
return self::COLORS;
}
}
Then, where you have stack of yout params:
$a = array(
'attr_value1' => new FirstScheme(),
'attr_value2' => new SecondScheme(),
);
You can call:
$param = 'attr_value1';
if(!isset($a[$param]))
throw new Exception("undefined param");
if(!($a[$param] instanceof ColorsProvider))
throw new Exception("Param should point to ColorsProvider");
$a[$param]->getColors();
Please note that it is full-objective. In PHP there are simplier ways to get this effects, but my solution is just elegant.
The another point is the interface completely separates the source of colors. There would be from file, database, xml, hardcoded etc.
Default implementation might be:
abstract class DefaultColorsProviderImpl implements ColorsProvider {
protected static COLORS = array();
public function getColors() {
return self::COLORS;
}
}
class FirstScheme extends DefaultColorsProviderImpl {
protected static COLORS = array( ... );
}
But still allows to make generic implementation that returns colors from e.x. from file.
Of course you can:
$schemes = [
'attr_value1' => FirstScheme::$COLORS,
'attr_value2' => SecondScheme::$COLORS,
...
];
Or even at runtime:
$schemes = [
'attr_value1' => 'FirstScheme',
'attr_value2' => 'SecondScheme',
...
];
And then:
$reflector = new ReflectionClass($schemes['attr_value1']);
$schema = $reflector->getStaticPropertyValue('COLORS');
But this seems not any maintainable at all, and you would like to store such informations in a proper data layer, without hardcoding them as static fields of a class [which is not their purpose].
An alternative to hard-coding the colors in their own classes would be the following approach:
class ColorScheme {
protected $colors;
public function __construct(array $colors) {
$this->colors = $colors;
}
public function getColors() {
return $this->colors;
}
}
$scheme1 = new ColorScheme(array('red', 'blue', 'green'));
$scheme2 = new ColorScheme(array('yellow', 'pink', 'cyan'));
The equivalent for a python dictionary in PHP is an associative array. So you could do this:
$d = array (
'values_1' => $scheme1->getColors(),
'values_2' => $scheme2->getColors()
);

Categories