Array structure for Shopping Basket - php

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

Related

PHP Reverse Category Tree array to Breadcrumb List

I have a category tree array fetched from MySQL Table. I want to revert this Category array tree back into Breadcrumb list using PHP.
PHP Category Tree Building Function
function buildTree(array &$elements, $parentId = 0)
{
$branch = array();
foreach ($elements as $element) {
if ($element['parent_category_id'] == $parentId) {
$children = buildTree($elements, $element['category_id']);
if ($children) {
$element['children'] = $children;
}
$branch[$element['category_id']] = $element;
unset($elements[$element['category_id']]);
}
}
return $branch;
}
Result Array
[48] => Array
(
[category_id] => 48
[category_name] => Cat 1
[parent_category_id] => 0
[children] => Array
(
[957] => Array
(
[category_id] => 957
[category_name] => Cat 2
[parent_category_id] => 48
[children] => Array
(
[1528] => Array
(
[category_id] => 1528
[category_name] => Cat 3
[parent_category_id] => 957
)
[1890] => Array
(
[category_id] => 1890
[category_name] => Cat 4
[parent_category_id] => 957
)
[1570] => Array
(
[category_id] => 1570
[category_name] => Cat 5
[parent_category_id] => 957
)
[958] => Array
(
[category_id] => 958
[category_name] => Cat 6
[parent_category_id] => 957
)
)
)
Now I want to convert this array tree back into Breadcrumb List using PHP, for example
"Cat 1 > Cat 2 > Cat 3"
"Cat 1 > Cat 2 > Cat 4"
"Cat 1 > Cat 2 > Cat 5"
"Cat 1 > Cat 2 > Cat 6"
Any help would be much appreciated.
Screenshot
Screenshot Reference
The key concept is converting your tree into a flat array where each category is indexed by it's ID. From that flat structure, you can walk up the hierarchy until you reach the root for each category, creating an array of the levels. I've created a helper class to encapsulate the basic functionality you might want for breadcrumbs. The recursion happens in the _unwindTree method. The _buildBreadcrumbs method calls this function and uses the resulting flat array to build the breadcrumb "lines" for each category. These are the two functions to look at to understand how you convert a tree into an array of category paths.
There are some public functions that provide access to the breadcrumb data in different ways.
<?php
$tree = [
48 => [
'category_id' => 48,
'category_name' => 'Cat 1',
'parent_category_id' => 0,
'children' =>
[
957 =>
[
'category_id' => 957,
'category_name' => 'Cat 2',
'parent_category_id' => 48,
'children' =>
[
1528 =>
[
'category_id' => 1528,
'category_name' => 'Cat 3',
'parent_category_id' => 957
],
1890 =>
[
'category_id' => 1890,
'category_name' => 'Cat 4',
'parent_category_id' => 957
],
1570 =>
[
'category_id' => 1570,
'category_name' => 'Cat 5',
'parent_category_id' => 957
],
958 =>
[
'category_id' => 958,
'category_name' => 'Cat 6',
'parent_category_id' => 957
]
]
]
]
]
];
class BreadcrumbHelper
{
private $_leafOnly = true;
private $_defaultBaseUrlPath = '/category/';
private $_tree = [];
private $_idMap = [];
private $_leafIds = [];
private $_breadcrumbs = [];
/**
* BreadcrumbHelper constructor.
* #param array $tree The tree of category data
*/
public function __construct($tree)
{
$this->_tree = $tree;
//Build the breadcrumb data structure
$this->_buildBreadcrumbs();
}
/**
* Return breadcrumbs as an array
* #param mixed $categoryIds optional, only specified categories will be returned
* #return array
*/
public function getBreadcrumbArray($categoryIds = [])
{
//If a bare ID is passed, wrap it in an array so we can treat all input the same way
if (!is_array($categoryIds))
{
$categoryIds = [$categoryIds];
}
//If we have category input, return a filtered array of the breadcrumbs
if (!empty($categoryIds))
{
return array_intersect_key($this->_breadcrumbs, array_flip($categoryIds));
}
//If no input, return the fill array
return $this->_breadcrumbs;
}
/**
* Return breadcrumbs as an array containing HTML markup
* You may want to modify this to echo HTML directly, or return markup only instead of an array
* #param mixed $categoryIds optional, only specified categories will be returned
* #return array
*/
public function getBreadcrumbHtml($categoryIds = [], $baseUrlPath = null)
{
//If a bare ID is passed, wrap it in an array so we can treat all input the same way
if (!is_array($categoryIds))
{
$categoryIds = [$categoryIds];
}
//If a base URL path is provided, use it, otherwise use default
$baseUrlPath = (empty($baseUrlPath)) ? $this->_defaultBaseUrlPath : $baseUrlPath;
//Filter breadcrumbs if IDs provided
$breadcrumbs = (empty($categoryIds)) ? $this->_breadcrumbs : array_intersect_key($this->_breadcrumbs, array_flip($categoryIds));
$output = [];
foreach ($breadcrumbs as $currCategoryId => $currLine)
{
$currLinkBuffer = [];
foreach ($currLine as $currCategory)
{
//Build the markup - customize the URL for your application
$currLinkBuffer[] = '' . $currCategory['category_name'] . '';
}
$output[$currCategoryId] = implode(' > ', $currLinkBuffer);
}
return $output;
}
/**
* Print the breadcrumbs
* #param array $categoryIds optional, only specified categories will be printed
*/
public function printBreadcrumbs($categoryIds = [])
{
//If a bare ID is passed, wrap it in an array so we can treat all input the same way
if (!is_array($categoryIds))
{
$categoryIds = [$categoryIds];
}
//Filter breadcrumbs if IDs provided
$breadcrumbs = (empty($categoryIds)) ? $this->_breadcrumbs : array_intersect_key($this->_breadcrumbs, array_flip($categoryIds));
foreach ($breadcrumbs as $currLine)
{
//Build a buffer of the category names
$currNameBuffer = [];
foreach ($currLine as $currCategory)
{
$currNameBuffer[] = $currCategory['category_name'];
}
//Join the name buffer with a separator and echo the result
echo implode(' > ', $currNameBuffer) . PHP_EOL;
}
}
/**
* Create the breadcrumb data structure from the provided tree
*/
private function _buildBreadcrumbs()
{
//Unwind the tree into a flat array
$this->_unwindTree($this->_tree);
//Traverse the flat array and build the breadcrumb lines
$categoryIds = ($this->_leafOnly) ? $this->_leafIds:array_keys($this->_idMap);
foreach ($categoryIds as $currLeafId)
{
$currCategoryId = $currLeafId;
$currLine = [];
do
{
$currLine[] = $this->_idMap[$currCategoryId];
$currCategoryId = $this->_idMap[$currCategoryId]['parent_category_id'];
} while ($currCategoryId != 0);
$this->_breadcrumbs[$currLeafId] = array_reverse($currLine);
}
}
/**
* Recursive function that traverses the tree and builds an associative array of all categories
* indexed by ID. Categories saved in this structure do not include children.
* #param $branch
*/
private function _unwindTree($branch)
{
foreach ($branch as $currId => $currData)
{
//Set the current category in the ID map, remove the children if present
$this->_idMap[$currId] = array_diff_key($currData, array_flip(['children']));
if (!empty($currData['children']))
{
//Recursion
$this->_unwindTree($currData['children']);
}
else
{
$this->_leafIds[] = $currId;
}
}
}
}
//Instantiate our helper with the tree data
$breadcrumbHelper = new BreadcrumbHelper($tree);
echo 'All breadcrumbs: ' . PHP_EOL;
$breadcrumbHelper->printBreadcrumbs();
echo PHP_EOL;
echo 'Single breadcrumb line by category ID: ' . PHP_EOL;
$breadcrumbHelper->printBreadcrumbs(1570);
echo PHP_EOL;
echo 'Multiple categories: ' . PHP_EOL;
$breadcrumbHelper->printBreadcrumbs([957, 1570]);
echo PHP_EOL;
echo 'Breadcrumb HTML: ' . PHP_EOL;
$breadcrumbMarkup = $breadcrumbHelper->getBreadcrumbHtml();
echo $breadcrumbMarkup[1570] . PHP_EOL;
echo PHP_EOL;
echo 'Breadcrumb HTML with custom base URL: ' . PHP_EOL;
$breadcrumbMarkup = $breadcrumbHelper->getBreadcrumbHtml(1570, '/category.php?id=');
echo $breadcrumbMarkup[1570] . PHP_EOL;
echo PHP_EOL;
function treeToArray($data, &$return_data, $index = '', $sub = 'sub')
{
if (is_array($data)) {
foreach ($data as $value) {
if (isset($value[$sub])) {
$tmp = $value[$sub];
unset($value[$sub]);
if ($index) {
$return_data[$value[$index]] = $value;
} else {
$return_data[] = $value;
}
treeToArray($tmp, $return_data, $index, $sub);
} else {
if ($index) {
$return_data[$value[$index]] = $value;
} else {
$return_data[] = $value;
}
}
}
}
return $return_data;
}
$tree[48] = Array
(
"category_id" => '48',
"category_name" => 'Cat 1',
"parent_category_id" => '0',
"children" => Array
(
'957' => Array
(
"category_id" => "957",
"category_name" => "Cat",
"parent_category_id" => "48",
"children" => Array
(
'1528' => Array
(
"category_id" => "1528",
"category_name" => "Cat3",
"parent_category_id" => "957",
)
)
)
)
);
$data = [];
treeToArray($tree, $data, 'category_id', 'children');
print_r($data);
hope it can be helpful

Foreach array to update table of database use those element of array

How to get the first value of element of array in php.
My story board is like this:
I have an array like this:
(
[0] => Array
(
[ID] => 68
[MATERIAL] => I have
[AC] => Try
)
[1] => Array
(
[ID] => 69
[MATERIAL] => It
[AC] => No Surrender
)
)
I want to update some record on my database like this,
foreach element of array,
UPDATE MY TABEL SET MATERIAL = [MATERIAL], AC = [AC] where id= [id]
this is the model named m_admin :
public function update_eir_to_cost($id, $material, $ac) {
$data = array(
"MATERIAL" => $material,
"AC" => $ac);
$this->db->trans_start();
$this->db->where($id);
$this->db->update('tb_repair_detail', $data);
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE) {
// generate an error... or use the log_message() function to log your error
echo "Error Updating";
} else {
echo "Alhamdulillah";
}
}
This is the controller :
public function update_json_detail() {
$post_data = $this->input->post("POST_ARRAY");
$execute = array();
foreach ($post_data as $data) {
$execute[] = array(
'ID'=> $data['0'],
'MATERIAL' => $data['7'],
'AC' => $data['8']
);
}
echo "<pre>";
print_r($execute); // return an array like above.
/*forech element
update table using model
*/
}
This will solve your problem:
public function update_json_detail() {
$post_data = $this->input->post("POST_ARRAY");
$execute = array();
foreach ($post_data as $data) {
$execute[] = array(
'ID'=> $data['0'],
'MATERIAL' => $data['7'],
'AC' => $data['8']
);
}
echo "<pre>";
print_r($execute); // return an array like above.
$this->load->model('m_admin');
foreach ($execute as $row) {
$this->m_admin->update_eir_to_cost($row['ID'], $row['MATERIAL'], $row['AC']);
}
}

Compare each multidimensional array value to all values in second multidimensional array?

Im a bit new here, Thanks for the help in advance.
I got 2 Multidimensional Arrays in PHP
Both pretty much look like this one:
Array
(
[0] => Array
(
[0] => 1
[Id] => 1
[1] => Soldier
[Name] => Soldier
[2] => 100
[Hitpoints] => 100
[3] => 15
[Attack] => 15
[4] => 50
[Speed] => 50
[5] => 50
[Range] => 50
[Total Units] => 511
[Combined Damage] => 7588.35
[Combined Hitpoints] => 51100
[Position] => 50
)
[1] => Array
(
[0] => 2
[Id] => 2
[1] => Sniper
[Name] => Sniper
[2] => 20
[Hitpoints] => 20
[3] => 50
[Attack] => 50
[4] => 20
[Speed] => 20
[5] => 300
[Range] => 0
[Total Units] => 0
[Combined Damage] => 0
[Combined Hitpoints] => 0
[Position] => 50
)
)
The arrays names are:
$Attackers
$Defenders
I need to compare values from $Attackers[*]['Position'] with $Defenders[*]['Position'].
I did make this loop but my problem is, that it only checks the same Index count to eachother. I need to check the $Attackers[0] to all $Defender indexes, then $Attackers[1] to all $Defender indexes, and so on.
Heres my original code that works, but only checks against the same index.
for($rowcount=0;$rowcount<count($Attackers);$rowcount++){
if (isset($Attackers[$rowcount]['Range']) && $Attackers[$rowcount]['Range'] != 0) {
if (($Attackers[$rowcount]['Position'] + $Defenders[$rowcount]['Position']) < $Attackers[$rowcount]['Range']) {
echo "within range, ATTACK";
} else {
echo "Before: " . $Attackers[$rowcount]['Position'] . "<br>";
$Attackers[$rowcount]['Position'] = $Attackers[$rowcount]['Position'] - $Attackers[$rowcount]['Speed'];
echo "After: " . $Attackers[$rowcount]['Position'] . "<br>";
}
}
}
I hope this is enough information.
Regards
Jesper
Doing this kind of check is pretty horrific and not scalable...
You would possibly benefit from having a map of positions. You could then simplify by observing events and applying outcomes.
class GameMap implements SplObserver
{
private $positions;
public function __construct($xSize, $ySize)
{
$this->positions = array();
for ($x = 0; $x < $xSize; $x++) {
$this->positions[$x] = array();
for ($y = 0; $y < $ySize; $y++) {
$this->positions[$x][$y] = new MapPosition($x, $y);
}
}
}
public function update(SplSubject $subject)
{
switch ($subject->getAction($this)) {
case "attack":
$positions = $this->getPositionsInRange($subject);
foreach ($positions as $position) {
$position->defend($subject);
}
break;
}
}
private function getPositionsInRange(Soldier $soldier)
{
$inRange = array();
$position = $soldier->getPosition();
$range = $soldier->range;
for ($x = ($position->coord["x"] - $range); $x < ($position->coord["x"] + $range); $x++) {
for ($y = ($position->coord["y"] - $range); $y < ($position->coord["y"] + $range); $y++) {
if (isset($this->positions[$x][$y])) {
$inRange[] = $this->positions[$x][$y];
}
}
}
return $inRange;
}
public function __get($key)
{
return isset($this->$key) ? $this->$key : null;
}
}
class MapPosition
{
private $coords = array();
private $players;
public function __construct($x, $y)
{
$this->coords["x"] = $x;
$this->coords["y"] = $y;
$this->players = new SplObjectStorage();
}
public function enter(Soldier $player)
{
$this->players->attach($player);
return $this;
}
public function leave(Soldier $player)
{
$this->players->detach($player);
return $this;
}
public function defend(Soldier $soldier)
{
foreach($this->players as $player)
{
$player->defend($soldier);
}
}
public function __get($key)
{
return isset($this->$key) ? $this->$key : null;
}
}
class Soldier implements SplSubject
{
private $id;
private $name;
private $hitPoints;
private $health;
private $attack;
private $speed;
private $range;
private $observers;
private $action;
private $position;
public function __construct($soldierData)
{
$this->id = $soldierData["id"];
$this->name = $soldierData["name"];
$this->hitPoints = $soldierData["hit_points"];
$this->health = $soldierData["health"];
$this->attack = $soldierData["attack"];
$this->speed = $soldierData["speed"];
$this->range = $soldierData["range"];
$this->observers = new SplObjectStorage();
}
public function attach(SplObserver $observer)
{
$this->observers->attach($observer);
}
public function detach(SplObserver $observer)
{
$this->observers->detach($observer);
}
public function notify()
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function getAction($observer)
{
return $this->observers->contains($observer) ? $this->action : null;
}
public function setPosition(MapPosition $position)
{
$this->postion = $position;
}
public function getPosition()
{
return $this->position;
}
public function attack()
{
$this->action = "attack";
$this->notify();
}
public function defend(Soldier $soldier)
{
$this->health -= $soldier->attack;
}
public function __get($key)
{
return isset($this->$key) ? $this->$key : null;
}
}
$s1 = new Soldier(array(
"id" => 1,
"name" => 'Sniper',
"hit_points" => 1000,
"health" => 100,
"attack" => 20,
"speed" => 5,
"range" => 10
));
$s2 = new Soldier(array(
"id" => 1,
"name" => 'Medic',
"hit_points" => 10000,
"health" => 100,
"attack" => 4,
"speed" => 10,
"range" => 1
));
$s3 = new Soldier(array(
"id" => 1,
"name" => 'Private',
"hit_points" => 5000,
"health" => 100,
"attack" => 10,
"speed" => 15,
"range" => 3
));
$a1 = new Soldier(array(
"id" => 1,
"name" => 'Sniper',
"hit_points" => 1000,
"health" => 100,
"attack" => 20,
"speed" => 5,
"range" => 15
));
$a2 = new Soldier(array(
"id" => 1,
"name" => 'Medic',
"hit_points" => 10000,
"health" => 100,
"attack" => 4,
"speed" => 10,
"range" => 1
));
$a3 = new Soldier(array(
"id" => 1,
"name" => 'Private',
"hit_points" => 5000,
"health" => 100,
"attack" => 10,
"speed" => 15,
"range" => 3
));
$map = new GameMap(20, 20);
$s1->attach($map);
$s2->attach($map);
$s3->attach($map);
$a1->attach($map);
$a2->attach($map);
$a3->attach($map);
$map->positions[0][0]->enter($a1)->enter($a2)->enter($a3);
$map->positions[9][9]->enter($s1)->enter($s2)->enter($s3);
var_dump($s1->health, $s2->health, $s3->health);
$a1->attack();
var_dump($s1->health, $s2->health, $s3->health);
$map->positions[9][9]->leave($s3);
$map->positions[19][19]->enter($s3);
$a1->attack();
var_dump($s1->health, $s2->health, $s3->health);
There is lots and lots of room for improvement here but hopefully you can see that you don't need to do all this checking. A soldier can attack, the attack notifies the map and the map checks which positions are in range of the attack. The positions then call the defend method of any soldiers in that position. The soldier defending then has their health reduced by the attack amount.
As I said LOADS of room for improvement like introducing sides and making sure no friendly fire kills can happen. Improve movement by applying move methods to Soldiers and they could then trigger leave/enter events on MapPositions (making the MapPosition an observer of the soldier too perhaps).
The main point though is that this looping is not really necessary and would suffer in scaling. Better design of your app will reap its own rewards...
You need to use nested for loops to check all the defenders positions versus one attackers position.
Something like this should get you started.
for($rowcount=0;$rowcount<count($Attackers);$rowcount++){
for($defrowcount=0; $defrowcount<count($Defenders); $defrowcount++){
if (isset($Attackers[$rowcount]['Range']) && $Attackers[$rowcount]['Range'] != 0) {
if (($Attackers[$rowcount]['Position'] + $Defenders[$defrowcount]['Position']) < $Attackers[$rowcount]['Range']) {
echo "within range, ATTACK";
} else {
echo "Before: " . $Attackers[$rowcount]['Position'] . "<br>";
$Attackers[$rowcount]['Position'] = $Attackers[$rowcount]['Position'] - $Attackers[$rowcount]['Speed'];
echo "After: " . $Attackers[$rowcount]['Position'] . "<br>";
}
}
}
}

Finding the best-fit object from an array of objects

Given an array in the following structure (although obviously with many more items in it):
Array
(
[0] => stdClass Object
(
[currency] => 1
[role] => 3
[client_company] =>
[client_group] =>
[hourly_rate] => 115.00
)
[1] => stdClass Object
(
[currency] => 1
[role] => 1
[client_company] =>
[client_group] =>
[hourly_rate] => 115.00
)
[2] => stdClass Object
(
[currency] => 1
[role] => 3
[client_company] => 58
[client_group] =>
[hourly_rate] => 110.00
)
)
I'm trying to create a function that will take four parameters:
$role
$currency
$company [optional]
$group [optional]
("groups" are children of "companies": if a group is specified, a parent company will always also be specified)
...and that will return the "hourly rate" value from the item that best fits those parameters, on the basis that:
if $row, $currency, $company and $group are specified:
find a rate that matches the role, currency, company and group.
if there isn't one, find one that matches the role, currency and company
if there isn't one, find one that matches the role and currency
if there isn't one, return FALSE
if just $row, $currency and $company are specified:
find a rate that matches the role, currency and company
if there isn't one, find one that matches the role and currency
if there isn't one, return FALSE
if just $row and $currency are specified:
find a rate that matches the role and currency
if there isn't one, return FALSE
What I've got is below, and it works. However, it's ugly as sin. There must be a more elegant way than just bashing a load of if/else and loops together. However, it's Friday and I've had too much pizza for lunch and my brain has become ensludged with cheese.
Can you help?
$hourly_rate = FALSE;
if ( !empty($group) && !empty($company) ) {
foreach ( $rates_cache as $rate ) {
if ( $rate->currency == $currency && $rate->role == $role && (int) $rate->client_company === (int) $company && (int) $rate->client_group === (int) $group ) {
$hourly_rate = $rate->hourly_rate;
}
}
if ( empty($hourly_rate) ) {
foreach ( $rates_cache as $rate ) {
if ( $rate->currency == $currency && $rate->role == $role && (int) $rate->client_company === (int) $company ) {
$hourly_rate = $rate->hourly_rate;
}
}
}
if ( empty($hourly_rate) ) {
foreach ( $rates_cache as $rate ) {
if ( $rate->currency == $currency && $rate->role == $role ) {
$hourly_rate = $rate->hourly_rate;
}
}
}
}else if ( !empty($company) ) {
foreach ( $rates_cache as $rate ) {
if ( $rate->currency == $currency && $rate->role == $role && (int) $rate->client_company === (int) $company ) {
$hourly_rate = $rate->hourly_rate;
}
}
if ( empty($hourly_rate) ) {
foreach ( $rates_cache as $rate ) {
if ( $rate->currency == $currency && $rate->role == $role ) {
$hourly_rate = $rate->hourly_rate;
}
}
}
}else{
foreach ( $rates_cache as $rate ) {
if ( $rate->currency == $currency && $rate->role == $role ) {
$hourly_rate = $rate->hourly_rate;
}
}
}
return $hourly_rate;
Assumption
I believe your cache is always in the format below
Cache Format:
$cache = array(
0 => (object) (array(
'currency' => 1,
'role' => 3,
'client_company' => '',
'client_group' => '',
'hourly_rate' => '115.00'
)),
1 => (object) (array(
'currency' => 1,
'role' => 1,
'client_company' => '',
'client_group' => '',
'hourly_rate' => '115.00'
)),
2 => (object) (array(
'currency' => 1,
'role' => 3,
'client_company' => 58,
'client_group' => '',
'hourly_rate' => '110.00'
))
);
Your Revised Function
$param = array(
"role" => 1,
"currency" => 1
);
echo find($cache, $param)->hourly_rate;
Function Used
function find($cache, $param) {
$mx = array();
if (! isset($param['role']) || ! isset($param['currency']))
throw new Exception("Missing Role Or Currency");
foreach ( $cache as $k => $r ) {
foreach ( array_keys(array_intersect($param, (array) $r)) as $key ) {
if ($r->{$key} == $param[$key]) {
isset($mx[$k]) ? $mx[$k] ++ : $mx[$k] = 1;
}
}
}
arsort($mx);
return $cache[key($mx)];
}
More Complex: Another Approach
Usage
$param = array(
"role" => 1,
"currency" => 1
);
$process = new Process($cache);
echo $process->find($param)->best()->hourly_rate; // Outputs 115.00
Multiple Results
When find best fit .. there is possibility you would get more than one result
$param = array(
"role" => 3,
"currency" => 1
);
$process = new Process($cache);
var_dump($process->find($param)->results());
Output
array (size=2)
0 =>
object(stdClass)[1]
public 'currency' => int 1
public 'role' => int 3
public 'client_company' => string '' (length=0)
public 'client_group' => string '' (length=0)
public 'hourly_rate' => string '115.00' (length=6)
2 =>
object(stdClass)[3]
public 'currency' => int 1
public 'role' => int 3
public 'client_company' => int 58
public 'client_group' => string '' (length=0)
public 'hourly_rate' => string '110.00' (length=6)
Not getting best result
You can see based on your parameters you are getting 2 if you are looking for cheapest prize and you call
$param = array(
"role" => 3,
"currency" => 1
);
echo Process::quick($cache, $param)->best()->hourly_rate; // returns 115.00 but that is not the cheapest
Resolution
The solution is you can add filter and sort
$param = array(
"role" => 3,
"currency" => 1
);
$sort = function ($a, $b) {
return $a->hourly_rate < $b->hourly_rate ? - 1 : 1;
};
echo Process::quick($cache, $param)->sort($sort)->best()->hourly_rate; // 110
Getting all Related
You can also just loop through all the result and select the columns you want insted of just getting best result
foreach ( Process::quick($cache, $param)->sort($sort)->getColoum("client_company", "hourly_rate") as $result ) {
print_r($result);
}
Output
stdClass Object
(
[client_company] => 58
[hourly_rate] => 110.00
)
stdClass Object
(
[client_company] =>
[hourly_rate] => 115.00
)
Updated Class
To add all this additional functions you need to upgrade your class to
class Process implements JsonSerializable, IteratorAggregate {
private $cache;
private $matrix = array();
private $final = array();
function __construct($cache) {
$this->cache = $cache;
}
function find($param) {
if (! isset($param['role']) || ! isset($param['currency']))
throw new Exception("Missing Role Or Currency");
foreach ( $this->cache as $k => $rate ) {
$keys = array_intersect($param, (array) $rate);
foreach ( array_keys($keys) as $key ) {
if ($rate->{$key} == $param[$key]) {
isset($this->matrix[$k]) ? $this->matrix[$k] ++ : $this->matrix[$k] = 1;
}
}
}
arsort($this->matrix);
$this->matrix = array_keys(array_filter($this->matrix, function ($v) {
return $v >= 2;
}));
$this->final = $this->sortArray($this->cache, $this->matrix);
return $this;
}
public static function quick($cache, $param) {
$process = new Process($cache);
return $process->find($param);
}
public function best() {
reset($this->final);
return empty($this->final) ? : current($this->final);
}
public function results() {
return $this->final;
}
public function limit($length = 0) {
$this->final = array_slice($this->final, 0, $length);
return $this;
}
public function sort(Callable $function) {
usort($this->final, $function);
return $this;
}
public function getColoum() {
$arg = array_flip(func_get_args());
foreach ( $this->final as &$s ) {
foreach ( $s as $k => $v ) {
if (! isset($arg[$k]))
unset($s->{$k});
}
}
return $this;
}
public function getIterator() {
return new ArrayIterator($this->final);
}
public function jsonSerialize() {
return json_encode($this->final);
}
public function __toString() {
return $this->jsonSerialize();
}
private function sortArray(array $array, array $orderArray) {
$ordered = array();
foreach ( $orderArray as $key => $value ) {
array_key_exists($value, $array) and $ordered[$value] = $array[$value];
}
return $ordered;
}
}

Create array of hierarchical directories in PHP

I have the following code (I know that this code is not optimized but it's not for discussion):
function select_categories($cat_id)
{
$this->db = ORM::factory('category')
->where('parent', '=', $cat_id)
->find_all();
foreach ($this->db as $num => $category)
{
if($category->parent == 0)
{
$this->tmp[$category->parent][$category->id] = array();
}
else {
$this->tmp[$category->parent][$category->id] = array();
}
$this->select_categories($category->id);
}
return $this->tmp;
}
Function returns this array:
array(3) (
0 => array(2) (
1 => array(0)
2 => array(0)
)
2 => array(1) (
3 => array(0)
)
3 => array(2) (
4 => array(0)
5 => array(0)
)
)
But how should I change the code
else {
$this->tmp[$category->parent][$category->id] = array();
// ^^^^^^^^^^^^^^^^^^^^^^ (this bit)
}
To merge array[3] to array[2][3] for example (because array[3] is a subdirectory of array[2] and array[2] is a subdirectory of array[0][2]), so, I need to make this (when I don't know the level of subdirectories):
array (
0 => array (
1 => array
2 => array (
3 => array (
4 => array
5 => array
)
)
)
)
A long time ago I wrote some code to do this in PHP. It takes a list of entities (in your case, categories) and returns a structure where those entities are arranged in a tree. However, it uses associative arrays instead of objects; it assumes that the “parent” ID is stored in one of the associative array entries. I’m sure that you can adapt this to your needs.
function make_tree_structure ($nontree, $parent_field)
{
$parent_to_children = array();
$root_elements = array();
foreach ($nontree as $id => $elem) {
if (array_key_exists ($elem[$parent_field], $nontree))
$parent_to_children [ $elem[$parent_field] ][] = $id;
else
$root_elements[] = $id;
}
$result = array();
while (count ($root_elements)) {
$id = array_shift ($root_elements);
$result [ $id ] = make_tree_structure_recurse ($id, $parent_to_children, $nontree);
}
return $result;
}
function make_tree_structure_recurse ($id, &$parent_to_children, &$nontree)
{
$ret = $nontree [ $id ];
if (array_key_exists ($id, $parent_to_children)) {
$list_of_children = $parent_to_children [ $id ];
unset ($parent_to_children[$id]);
while (count ($list_of_children)) {
$child = array_shift ($list_of_children);
$ret['children'][$child] = make_tree_structure_recurse ($child, $parent_to_children, $nontree);
}
}
return $ret;
}
To see what this does, first try running it on a structure like this:
var $data = array (
0 => array('Name' => 'Kenny'),
1 => array('Name' => 'Lilo', 'Parent' => 0),
2 => array('Name' => 'Adrian', 'Parent' => 1)
3 => array('Name' => 'Mark', 'Parent' => 1)
);
var $tree = make_tree_structure($data, 'Parent');
If I’m not mistaken, you should get something like this out: (the “Parent” key would still be there, but I’m leaving it out for clarity)
array (
0 => array('Name' => 'Kenny', 'children' => array (
1 => array('Name' => 'Lilo', 'children' => array (
2 => array('Name' => 'Adrian')
3 => array('Name' => 'Mark')
)
)
)
Examine the code to see how it does this. Once you understand how this works, you can tweak it to work with your particular data.
Assuming you dont want any data/children tags in your array:
foreach ($this->db as $num => $category)
{
// save the data to the array
$this->tmp[$category->id] = array();
// save a reference to this item in the parent array
$this->tmp[$category->parent][$category->id] = &$this->tmp[$category->id];
$this->select_categories($category->id);
}
// the tree is at index $cat_id
return $this->tmp[$cat_id];
If you just need to retrieve the full tree out of the database, you can even simplify your query (get all records at once) and remove the recursive call in this function. You will need an extra check that will only set the $this->tmp[$catagory->id] when it does not exist and else it should merge the data with the existing data.

Categories