php class with predefined rules for object attributes - php

I'm often coming along with the following case and I'm not sure if it's a good practice in OOP. So I thought maybe you can help me with this.
I'll simplify this with an example of adding fruits to a fruit basket, like this:
$obj = new FruitBasket();
$obj->add(new Fruit('Banana'));
$obj->add(new Fruit('Apple'));
$obj->add(new Fruit('Kiwi'));
$obj->get();
The class "FruitBasket" simply adds and outputs the data:
class FruitBasket {
private $fruits = [];
public function add(Fruit $fruit)
{
$this->fruits[] = $fruit;
}
public function get()
{
foreach ($this->fruits as $fruit)
{
echo $fruit->get('type') .': '. $fruit->get('color') .' with '. ($fruit->get('seed') === true ? 'seeds.' : 'no seeds.') . '<br>';
}
}
}
Ok.
Then the "Fruit" class — this is where I'm thinking of a better practice:
class Fruit {
private $type;
private $color;
private $seed;
// Is this cool(?):
private $rules = [
'Apple' => ['color' => 'red', 'seed' => true],
'Banana' => ['color' => 'yellow', 'seed' => false],
'Kiwi' => ['color' => 'green', 'seed' => true]
// …
];
public function __construct($type)
{
if (isset($this->rules[$type]))
{
$this->type = $type;
$this->color = $this->rules[$type]['color'];
$this->seed = $this->rules[$type]['seed'];
}
}
public function get($attr = null)
{
if (isset($this->$attr) && !is_null($this->$attr))
return $this->$attr;
}
}
This class (Fruit) contains an attribute $rules, which is an array of all possible (or "allowed") fruits. In this example there are only 3 of them, but in real world they could also be up to 20. Well. This array also contains attributes for each fruit. Attributes that won't ever change, because a banana always will be yellow (let's say so). So these attributes are constants.
When the Fruit-object is created, the constructor sets all these attributes.
So my question is: Is it good to have this array of possible fruits and their attributes?
…
I wrote an alternative version of the Fruit class AND classes for each type of Fruit. See it here:
abstract class Apple {
const color = 'red';
const seed = true;
}
abstract class Banana {
const color = 'yellow';
const seed = false;
}
abstract class Kiwi {
const color = 'green';
const seed = true;
}
// …
class Fruit {
private $type;
private $color;
private $seed;
public function __construct($type)
{
$class = $type; // just to make it clear
if (class_exists($class))
{
$this->type = $type;
$this->color = $class::color;
$this->seed = $class::seed;
}
}
public function get($attr = null)
{
if (isset($this->$attr) && !is_null($this->$attr))
return $this->$attr;
}
}
These classes are set to "abstract" because I don‘t want to create objects from them. (I know(!) that I could extend those Fruit-Classes with the Fruit-Class, but this doesn't work in every case and isn't the point here.)
What I also don't like about this (2nd) version is, that I need to create own files for each fruit (when using autoload …).
So if I compare writing up to 20 classes for each fruit plus putting them into 20 separate files WITH a simple array … well … the array is so simple.
But what i want to know is … is there any "best practice" for this problem?
Any pattern? What would you suggest?

Given that both methods work, it comes down to readability and practicality. I personally find the first method more user friendly. You can easily see the relationship between the fruit and it's properties in the array given.
The only problem I might see is that the class might get too big with the addition of more properties, and could dwarf the other variables and functions in the Fruits class. You could alternatively create another class for storing the rules:
class Rules {
protected $rules = [
'Apple' => ['color' => 'red', 'seed' => true],
'Banana' => ['color' => 'yellow', 'seed' => false],
'Kiwi' => ['color' => 'green', 'seed' => true]
// …
];
}
and then have your fruit class extend it:
class Fruit extends Rules {//...
This would give you 2 benefits:
Cleaner code.
If for some reason in the future you need these rules in another class, you can simple extend it as well.

Related

Array to dynamic object

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);

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

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()
);

How to inherit parent class array properties by merging array?

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.

Categories