CakePHP: Duplicating/Deep Copying a Tree Structure Easily? - cakephp

I am wondering is there an easy way to make a copy of a tree structure that consists of one model with a parent_id that actsAs a tree?
I was thinking it should be easy to recursively go through a given tree, remove all ids and lft, rght fields; however, as I add the new fields parent_id won't match up correctly. I suppose it should be easy enough to write my own function to handle this, but I just wanted to know if there was already something to handle this?
Thanks!!

First, I used Multi Tree Behaviour ( http://bakery.cakephp.org/articles/view/multitree-behavior ) as it allows several trees to be managed in one table.
My examples are perhaps a little complex and confused with application specific code, but I'm sure you can pick your way through it!
Pretty much anything you do with the tree is going to need a recursive 'tree-walker'. I wrote a task manager with sub-tasks etc. and this is an example of a method I used to walk the tree:
function _walkTasksTree($nodes, $model='Task')
{
foreach($nodes as $node)
{
$task = $node[$model];
$id = $task['id'];
$title = $task['name'];
$level = $task['level'];
$children = $this->_walkTasksTree($node['children'],$model);
if(empty($children))
{
$data[$id] = array('level'=>$level,'title'=>$title);
}
else
{
$data[$id] = array('level'=>$level,'title'=>$title,'children' => $children);
}
}
$data = (isset($data))?$data:array();
return $data;
}
My application has a 'repository' of common tasks which can be cloned into a project. The basic model is ProjectTask [1:1] ProjectTaskDescriptor - the descriptor holding the data, and the task holding the tree position. I use this method to traverse and clone trees and/or branches
function _saveTaskTree($subTasks,$parent_id,$root_id,$projId,$exclude=null)
{
foreach($subTasks as $node)
{
if(#$exclude!=$node['Task']['id'])
{
$node['Task']['id'] = null;
$node['Task']['root_id'] = $root_id;
$node['Task']['parent_id'] = $parent_id;
$node['Task']['project_id'] = $projId;
$this->ProjectTask->create();
$saved = $this->ProjectTask->save($node['Task']);
$this->ProjectTaskDescriptor->create();
$PTD = $node['TaskDescriptor'];
$PTD['project_task_id'] = $this->ProjectTask->id;
$this->ProjectTaskDescriptor->save($PTD);
}
else
{
$saved = true; //skip the first one - we've already done it.
}
if($saved)
{
if(#$node['children'])
$children = $this->_saveTaskTree($node['children'],$this->ProjectTask->id,$root_id,$projId);
}
}
}
It is a very hands on process and in terms of future understanding and maintenance of the code it is worthwhile fully understanding what is going on.

Related

Dapper One to Many Mapping Logic

The dapper tutorial gives this example to help a user with Multi Mapping (One to Many)
While this works I am curious why they have you store the orders in the dictionary but then in the end they use a linq.Distinct() and return from the list. It seems like it would be cleaner to just return the ordersDictionary.Values as the dictionary logic ensures no duplicates.
//Tutorial
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
Dictionary<int,Order> orderDictionary = new Dictionary<int, Order>();
List<Order> list = connection.Query<Order, OrderDetail, Order>(sql, (order, orderDetail) =>
{
if (!orderDictionary.TryGetValue(order.OrderID, out Order orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
}, splitOn: "OrderID")
.Distinct()
.ToList();
return list;
}
//my suggestion
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
Dictionary<int,Order> orderDictionary = new Dictionary<int, Order>();
//change 1 no need to store into list here
connection.Query<Order, OrderDetail, Order>(sql, (order, orderDetail) =>
{
if (!orderDictionary.TryGetValue(order.OrderID, out Order orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
}, splitOn: "OrderID"); //change 2 remove .Distinct().ToList()
return orderDictionary.Values.ToList(); //change 3 return dictionaryValues
}
I'm the author of this tutorial: https://dapper-tutorial.net/query#example-query-multi-mapping-one-to-many
why they have you store the orders in the dictionary
A row is returned for every OrderDetail. So you want to make sure to add the OrderDetail to the existing Order and not create a new one for every OrderDetail. The dictionary is used for performance to check if the Order has been already created or not.
it would be cleaner to just return the ordersDictionary.Values
How will your query return dictionary values?
Of course, if you are in a method such as yours, you can do
var list = orderDictionary.Values;
return list;
But how to make this Connection.Query return dictionary values? An order is returned for every row/OrderDetail, so the order will be returned multiple times.
Outside the Query, your dictionary solution works great and is even a better solution for performance, but if you want to make your Query return the distinct list of orders without using Distinct or some similar method, it's impossible.
EDIT: Answer comment
my suggestion return orderDictionary.Values.ToList(); //change 3 return dictionaryValues
Thank you for your great feedback, it's always appreciated ;)
It would be weird in a tutorial to use what the query returns when there is no relationship but use the dictionary for one to many relationships
// no relationship
var orders = conn.Query<Order>("", ...).Distinct();
// one to many relationship
conn.Query<Order, OrderDetail>("", ...);
var orders = orderDictionary.Values.ToList();
Your solution is better for performance the way you use it, there is no doubt about this. But this is how people usually use the Query method:
var orders = conn.Query("", ...).Distinct();
var activeOrders = orders.Where(x => x.IsActive).ToList();
var inactiveOrders = orders.Where(x => !x.IsActive).ToList();
They use what the Query method returns.
But again, there is nothing wrong with the way you do it, this is even better if you can do it.

Node fast way to find in array

I have a Problem.
My script was working fine and fast, when there was only like up to 5000 Objects in my Array.
Now there over 20.000 Objects and it runs slower and slower...
This is how i called it
for(var h in ItemsCases) {
if(itmID == ItemsCases[h].sku) {
With "for" for every object and check where the sku is my itmID, cause i dont want every ItemsCases. Only few of it each time.
But what is the fastest and best way to get the items with the sku i need out of it?
I think mine, is not the fastest...
I get multiple items now with that code
var skus = res.response.cases[x].skus;
for(var j in skus) {
var itmID = skus[j];
for(var h in ItemsCases) {
if(itmID == ItemsCases[h].sku) {
the skus is also an array
ItemsCases.find(item => item.sku === itmID) (or a for loop like yours, depending on the implementation) is the fastest you can do with an array (if you can have multiple items returned, use filter instead of find).
Use a Map or an object lookup if you need to be faster than that. It does need preparation and memory, but if you are searching a lot it may well be worth it. For example, using a Map:
// preparation of the lookup
const ItemsCasesLookup = new Map();
ItemsCases.forEach(item => {
const list = ItemsCasesLookup.get(item.sku);
if (list) {
list.push(item)
} else {
ItemsCasesLookup.set(item.sku, [item]);
}
});
then later you can get all items for the same sku like this:
ItemsCasesLookup.get(itmID);
A compromise (not more memory, but some speedup) can be achieved by pre-sorting your array, then using a binary search on it, which is much faster than linear search you have to do on an unprepared array.

FAL insertion into sys_file TYPO3

I'm trying to insert a file into TYPO3 db through frontend using core functions or FileRepository, exactly into sys_file table.
While investigating I've seen few solutions like,
$storageRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
$storage = $storageRepository->findByUid(1);
$fileObject = $storage->addFile('/tmp/myfile', $storage->getRootLevelFolder(), 'newFile');
echo $fileObject->getIdentifier(); // Should output "/newFile"
But I still can't find this addFile() in storageRepository class. Am I missing some thing here?
The line $storageRepository->findByUid(1) return a ResourceStorage Object with the Method addFile().
Here is a Documenttion of this class.
https://typo3.org/api/typo3cms/class_t_y_p_o3_1_1_c_m_s_1_1_core_1_1_resource_1_1_resource_storage.html
#mario Thanks. By the way I've achieved what I planned. Here's what I did..
public function uploadFile($uploadedfile) {
$storage = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
$filePath = 'uploads/tx_fileupload/'.$uploadedfile['updata']['name'];
$title = $uploadedfile['updata']['name'];
$size = $uploadedfile['updata']['size'];
// Moving the physical file to destined folder
\TYPO3\CMS\Core\Utility\GeneralUtility::upload_copy_move($uploadedfile['updata']['tmp_name'],$filePath);
// Adding a record in sys_file_storage
$fileObject = $storage->createLocalStorage($uploadedfile['updata']['name'],$uploadedfile['updata']['tmp_name'],$filePath,'');
// Inserting file in sys_file
$repositoryFileObject = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->retrieveFileOrFolderObject($filePath);
return $repositoryFileObject;
}
Now moving onto adding corresponding sys_file_reference record.

Circular references and stack overflow exceptions

I have this many to many association between KundeInfo and HovedKategori, which I have mapped in my MS SQL database like this:
I have implemented the methods KundeInfo.HovedKategoris:
public IEnumerable<KundeInfo> KundeInfos
{
get
{
using (var dc = new DataClassesBSMAKSDataContext())
{
dc.DeferredLoadingEnabled = false;
var kundeInfoHovedKategoris = dc.KundeInfoHovedKategoris.Where(x => x.HovedKategori_Id == Id);
var kundeInfos = dc.KundeInfos.Where(x => kundeInfoHovedKategoris.Any(y => y.KundeInfo_Id == x.Id));
return kundeInfos.ToList();
}
}
}
... and HovedKategori.KundeInfos:
public IEnumerable<HovedKategori> HovedKategoris
{
get
{
using (var dc = new DataClassesBSMAKSDataContext())
{
var kundeInfoHovedKategoris = dc.KundeInfoHovedKategoris.Where(x => x.KundeInfo_Id == Id);
var hovedKategoris = dc.HovedKategoris.Where(x => kundeInfoHovedKategoris.Any(y => y.HovedKategori_Id == x.Id));
return hovedKategoris.ToList();
}
}
}
This retrieves the associated KundeInfos from a specific HovedKategori and opposite. The problemhowever lies in the serialization. When I call ToList(), or serialize these objects to JSON, linq will try to first follow all references returned by HovedKategori.KundeInfos, if it were that method I were to call first, and then it would for each returned object, try to follow all references returned by KundeInfo.HovedKategoris and so on, until it would cast a stack overflow exception.
If I could somehow prevent linq from following certain properties with an [Ignore] attribute or something, it would work, but I haven't been able to find anything like that.
What can I do in this situation?
This is in part a design issue. What you should really ask yourself is if you need navigation properties in every possible direction. For example if you just add a Kategori ID instead of a navigation property you could still query your context (by using the ID) but do you really need to always get all the Kategori's with all underlying data?
also if you make your properties virtual you have lazy loading and will only get the information if you .Include it or explicitly reference it.
Ok - So I solved this problem by just making it into methods on the respective classes, which it should be in the first place, since it retrieved these entities from the database. So yes, partially design issue.
Virtual did not work, I considered using projection and an abstract class, but it would be such a haystack of inheritance, and class casts, that it would not even be worth considering.
public IEnumerable<KundeInfo> KundeInfos()
{
using (var dc = new DataClassesBSMAKSDataContext())
{
var kundeInfoHovedKategoris = dc.KundeInfoHovedKategoris.Where(x => x.HovedKategori_Id == Id);
var kundeInfos = dc.KundeInfos.Where(x => kundeInfoHovedKategoris.Any(y => y.KundeInfo_Id == x.Id));
return kundeInfos.ToList();
}
}

cakephp - using create() not working as expected

I have a product controller and when I'm saving a new product I want to save some records to another related controller to make a record of what categories the product is associated with.
My code I'm using is:
$this->Product->create();
if ($this->Product->save($this->request->data)) {
$newProductId = $this->Product->getInsertID();
//associate products with categories
foreach($categoriesToSave as $key=>$value) {
$saveArray['CategoriesProduct'] = array('category_id'=>$value, 'product_id'=>$newProductId);
$this->Product->CategoriesProduct->create();
$this->Product->CategoriesProduct->save($saveArray);
$this->Product->CategoriesProduct->clear();
}
}
For some reason though, even if $categoriesToSave has 10 items in it, only the very last one is being saved. So it's obviuosly creating only the one new CategoriesProduct item and saving each record over the top of the last instead of create()-ing a new one.
Can anyone explain what I'm doing wrong and how I can make this work?
The way I would do it would be like this:
//Add a counter
$c = 0
foreach($categoriesToSave as $key=>$value) {
$saveArray[$c]['CategoriesProduct']['category_id'] = $value;
$saveArray[$c]['CategoriesProduct']['product_id'] = $newProductId;
$c++;
}
$this->Product->CategoriesProduct->saveMany($saveArray);
I don't think that is the only way to do it but that should work just fine.
Good luck!

Resources