What is the best way to represent a tree navigation structure using PHP? I need to be able to track the current page location in the tree using a bread crumb trail. Parts of the tree are generated from a database such as make -> models. Each model then has the same equivalent tree branch, such as:
Make > Model > (Area 1, Area 2, Area 3).
Parts of the tree may change over time. Is it best to have a static class hierarchy structure or a dynamic solution where classes are re-used?
Hope this has been explained simply.
I'd go with:
a $nodes list for each element [if empty it's a leaf node of course];
a $parent field for parent element [if null it's the root node].
This way you can rebuild the breadcrumb trail for every node, providing them a getTrail() method:
public function getTrail()
{
$parent = $this -> parent();
$trail = array();
while($parent !== NULL)
{
// push parent element to trail
$trail[] = $parent;
// go backwards one node
$parent = $parent -> parent();
}
// you want trail in reverse order [from root to latest]
return array_reverse($trail);
}
If your nodes differ in type, be clean providing a TrailTrackable interface with getTrail() / getParent() methods at least.
class TreeNode {
/**
* the parent node
*
* #var TreeNode
*/
private $parentNode=null;
/**
* the children of this node
*
* #var TreeNode[]
*/
private $children=array();
/**
* The user element this tree node holds.
*
* #var Object
*/
private $element;
}
From an OO point of view, I'd recommend defining an interface such as the following:
interface BreadcrumbInterface
{
public function getLabel();
public function getParent(); // returns an instance of BreadcrumbInterface, or null
}
Then you'd create a Page class, which implements this interface and can optionally contain a 'parent', which must also implement this interface. This will build the heirarchy that you need.
A great way to retrieve the full breadcrumbs (while brushing up on your OO design patterns in the process) is to use the visitor pattern. In this case, you probably want to define a common abstract class as well as the interface in order to 'abstract away' the logic of handling the visitor...
abstract class BaseNode implements BreadcrumbInterface
{
protected $parent = null;
public function accept(BreadcrumbVisitor $visitor)
{
$visitor->visit($this);
}
public function setParent(BreadcrumbInterface $parent)
{
$this->parent = $parent;
}
public function getParent()
{
return $this->parent;
}
}
class BreadcrumbVisitor
{
protected $breadcrumbs = array();
public function visit(BreadcrumbInterface $node)
{
$parent = $node->getParent();
if ($parent instanceof BaseNode) {
$parent->accept($this);
}
$this->breadcrumbs[] = $node->getLabel();
}
public function getBreadcrumbs()
{
return $this->breadcrumbs;
}
}
This won't run as is, but hopefully you get the idea. You probably also want your nodes to determine the URL to their page as well as the label, but that can be added easily enough. I just wanted to show the general OO structure to solving this problem.
Edit:
Adding rough usage example:
$rootPage = new Page(/*...*/);
$parentPage = new Page(/*...*/);
$parentPage->setParent($rootPage); // In reality you most likely wouldn't be building this structure so explicitly. Each object only needs to know about it's direct parent
$currentPage = new Page(/*...*/);
$currentPage->setParent($parentPage);
$visitor = new BreadcrumbVisitor();
$currentPage->accept($visitor);
$breadcrumbs = $visitor->getBreadcrumbs(); // returns an array, where the first element is the root
// then you can implode with ' > ' if you want
$breadcumbString = implode(' > ', $breadcrumbs);
Related
This is my demonstration case:
<?php
declare(strict_types=1);
final class Order
{
/** #var array|OrderPosition[] */
private $orderPositions;
public static function fromArray($orderData)
{
assert(isset($orderData['orderId']));
assert(isset($orderData['positions']));
return
new self(
$orderData['orderId'],
array_map(
function (array $positionData): OrderPosition {
// I would like to put the "future self (Order)" alread here
return OrderPosition::fromArray($positionData);
},
$orderData['positions']
)
);
}
private function __construct(string $orderId, array $orderPositions)
{
$this->orderPositions = $orderPositions;
// what I want to avoid is:
array_walk($orderPositions, function (OrderPosition $position) {
$position->defineOwningOrder($this);
});
}
}
final class OrderPosition
{
/** #var Order */
private $owningOrder;
public static function fromArray($positionData /* + as mentioned I'd like to put the "calling" Order here already...? */)
{
return
new self(
$positionData['productId'],
$positionData['amount']
);
}
private function __construct(string $productId, int $amount)
{
// …
}
/** #internal */
public function defineOwningOrder(Order $order)
{
$this->owningOrder = $order;
}
}
I like to have a pointer to the "parent"/owning Order item in my OrderPosition; however since Order is considered an Aggregate Root I want Order to be in charge of creating the collection of OrderPositions.
How should I put the Order item in every OrderPosition when the final Order is not yet there on creation?
My current approach is to late-set it in Order's ctor but that would mutate OrderPosition, strictly spoken.
You have a combination of several design decisions here which are in conflict:
immutable objects
a circular reference
a constructor which is not responsible for constructing the dependent objects
a factory method which is not allowed to see and mutate a partial object
As you say, your current implementation compromises on (1) by allowing the OrderPosition to have the extra reference added in later.
You can make the problem go away if you remove (2). What is the situation where you would have a reference to an OrderPosition and want to navigate to the Order to which it belongs? Can that situation be re-framed as a responsibility of the Order, removing the circular reference?
You could change (3) such that the constructor took the information to create OrderPositions, not the OrderPositions themselves. In your example, this would be trivial, but if in practise you have a number of different factories feeding into one constructor, this might become messy.
Alternatively, if you relax (4) you could pass the partially constructed object into the OrderPosition constructor / factory:
public static function fromArray($orderData)
{
assert(isset($orderData['orderId']));
assert(isset($orderData['positions']));
$instance = new self($orderData['orderId']);
foreach ( $orderData['positions'] as $positionData ) {
$instance->orderPositions[] = OrderPosition::fromArray($positionData, $instance);
}
return $instance;
}
While this still looks like mutation, it is only the same mutation you would do in any constructor - you are creating the initial state of the object.
In a language that supported overloaded or named constructors, fromArray would be a constructor, and might not share any implementation with other constructors. In PHP, you can emulate that pattern with an empty private function __construct(){} and static methods starting with $instance = new self;
That's gonna be a tough one to explain.
I have a class Tree which is rather complex, I try to simplify:
class Tree {
/**
* #var Node[]
*/
private $nodes;
/**
* #var Edge[]
*/
private $edges;
}
class Node {
/**
* #var Value[]
*/
private $values;
/**
* #var array
*/
private $someArray;
}
class Value {
/**
* #var float
*/
private $result;
}
So you can see I have an object Tree containing two arrays of Objects again (Node and Edge) and every Node has an array of objects (Value), some other 'simple array' and every Value has a property result.
To calculate the property result I basically need to run up and down my tree etc... so some have business logic which will end up in still the same Tree but having some calculated results for my nodes.
So what I do so far is something like:
$tree = myCalculationFunction($tree, $calcParameter);
return $tree->getNode(1)->getValue(1)->getResult();
But no when I call additionally the same function with a different calcParameter of course my Tree operates on referenced Nodes, Values etc.
So I cannot:
$initialTree = myCalculationFunction($tree, $calcParameter);
$treeOne = myCalculationFunction($initialTree, $calcParameterOne);
$treeTwo = myCalculationFunction($initialTree, $calcParameterTwo);
$result1 = $treeOne->getNode(1)->getValue(1)->getResult();
$result2 = $treeTwo->getNode(1)->getValue(1)->getResult();
So by I have no deep-copy of my $initialTree because all objects in it are byReference. I cannot clone and I don't see how some manual/custom deep-copy like here will work for this case.
How can I achieve this here? I basically need the initialTree to be stable and every calculation function call manipulates a fully copy of the initially 'calculated tree'.
You could extend the approach from this question, and implement a custom __clone method for each of your classes. Since there don't seem to be any relations between the nodes or edges themselves, it should be enough to accomplish what you want.
It might be worth mentioning that, as described in the documentation, __clone is invoked on the new object just after cloning. It isn't actually responsible for cloning the object which might seem logical at first.
So, simplified for the Tree and Node class:
class Tree
{
private $nodes;
private $edges;
public function __clone()
{
$this->nodes = array_map(function ($node) {
return clone $node;
}, $this->nodes);
$this->edges = array_map(function ($edge) {
return clone $edge;
}, $this->edges);
// clone other properties as necessary
}
}
class Node
{
private $values;
private $someArray;
public function __clone()
{
$this->values = array_map(function ($value) {
return clone $value;
}, $this->values);
$this->someArray = array_map(function ($elem) {
return clone $elem;
}, $this->someArray);
// clone other properties as necessary
}
}
Just follow this pattern for each class down the road and it will recursively deep clone your whole tree.
I'm am a newb with the whole class inheritance in general but I am a bit more confused with php.
I would like to have the following:
class Base
{
//some fields and shared methods...
}
class Node
{
private $children = array();
public function getChildren()
{
...
}
public function addChild($item)
{
$children[] = $item;
}
public function sum()
{
}
}
I want $item to be either another Node or a Leaf:
class Leaf extends Base
{
private $value;
public getValue()
{
}
public setValue($someFloatNumber)
{
$this->value = $someFloatNumber;
}
}
For public sum(), I want something like:
$sum = 0;
foreach ($children as $child)
{
switch(gettype($child))
{
case "Node":
$sum+= ((Node) $child)->sum();
break;
case "Leaf":
$sum+= ((Leaf) $child)->getValue();
break;
}
}
return $sum;
Not sure how to do the cast. Also would the array store the type of the added $item?
This is not proper OOP. Try this instead:
Add method sum to Base (abstract if you don't want to implement). Implement this same method sum for Leaf, which would simply return it's getValue. Then you can simply call sum on both types, thus no need for case, or to know it's type and so on:
foreach ($children as $child) {
$sum += $child->sum();
}
This is called polymorphism and it's one of the basic concepts of object oriented programming.
To also answer your question, you can hint type locally in Netbeans and Zend Studio (and probably other editors) with:
/* #var $varName Type_Name */
You're asking about hinting but then, in your code, you actually try to do a cast. Which is not necessary and not possible in that way.
Hinting example:
private function __construct(Base $node) {}
Which ensures that you can only pass an instance of Base or inheriting classes to the function.
Or if it's important for working in your IDE, you can do:
$something = $child->someMethod(); /* #var $child Base */
Which will make sure, your IDE (and other software) know that $child is of the type Base.
Instead of casting you could just use is_a like that:
if (is_a($child, 'Node') {}
else (is_a($child, 'Leaf') {}
But to be honest, it rather seems like you should refactor your code. I don't think it a good idea that a leaf is any different from a node. A leaf is just a node that doesn't have any children, which you can test anytime with $node->hasChildren() and even set and unset a leaf flag, if you need to.
I have been programming in PHP for several years and have in the past adopted methods of my own to handle data within my applications.
I have built my own MVC in the past and have a reasonable understanding of OOP within php but I know my implementation needs some serious work.
In the past I have used an is-a relationship between a model and a database table. I now know after doing some research that this is not really the best way forward.
As far as I understand it I should create models that don't really care about the underlying database (or whatever storage mechanism is to be used) but only care about their actions and their data.
From this I have established that I can create models of lets say for example a Person
an this person object could have some Children (human children) that are also Person objects held in an array (with addPerson and removePerson methods, accepting a Person object).
I could then create a PersonMapper that I could use to get a Person with a specific 'id', or to save a Person.
This could then lookup the relationship data in a lookup table and create the associated child objects for the Person that has been requested (if there are any) and likewise save the data in the lookup table on the save command.
This is now pushing the limits to my knowledge.....
What if I wanted to model a building with different levels and different rooms within those levels? What if I wanted to place some items in those rooms?
Would I create a class for building, level, room and item
with the following structure.
building can have 1 or many level objects held in an array
level can have 1 or many room objects held in an array
room can have 1 or many item objects held in an array
and mappers for each class with higher level mappers using the child mappers to populate the arrays (either on request of the top level object or lazy load on request)
This seems to tightly couple the different objects albeit in one direction (ie. a floor does not need to be in a building but a building can have levels)
Is this the correct way to go about things?
Within the view I am wanting to show a building with an option to select a level and then show the level with an option to select a room etc.. but I may also want to show a tree like structure of items in the building and what level and room they are in.
I hope this makes sense. I am just struggling with the concept of nesting objects within each other when the general concept of oop seems to be to separate things.
If someone can help it would be really useful.
Let's say you organize your objects like so:
In order to initialize the whole building object (with levels, rooms, items) you have to provide db layer classes to do the job. One way of fetching everything you need for the tree view of the building is:
(zoom the browser for better view)
Building will initialize itself with appropriate data depending on the mappers provided as arguments to initializeById method. This approach can also work when initializing levels and rooms. (Note: Reusing those initializeById methods when initializing the whole building will result in a lot of db queries, so I used a little results indexing trick and SQL IN opetator)
class RoomMapper implements RoomMapperInterface {
public function fetchByLevelIds(array $levelIds) {
foreach ($levelIds as $levelId) {
$indexedRooms[$levelId] = array();
}
//SELECT FROM room WHERE level_id IN (comma separated $levelIds)
// ...
//$roomsData = fetchAll();
foreach ($roomsData as $roomData) {
$indexedRooms[$roomData['level_id']][] = $roomData;
}
return $indexedRooms;
}
}
Now let's say we have this db schema
And finally some code.
Building
class Building implements BuildingInterface {
/**
* #var int
*/
private $id;
/**
* #var string
*/
private $name;
/**
* #var LevelInterface[]
*/
private $levels = array();
private function setData(array $data) {
$this->id = $data['id'];
$this->name = $data['name'];
}
public function __construct(array $data = NULL) {
if (NULL !== $data) {
$this->setData($data);
}
}
public function addLevel(LevelInterface $level) {
$this->levels[$level->getId()] = $level;
}
/**
* Initializes building data from the database.
* If all mappers are provided all data about levels, rooms and items
* will be initialized
*
* #param BuildingMapperInterface $buildingMapper
* #param LevelMapperInterface $levelMapper
* #param RoomMapperInterface $roomMapper
* #param ItemMapperInterface $itemMapper
*/
public function initializeById(BuildingMapperInterface $buildingMapper,
LevelMapperInterface $levelMapper = NULL,
RoomMapperInterface $roomMapper = NULL,
ItemMapperInterface $itemMapper = NULL) {
$buildingData = $buildingMapper->fetchById($this->id);
$this->setData($buildingData);
if (NULL !== $levelMapper) {
//level mapper provided, fetching bulding levels data
$levelsData = $levelMapper->fetchByBuildingId($this->id);
//indexing levels by id
foreach ($levelsData as $levelData) {
$levels[$levelData['id']] = new Level($levelData);
}
//fetching room data for each level in the building
if (NULL !== $roomMapper) {
$levelIds = array_keys($levels);
if (!empty($levelIds)) {
/**
* mapper will return an array level rooms
* indexed by levelId
* array($levelId => array($room1Data, $room2Data, ...))
*/
$indexedRooms = $roomMapper->fetchByLevelIds($levelIds);
$rooms = array();
foreach ($indexedRooms as $levelId => $levelRooms) {
//looping through rooms, key is level id
foreach ($levelRooms as $levelRoomData) {
$newRoom = new Room($levelRoomData);
//parent level easy to find
$levels[$levelId]->addRoom($newRoom);
//keeping track of all the rooms fetched
//for easier association if item mapper provided
$rooms[$newRoom->getId()] = $newRoom;
}
}
if (NULL !== $itemMapper) {
$roomIds = array_keys($rooms);
$indexedItems = $itemMapper->fetchByRoomIds($roomIds);
foreach ($indexedItems as $roomId => $roomItems) {
foreach ($roomItems as $roomItemData) {
$newItem = new Item($roomItemData);
$rooms[$roomId]->addItem($newItem);
}
}
}
}
}
$this->levels = $levels;
}
}
}
Level
class Level implements LevelInterface {
private $id;
private $buildingId;
private $number;
/**
* #var RoomInterface[]
*/
private $rooms;
private function setData(array $data) {
$this->id = $data['id'];
$this->buildingId = $data['building_id'];
$this->number = $data['number'];
}
public function __construct(array $data = NULL) {
if (NULL !== $data) {
$this->setData($data);
}
}
public function getId() {
return $this->id;
}
public function addRoom(RoomInterface $room) {
$this->rooms[$room->getId()] = $room;
}
}
Room
class Room implements RoomInterface {
private $id;
private $levelId;
private $number;
/**
* Items in this room
* #var ItemInterface[]
*/
private $items;
private function setData(array $roomData) {
$this->id = $roomData['id'];
$this->levelId = $roomData['level_id'];
$this->number = $roomData['number'];
}
private function getData() {
return array(
'level_id' => $this->levelId,
'number' => $this->number
);
}
public function __construct(array $data = NULL) {
if (NULL !== $data) {
$this->setData($data);
}
}
public function getId() {
return $this->id;
}
public function addItem(ItemInterface $item) {
$this->items[$item->getId()] = $item;
}
/**
* Saves room in the databse, will do an update if room has an id
* #param RoomMapperInterface $roomMapper
*/
public function save(RoomMapperInterface $roomMapper) {
if (NULL === $this->id) {
//insert
$roomMapper->insert($this->getData());
} else {
//update
$where['id'] = $this->id;
$roomMapper->update($this->getData(), $where);
}
}
}
Item
class Item implements ItemInterface {
private $id;
private $roomId;
private $name;
private function setData(array $data) {
$this->id = $data['id'];
$this->roomId = $data['room_id'];
$this->name = $data['name'];
}
public function __construct(array $data = NULL) {
if (NULL !== $data) {
$this->setData($data);
}
}
/**
* Returns room id (needed for indexing)
* #return int
*/
public function getId() {
return $this->id;
}
}
This is now pushing the limits to my knowledge.....
The building/level/room/item structure you described sounds perfectly fine to me. Domain-driven design is all about understanding your domain and then modeling the concepts as objects -- if you can describe what you want in simple words, you've already accomplished your task. When you're designing your domain, keep everything else (such as persistence) out of the picture and it'll become much simpler to keep track of things.
This seems to tightly couple the different objects albeit in one direction
There's nothing wrong about that. Buildings in the real world do have floors, rooms etc. and you're simply modeling this fact.
and mappers for each class with higher level mappers using the child mappers
In DDD terminology, these "mappers" are called "repositories". Also, your Building object might be considered an "aggregate" if it owns all the floors/rooms/items within it and if it doesn't make sense to load a Room by itself without the building. In that case, you would only need one BuildingRepository that can load the entire building tree. If you use any modern ORM library, it should automatically do all the mapping work for you (including loading child objects).
If I understand your question right , your main problem is that you are not using abstract classes properly. Basically you should have different classes for each of your building, levels, rooms etc. For example you should have an abstract class Building, an abstract class Levels that is extended by Building and so on, depend on what you want to have exactly, and like that you have a tree building->level->room, but it's more like an double-linked list because each building has an array of level objects and each level has parent an building object. You should also use interfaces as many people ignore them and they will help you and your team a lot in the future.
Regarding building models on a more generic way the best way to do it in my opinion is to have a class that implements the same methods for each type of database or other store method you use. For example you have a mongo database and a mysql database, you will have a class for each of these and they will have methods like add, remove, update, push etc. To be sure that you don't do any mistakes and everything will work properly the best way to do this is to have an interface database that will store the methods and you will not end up using a mongo method somewhere where the mysql method is not defined. You can also define an abstract class for the common methods if they have any. Hope this will be helpful, cheers!
I have the following:
class A
{
public function getDependencies()
{
//returns A.default.css, A.default.js, A.tablet.css, A.tablet.js, etc,
//depending on what files exist and what the user's device is.
}
}
In class B, which extends A, if I call getDependencies I will get things like: B.default.css, B.default.js and so on.
What I want to do now is include the results of A as well, without having to override getDependencies() in B. In fact, I'm not even sure if overriding would work, at all. Is this possible?
This is for dynamic CSS/JS loading for templates, and eventually compilation for production as well.
EDIT= I should point out that what getDependencies returns is dynamically generated, and not a set of stored values.
EDIT2= The idea I have is that just inheriting from A will provide the behavior. I probably need some kind of recursion that goes through the hierarchy tree, starting from B, to B's parent, and all the way up to A, without any method overriding happening along the way.
Use parent::getDependencies(), e.g.:
class B
{
public function getDependencies()
{
$deps = array('B.style.js' 'B.default.js', 'B.tables.js' /*, ... */);
// merge the dependencies of A and B
return array_merge($deps, parent::getDependencies());
}
}
You can also try this code which uses ReflectionClass in order to iterate over all parents:
<?php
class A
{
protected static $deps = array('A.default.js', 'A.tablet.js');
public function getDependencies($class)
{
$deps = array();
$parent = new ReflectionClass($this);
do
{
// ReflectionClass::getStaticPropertyValue() always throws ReflectionException with
// message "Class [class] does not have a property named deps"
// So I'm using ReflectionClass::getStaticProperties()
$staticProps = $parent->getStaticProperties();
$deps = array_merge($deps, $staticProps['deps']);
}
while ($parent=$parent->getParentClass());
return $deps;
}
}
class B extends A
{
protected static $deps = array('B.default.js');
}
class C extends B
{
protected static $deps = array('C.default.js');
}
$obj = new C();
var_dump( $obj->getDependencies($obj) );
On Ideone.com
It's pretty easy using the reflection API.
I can simply iterate through the parent classes:
$class = new \ReflectionClass(get_class($this));
while ($parent = $class->getParentClass())
{
$parent_name = $parent->getName();
// add dependencies using parent name.
$class = $parent;
}
Credits to ComFreek who pointed me to the right place.
You can use self keyword - this will returns A class values and then you can use $this to get the B class values.