What is the right way to create nested classes in PHP? - php

I need to create a nested structure class of a multidimensional array, this is my array:
Array
(
[days] => Array
(
[0] => Array
(
[rows] => Array
(
[0] => Array
(
[activity_id] => 1
[name] => Activity 2
[city] => London
[info] => fsdsdshgsfd
)
[1] => Array
(
[activity_id] => 3
[name] => Activity 1
[city] => London
[info] => fsdhgsfd
)
)
)
[1] => Array
(
[rows] => Array
(
[0] => Array
(
[activity_id] => 3
[name] => Activity 1
[city] => London
[info] => fsdhgsfd
)
...
)
)
)
...
)
)
I have been trying to rewrite my code to make it class-driven, but I am struggling with that, what is the right way to build a class structure Itinerary->Days->Rows to replace an array? I tried something like this, I am not sure if it makes sense, I don't really understand the way how it has to be done:
class Itinerary
{
private $days = array();
public static function addDay($day) {
$this->$days[] = new ItineraryDay($day);
}
}
class ItineraryDay implements Countable
{
private $rows = array();
public static function addRow($row) {
$this->$rows[] = new ItineraryRow($row);
}
public function count()
{
return count($this->rows);
}
}
class ItineraryRow implements Countable
{
private $name;
private $city;
...
function __get($key)
{
...
}
function __set($key, $value)
{
...
}
public function count()
{
return count($this->rows);
}
}
$itinerary1 = new Itinerary();
$day1 = new ItineraryDay();
$itinerary1->addDay($day1);
$row1 = new ItineraryRow();
$day1->addRow($row1);
Can someone guide me?

It really depends what you ultimately want to do with said structure, but for a general idea I typically do something like this:
class Itinerary implements Countable
{
private $days;
public function __construct( array $days = array() )
{
$this->setDays( $days );
}
public function addDay( ItineraryDay $day )
{
$this->days[] = $day;
}
public function setDays( array $days )
{
$this->days = array();
foreach( $days as $day )
{
$this->addDay( $day );
}
}
public function count()
{
return count( $this->days );
}
}
class ItineraryDay implements Countable
{
private $rows;
public function __construct( array $rows = array() )
{
$this->setRows( $rows );
}
public function addRow( ItineraryRow $row )
{
$this->rows[] = $row;
}
public function setRows( array $rows )
{
$this->rows = array();
foreach( $rows as $row )
{
$this->addRow( $row );
}
}
public function count()
{
return count( $this->rows );
}
}
class ItineraryRow
{
private $id;
private $name;
private $city;
private $info;
public function __construct( $id, $name, $city, $info )
{
$this->id = $id;
$this->name = $name;
$this->city = $city;
$this->info = $info;
}
/* ... */
}
Then using it with the structure of your current array of data:
$days = array();
foreach( $originalData[ 'days' ] as $days )
{
$rows = array();
foreach( $days[ 'rows' ] as $row )
{
$rows[] = new ItineraryRow( $row[ 'activity_id' ], $row[ 'name' ], $row[ 'city' ], $row[ 'info' ] );
}
$days[] = new ItineraryDay( $rows );
}
$itinerary = new Itinerary( $days );
Or:
$itinerary = new Itinerary;
foreach( $originalData[ 'days' ] as $days )
{
$day = new ItineraryDay;
foreach( $days[ 'rows' ] as $row )
{
$row = new ItineraryRow( $row[ 'activity_id' ], $row[ 'name' ], $row[ 'city' ], $row[ 'info' ] )
$day->addRow( $row );
}
$itinerary->addDay( $day );
}
So, you can either pass "child" objects to the constructor (the method that constructs a new object), or add them with methods after construction. If you want the objects to be immutable, meaning you don't want to allow the objects to accept any more rows / days after construction, just make the addDay, setDays, addRow and setRows methods protected or private thereby only allowing passing "child" object through the constructors.
Be aware that, as PeeHaa already mentioned, you don't want static methods, because they operate class wide, not on individual instances of classes (objects). As a matter of fact, you cannot even use static methods the way you intended, because $this is only available in object context, not in class wide context.
But, to be honest, the question is a little bit to vague to be answered properly. We'd have to have a little more details about how you are going to construct the objects, and how you are going to use them later on.

Related

Recursive function return issue

I'm writing a recursive function like below:
private function getManager($employee)
{
$manager = $employee->manager;
if ($manager) {
array_push($this->managers, $manager->id);
$this->getManager($manager);
}
return;
}
This function receive an employee and find his manage. If find a manage, then push manager id into an array ($this->managers on line 5). Then call this function recursively and pass manager as an employee. If no manager found on line 3, then this function just return (line 8).
So my question is, is their any problem if i'm not return the recursive call at line 6 ($this->getManager($manager);)
Not sure if this is what you think, but it works.
function getManagers($employee)
{
$managers = [];
if (isset($employee->manager)) {
array_push($managers, $employee->manager->id);
array_push($managers, ...getManagers($employee->manager));
}
return $managers;
}
No, there is absolutely no benefit in writing the empty return. The method will halt regardless of the existence of the return.
Please observe the two methods below which show identical, error-less outcomes regardless of the return.
Code: (Demo)
class Management
{
private $managers = [];
function __construct($managerTree)
{
$this->getManager($managerTree);
var_export($this->managers);
echo "\n---\n";
$this->managers = [];
var_export($this->managers);
echo "\n---\n";
$this->getManager2($managerTree);
var_export($this->managers);
}
private function getManager(?object $employee): void
{
$manager = $employee->manager;
if ($manager) {
array_push($this->managers, $manager->id);
$this->getManager($manager);
}
return;
}
private function getManager2(?object $employee): void
{
$manager = $employee->manager;
if ($manager) {
array_push($this->managers, $manager->id);
$this->getManager($manager);
}
}
}
new Management(
(object) [
'id' => 3,
'manager' => (object) [
'id' => 2,
'manager' => (object) [
'id' => 1,
'manager' => null
]
]
]
);
Output:
array (
0 => 2,
1 => 1,
)
---
array (
)
---
array (
0 => 2,
1 => 1,
)

Retrieving Data from an object of an array of arrays

I know this question is more data structures but since I am doing it in Symfony there might be a simpler way. I have a recursive function treeBuilder() I want to call on some data to create a hierarchy. Say a database of people and I want to create a tree structure if they live with their parents. I know I am passing an array of object to the function but it needs to be an array. I am pretty sure I need to rewrite this function so that it handles the the array of object but am stumped. I am not sure how to access the elements of the array to check the parentid. I know the code below is not correct but that is where I am at now.
Controller:
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('CompanyMyBundle:Org')->findAll();
var_dump($entities);
$tree=$this->treeBuilder($entities);
return array(
'entities' => $tree,
);
}
private function treeBuilder($ar, $pid=null)
{
$op=array();
foreach( $ar as $item ) {
// I know I have an array of objects
if( $item['ParentId'] == $pid ) {
$op[$item['Id']] = array(
'Street' => $item['Street'],
'ParentId' => $item['ParentId']
);
$children = self::treeBuilder( $ar, $item['Id'] );
if( $children ) {
$op[$item['Id']]['children'] = $children;
}
}
}
return $op;
}
var_dump($entities) from indexAction():
/export/www/working/symfony/src/Company/MyBundle/Controller/DepController.php:34:
array (size=60)
0 =>
object(Company\MyBundle\Entity\Org)[1556]
private 'Name' => string 'Me' (length=46)
private 'Street' => string '123 Sesame' (length=255)
private 'City' => string 'Myhometown' (length=255)
private 'ParentId' => int 0
private 'Id' => int 1
1 =>
object(Company\MyBundle\Entity\Org)[1557]
private 'Name' => string 'Me2' (length=46)
private 'Street' => string '123 Sesame' (length=255)
private 'City' => string 'Myhometown' (length=255)
private 'ParentId' => int 1
private 'Id' => int 2
If you need to get entities as arrays instead of objects, you would need to use Doctrine's hydrator:
$em = $this->getDoctrine()->getManager();
$orgRepo = $em->getRepository('CompanyMyBundle:Org');
$entities = $orgRepo->createQueryBuilder('org')
->getQuery()
->getResult(\Doctrine\ORM\AbstractQuery::HYDRATE_ARRAY);
Note:
I would suggest to leave entities as objects and use getters:
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('CompanyMyBundle:Org')->findAll();
$tree = $this->treeBuilder($entities);
return array(
'entities' => $tree,
);
}
private function treeBuilder($entities, $pid = null)
{
$op = array();
/** Org $entity */ //Type hinting, if you use autocompletion
foreach ($entities as $entity) {
if ($entity->getParentId() == $pid) {
$op[$entity->getId()] = [
'Street' => $entity->getStreet(),
'ParentId' => $entity->getParentId()
];
$children = self::treeBuilder($entities, $entity->getId());
if (!empty($children)) {
$op[$entity->geId()]['children'] = $children;
}
}
}
return $op;
}

List all extended classes with their settings

I have a system where I am creating multiple classes that all extend from an abstract class.
Each class also declares 'settings' for that particular class type.
Example:
class First extends Base {
protected $name = 'First';
protected $lug = 'first';
protected $fields = [
'name',
'address',
'phone',
];
function __construct()
{
parent::__construct();
}
public function abstractMethod()
{
// do stuff for this particular class
}
}
and
class Second extends Base {
protected $name = 'Second';
protected $lug = 'second-one';
protected $fields = [
'first-name',
'last-name',
'email',
];
function __construct()
{
parent::__construct();
}
public function abstractMethod()
{
// do stuff for this particular class
}
}
Now what I want to be able to do is grab all extended classes and their 'settings' and return something like this:
$classes = [
'first' => [
'name' => 'First',
'slug' => 'first',
'fields' => ['name', 'address', 'phone']
],
'second' => [
'name' => 'Second',
'slug' => 'second-one',
'fields' => ['first-name', 'last-name', 'email']
]
];
So how would I go about doing this? Is there a better way?
I am using Laravel if that helps.
Edit: To Explain why not a duplicate
I'm not just after a way to get classes and their information I am after a way to architect this situation. I am essentially creating an extensible plugin system and need a way to Tell-Don't-Ask which plugins have been added.
I didn't try it, but it should work. Or it'll directs you.
$result = array();
foreach (get_declared_classes() as $class) {
if (is_subclass_of($class, 'Base'))
$result[] = get_class_vars($class);
}
But your properties needs to be public also.
What about using ReflectionClass? Getting properties is quite easy, example from manual below. Listing extended classes should be easy too.
<?php
class Bar {
protected $inheritedProperty = 'inheritedDefault';
}
class Foo extends Bar {
public $property = 'propertyDefault';
private $privateProperty = 'privatePropertyDefault';
public static $staticProperty = 'staticProperty';
public $defaultlessProperty;
}
$reflectionClass = new ReflectionClass('Foo');
var_dump($reflectionClass->getDefaultProperties());
Output:
array(5) {
["staticProperty"]=>
string(14) "staticProperty"
["property"]=>
string(15) "propertyDefault"
["privateProperty"]=>
string(22) "privatePropertyDefault"
["defaultlessProperty"]=>
NULL
["inheritedProperty"]=>
string(16) "inheritedDefault"
}
Using ReflectionObject you can do it like this:
$result = array();
foreach (get_declared_classes() as $class) {
if (is_subclass_of($class, 'Base')) {
$obj = new $class;
$refObj = new ReflectionObject($obj);
$props = $refObj->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED);
$classProps = array();
foreach ($props as $prop) {
$property = $refObj->getProperty($prop->getName());
$property->setAccessible(true);
$classProps[$prop->getName()] = $property->getValue($obj);
}
$result[$class] = $classProps;
}
}
print_r($result);
Output:
Array (
[First] => Array (
[name] => First
[lug] => first
[fields] => Array (
[0] => name
[1] => address
[2] => phone
)
)
[Second] => Array (
[name] => Second
[lug] => second-one
[fields] => Array (
[0] => first-name
[1] => last-name
[2] => email
)
)
)

How to give object a name

When I do the following:
$arUserStuff = array ('name' => 'username', 'email' => 'test#test.com');
$object = (object) $arUserStuff;
print_r($object);
The print function returns me the following:
stdClass Object ( [name] => username [email] => test#test.com )
How can I change std class object in let's say's User Object?
Create that class, then create an object of it:
class User {
public $name, $email; // public for this example, or set these by constructor
public function __construct( array $fields) {
foreach( $fields as $field => $value)
$this->$field = $value;
}
}
$object = new User;
$object->name = 'username';
$object->email = 'test#test.com';
Or, you can do:
$arUserStuff = array ('name' => 'username', 'email' => 'test#test.com');
$object = new User( $arUserStuff);
Now, from print_r( $object);, you'll get something like this:
User Object ( [name] => username [email] => test#test.com )
actually to do what you want, you should make it like:
$arUserStuff = new ArrayObject(
array (
'name' => 'username', 'email' => 'test#test.com'
)
);
to change the class name you need to create a new class.
It's a rather complex process but you can learn about it here:
http://php.net/manual/en/language.oop5.php
Here's a generic function that converts an array into any type of object, assuming the fields are public
class User { public $name, $email; }
class Dog { public $name, $breed; }
function objFromArray($className, $arr) {
$obj = new $className;
foreach(array_keys(get_class_vars($className)) as $key) {
if (array_key_exists($key, $arr)) {
$obj->$key = $arr[$key];
}
}
return $obj;
}
print_r(objFromArray('User',
array ('name' => 'username', 'email' => 'test#test.com')));
echo "<br/>";
print_r(objFromArray('Dog',
array ('name' => 'Bailey', 'breed' => 'Poodle')));
Output
User Object ( [name] => username [email] => test#test.com )
Dog Object ( [name] => Bailey [breed] => Poodle )
I wanted to make a trait out of it but don't have PHP 5.4 installed to test it. This wouldn't require the fields to be public
trait ConvertibleFromArray {
public static function fromArray($arr) {
var $cls = get_called_class();
var $obj = new $cls;
foreach($arr as $key=>$value) {
if (property_exists($obj, $arr)) {
$obj->$key = $value;
}
}
return $obj;
}
}
class User {
use ConvertibleFromArray;
public $name, $email;
}
class Dog {
use ConvertibleFromArray;
public $name, $breed;
}
print_r(User::fromArray(array ('name' => 'username', 'email' => 'test#test.com')));
print_r(Dog::fromArray(array('name' => 'Bailey', 'breed' => 'Poodle')));
?>

Array structure for Shopping Basket

I am trying to implement multidimensional array that hold data of Pizza id's with options and extras id's.
Let look at the following scenario...
Customer wants
Two 'Chicken Pizza' (ProductID:12) - '10 inches' (OptionsID:4) with extras of Ham and Tuna (ExtrasID: 5,10)
One 'Chicken Pizza' (ProductID:12) - '10 inches' (OptionsID:4) with extras of Sweet Corn (ExtrasID: 2)
One 'Chicken Pizza' (ProductID:12) - '14 inches' (OptionsID:2) with no extras
Eleven 'Vegetarian Pizza' (ProductID:35) - '7 inches' (OptionsID:52) with no extras
See the following code below that match the scenario... Im I doing it right? or what can be done to improve it and readable?
//Two 'Chicken Pizza' (ProductID:12) - '10 inches' (OptionsID:4)
//With extras of Ham and Tuna (ExtrasID: 5,10)
$option4[] = array(
'quantity' => 2,
'extras_id' => array(5, 10)
);
//One 'Chicken Pizza' (ProductID:12) - '10 inches' (OptionsID:4)
//With extras of Sweet Corn (ExtrasID: 2)
$option4[] = array(
'quantity' => 1,
'extras_id' => array(2)
);
//One 'Chicken Pizza' (ProductID:12) - '14 inches' (OptionsID:2)
//With no extras
$option2[] = array(
'quantity' => 1,
'extras_id' => array()
);
//Eleven 'Vegetarian Pizza' (ProductID:35) - '7 inches' (OptionsID:52)
//With no extras
$option52[] = array(
'quantity' => 11,
'extras_id' => array()
);
//Hold data of Customer Orders
$shoppingBasket = array(
"ShopID_24" => array(
'ProductID' => array(
'12' => array(
'OptionID' => array(
'4' => $option4,
'2' => $option2
)
),
'35' => array(
'OptionID' => array(
'52' => $option52
)
),
)
)
);
echo "<pre>";
print_r($shoppingBasket);
echo "</pre>";
print_r output:
Array
(
[ShopID_24] => Array
(
[ProductID] => Array
(
[12] => Array
(
[OptionID] => Array
(
[4] => Array
(
[0] => Array
(
[quantity] => 2
[extras_id] => Array
(
[0] => 5
[1] => 10
)
)
[1] => Array
(
[quantity] => 1
[extras_id] => Array
(
[0] => 2
)
)
)
[2] => Array
(
[0] => Array
(
[quantity] => 1
[extras_id] => Array ()
)
)
)
)
[35] => Array
(
[OptionID] => Array
(
[52] => Array
(
[0] => Array
(
[quantity] => 11
[extras_id] => Array ()
)
)
)
)
)
)
)
I would consider doing this by modeling the same data in a few custom PHP objects. In this case you might have a shop object with products, and product objects with options. This is really quick off the top of my head:
class Shop {
private $_products = array();
public function getProducts()
{ return $this->_products;}
public function addProduct(Product $product)
{ $this->_products[] = $product;
return $this;
}
}
class Product {
private $_options = array();
public function getOptions()
{ return $this->_options; }
public function addOption(Option $option)
{ $this->_options[] = $option;
return $this;
}
}
class Option {
private $_optionKey;
private $_optionValue;
public function getKey()
{ return $this->_optionKey; }
public function getKey()
{ return $this->_optionValue; }
public function setOption($key, $value)
{
$this->_optionKey = $key;
$this->_optionValue = $value;
return $this;
}
}
What does this get you? For starters, you can define limits and parameters to what you can store in this, while with the nested array that you are using, there is absolutely no enforcement of structure or value. You can also define other methods that allow you to actually DO things with these bits of data.
If you absolutely MUST have an array version of these, you can implement something like a toArray() method in each of these that will convert the objects into an array to be consumed by some other bit of code. You might also consider reading up on a few interfaces such as iterator and countable in the PHP manual.
You set up one array on the beginning, fine. Now use it in the right way.
$option['ShopID_'.$id]; //where $id is obviusly the id number;
Now fill the $option array with the orders.
$option['ShopID_'.$id]['ProductId_'.$pid][] = array(
'quantity' => 1,
'extras_id' => array(2), //maybe a string is enough here (?) (e.g. '2,5,etc..')
'inches' => $inches
);
$pid is obviusly the pizza Id you are searching for..
as well this is just a "static" example!
I recommend you to use OO programming, this saves you a lot of headache!
Try something like this:
<?php
class Extra
{
var $id;
var $name;
var $amount;
function __construct()
{
$this->id = 0;
$this->name = '';
$this->amount = 0;
}
}
class Option
{
var $id;
var $name;
function __construct()
{
$this->id = 0;
$this->name = '';
}
}
class Pizza
{
var $id;
var $name;
var $options;
var $extras;
function __construct()
{
$this->id = 0;
$this->name = '';
$this->options = array();
$this->extras = array();
}
}
?>
And to test it:
<?php
$pizzas = array();
for($i=0; $i<10; $i++)
{
$pizza = new Pizza();
$pizza->id = $i;
$pizza->name = 'Pizza '.$i;
for($j=0; $j<$i; $j++)
{
$option = new Option();
$option->id = $j;
$option->name = 'Option '.$j;
$pizza->options[] = $option;
}
for($k=$i; $k>0; $k--)
{
$extra = new Extra();
$extra->id = $k;
$extra->name = 'Extra '.$k;
$extra->amount = $k;
$pizza->extras[] = $extra;
}
$pizzas[] = $pizza;
}
print_r($pizzas);
?>
Good luck :)

Categories