I have a structure table like this
I want to change record data field goid = 1, but I as you can see my code bellow, there's limit looping. Is there a better way to do it?
public function actionShareGoid($folder)
{
$one = DokumenFolder::findOne($folder);
$one->goid = '1';
$one->update(false);
$models_1 = DokumenFolder::find()->where(['parent_id'=>$one->id])->all();
foreach ($models_1 as $key_1 => $model_1) {
$model_1->goid = '1';
$model_1->update(false);
$models_2 = DokumenFolder::find()->where(['parent_id'=>$model_1->id])->all();
foreach ($models_2 as $key_2 => $model_2) {
$model_2->goid = '1';
$model_2->update(false);
$models_3 = DokumenFolder::find()->where(['parent_id'=>$model_2->id])->all();
foreach ($models_3 as $key_3 => $model_3) {
$model_3->goid = '1';
$model_3->update(false);
$models_4 = DokumenFolder::find()->where(['parent_id'=>$model_3->id])->all();
foreach ($models_4 as $key_4 => $model_4) {
$model_4->goid = '1';
$model_4->update(false);
}
}
}
}
return $this->redirect(Yii::$app->request->referrer);
}
Thanks to someone, I got the answer. ^_^
So here it is...
public function folderParent($id)
{
$models = DokumenFolder::find()->where(['parent_id'=>$id])->all();
foreach ($models as $key => $model) {
$model->goid = '1';
$model->update(false);
}
}
public function actionShareGoid($id)
{
$models = DokumenFolder::find()->where(['parent_id'=>$id])->all();
foreach ($models as $key => $model) {
$this->folderParent($model->id);
}
return $this->redirect(Yii::$app->request->referrer);
}
Related
I have a dictionaries table looks like this:
Here is the array result that I want:
I've already achieved the result by these ugly code:
$dictionaries = Dictionary::all();
$carriers_array = [];
$carriers = $dictionaries->unique('Carrier');
foreach($carriers as $carrier) {
$carriers_array[] = $carrier->Carrier;
}
$DOM_INLs_array = [];
foreach($carriers_array as $carrier_item) {
$DOM_INLs = $dictionaries->where('Carrier', $carrier_item)->unique('DOM_INL');
foreach($DOM_INLs as $DOM_INL) {
$DOM_INLs_array[$carrier_item][] = $DOM_INL->DOM_INL;
}
}
$groups_array = [];
foreach($DOM_INLs_array as $carrier => $DOM_INLs) {
foreach($DOM_INLs as $DOM_INL) {
$groups = $dictionaries->where('Carrier', $carrier)->where('DOM_INL', $DOM_INL)->unique('Group');
foreach($groups as $group) {
$groups_array[$carrier][$DOM_INL][] = $group->Group;
}
}
}
$routes_array = [];
foreach($groups_array as $carrier => $DOM_INLs) {
foreach($DOM_INLs as $DOM_INL => $groups) {
foreach($groups as $group) {
$routes = $dictionaries->where('Carrier', $carrier)->where('DOM_INL', $DOM_INL)->where('Group', $group)->unique('Route');
foreach($routes as $route) {
$routes_array[$carrier][$DOM_INL][$group][] = $route->Route;
}
}
}
}
$sectors_array = [];
foreach($routes_array as $carrier => $DOM_INLs) {
foreach($DOM_INLs as $DOM_INL => $groups) {
foreach($groups as $group => $routes) {
foreach($routes as $route) {
$sectors = $dictionaries->where('Carrier', $carrier)->where('DOM_INL', $DOM_INL)->where('Group', $group)->where('Route', $route)->unique('Sector');
foreach($sectors as $sector) {
$sectors_array[$carrier][$DOM_INL][$group][$route][] = $sector->Sector;
}
}
}
}
}
dd($sectors_array);
But it runs very slow, I want another way to get the same result but more efficient. Can anyone help?
This looks like a bunch of nested maps and groupBys with a pluck at the end:
Arrow functions, PHP 7.4+:
$dictionaries->groupBy('Carrier')
->map(fn ($i) => $i->groupBy('DOM_INL')
->map(fn ($i) => $i->groupBy('Group')
->map(fn ($i) => $i->groupBy('Route')
->map->pluck('Sector')
)
)
);
With anonymous functions:
$dictionaries->groupBy('Carrier')->map(function ($i) {
return $i->groupBy('DOM_INL')->map(function ($i) {
return $i->groupBy('Group')->map(function ($i) {
return $i->groupBy('Route')->map->pluck('Sector');
});
});
});
If you only needed these results grouped in this way and didn't need the leaves to just be the 'Sector' values this would be 1 call to groupBy:
$dictionary->groupBy(['Carrier', 'DOM_INL', 'Group', 'Route'])
I would to create a recursive function in order to retrieve data from an array and to organize then.
However I have some difficulties to create the right logic. The principle must be apply to any sub level until it ends or nothing is found.
I want to prevent this kind of code by repeating a foreach inside a foreach...:
$cats = get_categories($args);
$categories = array();
foreach($cats as $cat){
$parent = $cat->category_parent;
if ($parent) {
$categories['child'][$parent][$cat->cat_ID] = $cat->name;
} else {
$categories['parent'][$cat->cat_ID] = $cat->name;
}
}
}
if (isset($categories['parent']) && !empty($categories['parent'])) {
foreach($categories['parent'] as $id => $cat){
$new_cats[$id]['title'] = $cat;
if (isset($categories['child'][$id])) {
foreach($categories['child'][$id] as $child_id => $child_cat){
$new_cats[$child_id]['title'] = $child_cat;
$new_cats[$child_id]['parent_id'] = $id;
if (isset($categories['child'][$child_id])) {
foreach($categories['child'][$child_id] as $sub_child_id => $sub_child_cat){
$new_cats[$sub_child_id]['title'] = $sub_child_cat;
$new_cats[$sub_child_id]['parent_id'] = $child_id;
}
}
}
}
}
}
}
May be this code help you to get idea for formatting your desired array format.
<?php
// Main function
function BuildArr($arr)
{
$formattedArr = array();
if(!empty($arr))
{
foreach($arr as $val)
{
if($val['has_children'])
{
$returnArr = SubBuildArr($val['children']); // call recursive function
if(!empty($rs))
{
$formattedArr[] = $returnArr;
}
}
else
{
$formattedArr[] = $val;
}
}
}
return $formattedArr;
}
// Recursive Function( Build child )
function SubBuildArr($arr)
{
$sub_fortmattedArr = array();
if(!empty($arr))
{
foreach($arr as $val)
{
if($val['has_children'])
{
$response = SubBuildArr($val['children']); // call recursive
if(!empty($response))
{
$sub_fortmattedArr[] = $response;
}
}
else
{
$sub_fortmattedArr[] = $arr;
}
}
return $sub_fortmattedArr;
}
}
?>
I use this code for my previous project for generating categories that upto n-th level
EDIT 3: Got it down to 300-500 ms by changing flatten method to only merge arrays if not empty.
EDIT 2: Got it down to 1.6 seconds by only calling array_replace for non empty array. Now all that is left to do is optimize the function sort_categories_and_sub_categories. That is NOW the bottleneck. If I remove that I am down to 300ms. Any ideas?
get_all_categories_and_sub_categories
foreach(array_keys($categories) as $id)
{
$subcategories = $this->get_all_categories_and_sub_categories($id, $depth + 1);
if (!empty($subcategories))
{
$categories = array_replace($categories, $subcategories);
}
}
EDIT
I improved performance by over 50% (6 seconds --> 2.5 seconds) by doing a cache in the get_all method. It reduces the amount of queries to 1 from 3000. I am still wondering why it is slow.
I have the following method for getting categories and nested sub categories. If a user has a couple hundred (or thousand) top level categories it does a bunch of queries for each category to find the children. In one case I have 3000 categories and it did 3000 queries. Is there a way to optimize this to do less queries? OR should I just check to see if they have a lot of categories NOT to try to show nested too.
function get_all_categories_and_sub_categories($parent_id = NULL, $depth = 0)
{
$categories = $this->get_all($parent_id);
if (!empty($categories))
{
foreach($categories as $id => $value)
{
$categories[$id]['depth'] = $depth;
}
foreach(array_keys($categories) as $id)
{
$categories = array_replace($categories, $this->get_all_categories_and_sub_categories($id, $depth + 1));
}
return $categories;
}
else
{
return $categories;
}
}
function get_all($parent_id = NULL, $limit=10000, $offset=0,$col='name',$order='asc')
{
static $cache = array();
if (!$cache)
{
$this->db->from('categories');
$this->db->where('deleted',0);
if (!$this->config->item('speed_up_search_queries'))
{
$this->db->order_by($col, $order);
}
$this->db->limit($limit);
$this->db->offset($offset);
foreach($this->db->get()->result_array() as $result)
{
$cache[$result['parent_id'] ? $result['parent_id'] : 0][] = array('name' => $result['name'], 'parent_id' => $result['parent_id'], 'id' => $result['id']);
}
}
$return = array();
$key = $parent_id == NULL ? 0 : $parent_id;
if (isset($cache[$key]))
{
foreach($cache[$key] as $row)
{
$return[$row['id']] = array('name' => $row['name'], 'parent_id' => $row['parent_id']);
}
return $return;
}
return $return;
}
function sort_categories_and_sub_categories($categories)
{
$objects = array();
// turn to array of objects to make sure our elements are passed by reference
foreach ($categories as $k => $v)
{
$node = new StdClass();
$node->id = $k;
$node->parent_id = $v['parent_id'];
$node->name = $v['name'];
$node->depth = $v['depth'];
$node->children = array();
$objects[$k] = $node;
}
// list dependencies parent -> children
foreach ($objects as $node)
{
$parent_id = $node->parent_id;
if ($parent_id !== null)
{
$objects[$parent_id]->children[] = $node;
}
}
// clean the object list to make kind of a tree (we keep only root elements)
$sorted = array_filter($objects, array('Category','_filter_to_root'));
// flatten recursively
$categories = self::_flatten($sorted);
$return = array();
foreach($categories as $category)
{
$return[$category->id] = array('depth' => $category->depth, 'name' => $category->name, 'parent_id' => $category->parent_id);
}
return $return;
}
static function _filter_to_root($node)
{
return $node->depth === 0;
}
static function _flatten($elements)
{
$result = array();
foreach ($elements as $element)
{
if (property_exists($element, 'children'))
{
$children = $element->children;
unset($element->children);
}
else
{
$children = null;
}
$result[] = $element;
if (isset($children))
{
$flatened = self::_flatten($children);
if (!empty($flatened))
{
$result = array_merge($result, $flatened);
}
}
}
return $result;
}
I ceated a function to convert a list of parent child to an object of class "city" :
public static function createCity(array $config)
{
$city= new City();
$id=key($config);
$label= $config[$id]['label'];
$city->setId($id);
$city->setLabel($label);
$children = $config[$id]['childrens'];
if(!empty($children)){
foreach($children as $key_child => $child) {
$children = array($key_child => $child);
$city->addChild(self::createCity($children));
}
}
return $city;
}
Now I would to creat a function to do the opposite => convert an object of type Class City to an array,so I do like that :
public function getCityArray(City$rootCity)
{
$result = array();
$result['id'] = $rootCity->getId();
$result['label']= $rootCity->getLabel();
$children = $rootCity->getChildren();
if ( !empty($children)) {
foreach ($children as $key_child => $child) {
$result['childrens'] = array($key_child => $child );
$result[] = $this->getCityArray($child);
}
}
return $result;
}
But it doesn't work because when I do var_dump('$result') so I have a list with no end and the loop does not stop?
Try this. Since I don't know have the full code, not sure if it will work. The class variable $result will contain the results.
$result = array();
public function getCityArray(City $rootCity) {
$result['id'] = $rootCity->getId();
$result['label']= $rootCity->getLabel();
$children = $rootCity->getChildren();
if ( !empty($children)) {
$result['childrens'] = $children;
$this->result[] = $result;
foreach ($children as $key_child => $child) {
$this->getCityArray($child);
}
}
}
I would like to split an array:
$o = json_decode('[{"id":"1","color":"green"},{"id":"2","color":"green"},{"id":"3","color":"yellow"},{"id":"4","color":"green"}]');
based on the color attribute of each item, and fill corresponding sub arrays
$a = array("green", "yellow", "blue");
function isGreen($var){
return($var->color == "green");
}
$greens = array_filter($o, "isGreen");
$yellows = array_filter($o, "isYellow");
// and all possible categories in $a..
my $a has a length > 20, and could increase more, so I need a general way instead of writing functions by hand
There doesn't seem to exist a function array_split to generate all filtered arrays
or else I need a sort of lambda function maybe
You could do something like:
$o = json_decode('[{"id":"1","color":"green"},{"id":"2","color":"green"},{"id":"3","color":"yellow"},{"id":"4","color":"green"}]');
$greens = array_filter($o, function($item) {
if ($item->color == 'green') {
return true;
}
return false;
});
Or if you want to create something really generic you could do something like the following:
function filterArray($array, $type, $value)
{
$result = array();
foreach($array as $item) {
if ($item->{$type} == $value) {
$result[] = $item;
}
}
return $result;
}
$o = json_decode('[{"id":"1","color":"green"},{"id":"2","color":"green"},{"id":"3","color":"yellow"},{"id":"4","color":"green"}]');
$greens = filterArray($o, 'color', 'green');
$yellows = filterArray($o, 'color', 'yellow');
In my second example you could just pass the array and tell the function what to filter (e.g. color or some other future property) on based on what value.
Note that I have not done any error checking whether properties really exist
I would not go down the road of creating a ton of functions, manually or dynamically.
Here's my idea, and the design could be modified so filters are chainable:
<?php
class ItemsFilter
{
protected $items = array();
public function __construct($items) {
$this->items = $items;
}
public function byColor($color)
{
$items = array();
foreach ($this->items as $item) {
// I don't like this: I would prefer each item was an object and had getColor()
if (empty($item->color) || $item->color != $color)
continue;
$items[] = $item;
}
return $items;
}
}
$items = json_decode('[{"id":"1","color":"green"},{"id":"2","color":"green"},{"id":"3","color":"yellow"},{"id":"4","color":"green"}]');
$filter = new ItemsFilter($items);
$greens = $filter->byColor('green');
echo '<pre>';
print_r($greens);
echo '</pre>';
If you need more arguments you could use this function:
function splitArray($array, $params) {
$result = array();
foreach ($array as $item) {
$status = true;
foreach ($params as $key => $value) {
if ($item[$key] != $value) {
$status = false;
continue;
}
}
if ($status == true) {
$result[] = $item;
}
}
return $result;
}
$greensAndID1 = splitArray($o, array('color' => 'green', 'id' => 1));