Update Doctrine Cache Values in Symfony - php

I have a Symfony Standard Distribution app and use Doctrine's query builder and result cache to speed up database queries. I also assign unique ids to all my caches such as
....
->getQuery()
->useResultCache(true, 2592000, 'single_product_query_id_'.$id)
->getOneOrNullResult();
....
When a product field changes I can delete this specific cache using
....
$em = $this->getDoctrine()->getManager();
$cacheDriver = $em->getConfiguration()->getResultCacheImpl();
$cache = $cacheDriver->fetch('single_product_query_id_'.$id);
if($cache){
$cacheDriver->delete('single_product_query_id_'.$id);
}
....
Deleting the cache obviously make the changes visible instantly but I found this method to be a waste because in my controller I already have the up-to-date data to be persisted in my db using doctrine so I resolved to updating my cache instead of deleting it. This is where my challenge lies because the documentation is not clear on how to do this apart from the code to save the cache which is
....
$cacheDriver->save('single_product_query_id_'.$id, $new_cache, $new_lifetime);
....
So I dug deeper by passing my cache as variable $cache to a twig template and viewing via var dump. It looked like this
....
array (size=2)
'SELECT d0_.id AS id_0, d0_.title AS title_1, d0_.imagepath AS imagepath_2, d0_.description AS description_3,.......................
array (size=1)
0 =>
array (size=57)
'id_0' => string '1' (length=1)
'title_1' => string ''Tasty Thursday'!' (length=17)
'imagepath_2' => string 'main.jpeg' (length=9)
'description_3' => string 'this and every Thursday, buy a 2kg forest cake and get a 1kg fruity forest cake FREE!!!' (length=87)
...........................
I do not know how doctrine generates this multidimensional array, I would like to get this function(s) so that I can properly populate my cache to update.
Currently, I successfully update my cache by var dumping my cache to find out which row I need to update then iterating through the array. But every time I get a new idea and add a new row to an entity I have to repeat this process especially if this entity was joined to product table.
So my question is, how can I manually build a doctrine cache array from query result or better entity?

Related

How to sync multiple values of the same Attribute in Laravel?

I developing an eCommerce ( with Multiple Product Attributes feature ) website using Laravel 5.4. Everything is working fine. But When I try to sync multiple values of the same Attribute in Pivot table. Laravel ignores the duplicate pares. For example, I've an Attribute called "Network" which has 3 values: 2G, 3G, 4G. A mobile supports 3G and 4G network. I want to sync 3G and 4G value in database. Laravel ignores one of them.
Products Table:
ID - Product Name
1 - Test Mobile
Attributes Table
ID - AttributeName
1 - Network
AttributeValues Table
ID - AttributeID - AttributeValue
1 - 1 - 2G
2 - 1 - 3G
3 - 1 - 4G
ProductAttributes Table
ID - AttributeID - ProductID - AttributeValue
1 - 1 - 1 - 3G
1 - 1 - 1 - 4G
I want to store the Product Attributes in "ProductAttributes" table something like that. But Laravel Ignore one of them.
I am saving the data like that:
$product = Product::create([
'name' => 'Test Mobile'
]);
$product->attributes()->sync([
1 => ['AttributeValue' => '3G'],
1 => ['AttributeValue' => '4G']
]);
Any suggestions, Ideas?
I know this is two years late, but I was dealing with the same issue today, and figured I may leave the solution here, in case anyone looks for it in the future. If you use your original code:
$product->attributes()->sync([
1 => ['AttributeValue' => '3G'],
1 => ['AttributeValue' => '4G']
]);
the second item in the array will overwrite the first one, so in the end, there will only be a "4G" entry in the database. This is not really a laravel issue, it is how PHP associative arrays are implemented - you basically cannot have two items in the array on the same index.
There are actually two ways to solve this issue
1) first one is very inefficient, but it is functional. I am leaving it here only for the record, because that was the original way I solved the issue. Instead of your code, you would need something like this
$product->attributes()->sync([]); // empty relation completely
// add first item
$product->attributes()->syncWithoutDetaching([
1 => ['AttributeValue' => '3G'],
]);
// add second item without detaching the first one
$product->attributes()->syncWithoutDetaching([
1 => ['AttributeValue' => '4G'],
]);
this is EXTREMELY inefficient, because it needs one query to delete all existing data from the relation, and then add new items one by one. You could also run the syncWithoutDetaching inside a loop, and overall inefficiency would greatly depend on how many items you need to sync.
2) the first solution was not good enough, so I kept digging and experimenting, and figured out a way how to make this happen. Instead of putting your items on specific index in the array, you can send array without specific indexes given, and put the ID in the array itself. Something like this
$product->attributes()->sync([
['AttributeID' => 1, 'AttributeValue' => '3G'],
['AttributeID' => 1, 'AttributeValue' => '4G']
]);
by doing it this way, you can actually send two items with the same AttributeID to the sync() method, without one overwriting the other one
For now,
$product->attributes()->sync([]);
$product->attributes()->sync([
['AttributeID' => 1, 'AttributeValue' => '3G'],
['AttributeID' => 1, 'AttributeValue' => '4G']
]);
Looking at Becquerel's response of April 5th, response #2, and in reading the source code of the sync() method in /laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php (this is, I think, Laravel 2.4), I do not see (or, cannot identify) code that would support this "array of array" functionality. In fact, here's the source-code of the entire method:
public function sync($ids, $detaching = true)
{
$changes = [
'attached' => [], 'detached' => [], 'updated' => [],
];
if ($ids instanceof Collection) {
$ids = $ids->modelKeys();
}
// First we need to attach any of the associated models that are not currently
// in this joining table. We'll spin through the given IDs, checking to see
// if they exist in the array of current ones, and if not we will insert.
$current = $this->newPivotQuery()->pluck($this->otherKey);
$records = $this->formatSyncList($ids);
$detach = array_diff($current, array_keys($records));
// Next, we will take the differences of the currents and given IDs and detach
// all of the entities that exist in the "current" array but are not in the
// the array of the IDs given to the method which will complete the sync.
if ($detaching && count($detach) > 0) {
$this->detach($detach);
$changes['detached'] = (array) array_map(function ($v) {
return is_numeric($v) ? (int) $v : (string) $v;
}, $detach);
}
// Now we are finally ready to attach the new records. Note that we'll disable
// touching until after the entire operation is complete so we don't fire a
// ton of touch operations until we are totally done syncing the records.
$changes = array_merge(
$changes, $this->attachNew($records, $current, false)
);
if (count($changes['attached']) || count($changes['updated'])) {
$this->touchIfTouching();
}
return $changes;
}
Now, Laravel is full of dependency-injection and other Magick (apparently similar to Perl's notion of "map?"), but I don't see anything here that will do what Becquerel says it will. And, generally speaking, Laravel's documentation really doesn't come out and say what it does do with repeated values in many-to-many relationships, or if it is cognizant of them at all.
I also notice that the implementation of the method as shown above is actually very similar to the "alternative #1" that he cites as "extremely inefficient." It seems to classify the keys into three buckets ... never seeming to allow for repetition, by my reading ... and then to perform insert, update and delete operations as needed. (No SQL "transactions" anywhere, that I can see, which also surprises me very much ... are they "magickally" there somehow?)
I simply can't determine if Laravel, when presented with more than one occurrence of a value in the (set of related records in the) foreign table, does anything sensible like return them as an array.
I've made this very long-winded response in hopes of eliciting further comments. Thanks.
Use the sync method to store/update the data in the Controller using the relationship :
$product-> attribute()->sync($request->input('product_ids', []));
sync() function in Laravel automatically get reads of duplicates. You can force it with
$product->attribute()->syncWithoutDetaching([
1 => ['AttributeValue' => '3G'],
1 => ['AttributeValue' => '4G']
]);
Good luck mate!

Doctrine: keeps dropping the first new ManyToOne relation

I have the following class diagram:
When I try to persist the $blockImage collection on a new ImageContentBlock the first ImageContentBlockImage object (always the one with $order = 1) is dropped from the collection and not persisted. I can trace its existing back until the point I'm actually persisting (and flushing) the object.
The ImageContentBlock => ImageContentBlockImage relation is defined as OneToMany, where the ImageContentBlockImage object as a ManyToOne relation with a JoinColumn. The ImageContentBlockImage => Image relation is a ManyToOne relation. All of these relations use the following cascade options: cascade={"merge", "persist", "detach"}
The objects are temporarily added, managed and edited in serialised string through Redis before they are persisted. The objects are retrieved from Redis correctly and stored back into it again.
I have the following code when I get the objects out of Redis and intent to persist them in the entity manager. The objects need to be merged (attached) after detaching them.
if ($block instanceof ImageContentBlock) {
/**
* #var ImageContentBlock $block
* #var ImageContentBlockImage $blockImage
* #var Image $mergedImage
* #var ArrayCollection $images ;
*/
$blockImages = $block->getBlockImages();
$block->setBlockImages(new ArrayCollection());
foreach ($blockImages as $blockImageUuid => $blockImage) {
// merge the image?
$image = $blockImage->getImage();
$blockImage->setImage(new Image());
$image = $entityManager->merge($image);
$blockImage->setImage($image);
// set the image block
$blockImage->setBlock($block);
if (!is_null($blockImage->id)) {
$blockImage = $entityManager->merge($blockImage);
}
if (is_null($blockImage->id)) {
$entityManager->persist($blockImage);
}
// put it back!
$block->putImageContentBlockImage($blockImage, $blockImageUuid);
}
}
I have read about the limitations of Doctrine and its bad practices. I guess this is one of them? But I do need this setup to work. How can I achieve this?
Edit 1:
I've been digging into this bug even further. I found this issue here on StackOverflow. This dude did debug the EntityManager and the UnitOfWork. When I add 3 ImageContentBlockImage objects to the ImageContentBlock object they are added to the UnitOfWork and marked for persistence. But one, the first one, is never written to the db. Their solution is to persist the temporary data to the database and not to keep in session (Redis in my case). I use a lot of requests that manage these objects, it would slow down the user experience. So how can I enforce this to work?
Edit 2:
When trying to create a new ImageContentBlock object with 3 ImageContentBlockImage objects. The output of the UnitOfWork is;
array (size=3)
'0000000001fdf8f000000000117b20f8' =>
object(stdClass)[1214]
public '__CLASS__' => string 'Pagewize\Domain\Block\ContentBlock\Image\ImageContentBlockImage' (length=63)
public 'block' => string 'Pagewize\Domain\Block\ContentBlock\Image\ImageContentBlock' (length=58)
public 'image' => string 'PagewizeProxy\__CG__\Pagewize\Domain\File\Image' (length=47)
[..]
'0000000001fdf8f200000000117b20f8' =>
object(stdClass)[1255]
public '__CLASS__' => string 'Pagewize\Domain\Block\ContentBlock\Image\ImageContentBlockImage' (length=63)
public 'block' => string 'Pagewize\Domain\Block\ContentBlock\Image\ImageContentBlock' (length=58)
public 'image' => string 'PagewizeProxy\__CG__\Pagewize\Domain\File\Image' (length=47)
[..]
'0000000001fdf8fb00000000117b20f8' =>
object(stdClass)[1257]
public '__CLASS__' => string 'Pagewize\Domain\Block\ContentBlock\Image\ImageContentBlock' (length=58)
public 'blockImages' => string 'Array(3)' (length=8)
I can see there is one ImageContentBlockImage object missing from the UnitOfWork. This is before i call the flush operation, after doing some merge operations and such. I guess i'll have to look into the flow.
Still if someone can point me into the right direction..
Edit 3:
Alright, I have been testing with the JMS Serializer to rule out the php-serialiser problem that Doctrine has. When I debug the application I can see the new ImageContentBlockImage is added with its (unique) Image object. All the ImageContentBlockImage objects have their own Image. After submitting the page that holds the Block objects, and the array is deserialised from Redis I can see that 2 ImageContentBlockImage objects have a reference to the same the Image object. This reference is unique while I'm on the page and I add an image.

How to use the cascade_delete config option in CodeIgniter Datamapper

I use MySQL using InnoDB tables with CodeIgniter Datamapper in my PHP application. Often, the user is given the option of deleting a record through the app by initiating a ->delete function call. When a record has child records (one-to-one or one-to-many), I would also like these records to be deleted along with the parent record, if it is stated by FK constraints in the database.
In this case, I have 2 tables, items and input_lines. I have confirmed that both are using InnoDB. Each item can have many input_lines, so input_lines has a field called item_id, which is set to NULL, indexed, and have FK constraints (ON CASCADE DELETE and ON CASCADE UPDATE). I have set the config element in the DM config file as
$config['cascade_delete'] = FALSE
Because in the documentation it says you should do that if you are using ON UPDATE/DELETE CASCADE. However, when the user initiates the $item->delete() method, only the item is deleted, and the item_id fields on the input_line records associated with the item are set to null.
My models look like this:
class Item extends DataMapper {
public $has_many = array('labour', 'item_type', 'input_line', 'custom_item_type');
...
}
class Input_line extends DataMapper {
public $has_one = array('item');
...
}
I have tried this with cascade_delete = false and true and it won't work. I know the constraints work because deleting the record with MySQL directly works as expected, deleting the child records.
What am I missing? Why is it setting the FK fields to null instead of deleting the record?
EDIT 1:
I decided against my better judgment to debug the delete function in datamapper.php (libraries directory).
I noticed this code in that function:
// Delete all "has many" and "has one" relations for this object first
foreach (array('has_many', 'has_one') as $type)
{
foreach ($this->{$type} as $model => $properties)
{
// do we want cascading delete's?
if ($properties['cascade_delete'])
{
....
So I var_dumped the contents of $properties, and I saw this:
array (size=8)
'class' => string 'labour' (length=6)
'other_field' => string 'item' (length=4)
'join_self_as' => string 'item' (length=4)
'join_other_as' => string 'labour' (length=6)
'join_table' => string '' (length=0)
'reciprocal' => boolean false
'auto_populate' => null
'cascade_delete' => boolean true
It appears the default for when the model doesn't have the property specifically initialized is overriding the config value. This seems like too glaring a mistake so there's definitely something I'm doing wrong somewhere...right? I really, really want to avoid hacking the DM core files...
EDIT 2:
I was thinking maybe the config file wasn't being found, but I checked the logs and there're entries stating that the Datamapper config file was successfully loaded, so that's not the issue.
Doesn't look like anyone has any answers.
My solution was to change the property in the datamapper library $cascade_delete to false, since it's set to true right now. It's unfortunate that I have to resort to hacking the core, but DM won't respect my changes in the config file for cascade_delete so I have no other choice.
If anyone comes across this question and has encountered an issue like this before, please comment.
I have come across the same problem, but finally I just did like this:
$sql = "DELETE FROM EVENT WHERE event_id=".$event_id.";";
$this->db->query ($sql );
In case we set "ON DELETE CASCADE" for the foreign key which refers to event_id, the above SQL works fine, so I just call it directly.

Magento: Proper way to create category objects

I am writing a module that will extract objects, such as websites, group, categories, and products from one Magento instance, serialize their properties and write everything to a text file, to be de-serialized and on another server. These properties are then used to programatically re-create these objects on the new server. The idea is that we will be able to extract all of the objects that make up a Magento web store, and move them to another server. (No, we don't want to move the whole instance to another server. We just want to be able to move a store and it's related objects.)
Obviously, since we are creating categories on a new server, their entity_id's will change. I have worked that part out, as well as making sure that sub-categories have the proper parent id. This project has been mostly straightforward until I tried to recreate category and sub-category object. I am having all manner of problems. The new category objects save t the database. However, sometimes they don't show up in the category tree, sometimes their parent_id's change to 0, sometimes the whole category tree goes away. I been working on this for about a week. I have read that you have to set the 'path' property to the path of the parent before saving. I have read that you have to use the 'move' method to set the category to be a child of it's parent. There is lots of theory, but nobody seems to have an answer.
So my question: How do you create category and sub-category records that actually work, are properly linked to their parent categories, show up in the category tree, and don't beak things?? I have the following attributes from the original source category stored in an array called $aryData().
[entity_id] => 127 //This usually changes on new server
[parent_id] => 1 //Lookup NEW entity_id of parent and use it
[path] => //Not sure how to properly set this. Tried a few things
[position] => 8 //Leave this alone, hope for the best
[children_count] => 0 //Have to zero this out when you create new category object
[name] => Best Test
[url_key] => best-test
[is_active] => 1
[include_in_menu] => 1
And here is generally what I am doing, in a simplified fashion:
$objNewCat = Mage::getModel('catalog/category'); //Create new object to populate
$parent_id = getNewParent($data['name'], $data['url-key']; //Get new parent id by name and URL key. (This works)
$objParentCat = Mage::getModel('catalog/category')->load($parent_id);
$aryData(['parent_id']) = $parent_id; //Update parent ID in data array
$aryData(['children_count']) = 0; //Must set to 0. Updated as children are added
$objNewCat->setData($data); //Set all data parameters from our save array
$objNewCat->setPath($objParentCat->getPath()); //Is this correct? Read you have to do this
$objNewCat->save(); //Save object to populate entity_id field
//--- Now assign object to be child of the parent.
$objCat = Mage::getModel('catalog/category')->load($newCat->getId()); //reloading to set 'current category'
Mage::unregister('category');
Mage::unregister('current_category');
Mage::register('category', $objCat);
Mage::register('current_category', $objCat);
$objCat->move($parent_id);
$objCat->save();
Yes, some of this code is kind of rough. It is a simplified, and I have been trying many things to get it to work. It's very frustrating. Help me Obi-Wan Knobi. Your my only hope.
Don!, I have spent many hours researching and struggling with this problem. There are lots of references to the problem on the Internet, but nobody has come up with a reliable solution, until now.
Magento uses an object model to handle all database I/O. This is supposed to insulate you from having to do tedious work, such as updating related fields. For example, if you add a new child category, the Magento object model will automatically update the 'children_count' in the parent record. The Magento object model basically lets you concern yourself with the data that you wish to write, while taking care of the housekeeping for you. However, when the object model does not do what it is supposed to, things really suck. That is the situation with the 'parent_id' field, and the 'path' field. Magento just does not update them the way it should.
The solution is to make the changes that you need to via the object model, save the object, and then FORCE the correct values for 'parent_id' and 'path' via a database query. I have found that the 'parent_id', and 'path' fields seem to be saved properly when the record is first created, but then they get messed up if the object is updated. This code presumes you are updating an existing category object.
$catId = 127; //ID of category record you wish to edit
$objCat = Mage::getModel('catalog/category')->load($catId);
if(!is_object($objCat)) {
throw new Exception("ERROR: Could not find category id: " .$catId);
}
//--- Make sure we have proper parent_id and path values
$parentId = $objCatParent->getId();
$path = $objCatParent->getPath() ."/" .$objCat->getId();
$objCatParent = $objCat->getParentCategory(); //We will need this parent category
$data = array('name' => 'My Cool Category',
'parent_id => $parentId,
'path' => $path;
$objCat->addData($data); //Use 'addData' rather than 'setData'
$objCat->save(); //Save to update record and break parent_id, path fields
//--- Now we get a connection to the database, build a query, and update record
$resource = Mage::getSingleton('core/resource');
$objCon = $resource->getConnection('core_write');
$tableName = "catalog_category_entity"; //Well, it's the name of the Category table
$query = "UPDATE {$table} SET parent_id = {$parentId}, path = '{$path}' "
. " WHERE entity_id = " .$catId;
$objCon->query($query); //Force proper values directly into table
Yes, yes, yes, you have to be very careful when you bypass the object model and make changes directly to database records. However, the Magento object model just does not seem to work here, so we have little choice. And since we are saving the record via the object's 'save()' method before it is doing whatever 'behind the scenes' work it is supposed to do. We are just correcting an error.
I hope this helps you out. This was a sticky problem. I wish there was a better solution, but when they fix the API I will stop playing with the database. And to those of you who would say that answering your own question in the third person is akward, I say "HA!"

How to merge two doctrine entities in the same object

In my database, the user management is divided into two tables:
- One created by the symfony sfDoctrineGuard plugin (sfGuardUser), with the username, the password and other information used by the plugin
- another one that I created to extend this table with more properties such as the firstname, surname etc...
What I want to do is to gather all the properties of the two tables in a same object to display all the information related to any member on a specific page.
In that purpose I did a join of the two tables like this:
$q = $this->createQuery()
->from('sfGuardUser u')
->leftJoin('u.Mishmember m WITH u.id = ?', $rel['member_id']);
$member = $q->fetchOne();
My problem is that the generated query seems correct since it selects all the attributes of both tables, but in the $member variable, I can only access the properties of the sfGuardUser object.
I want the $member object to encapsulate all the properties of both tables/doctrine objects.
How would you do that?
EDIT1: if I do a
print_r($member->toArray())
after the previous code I get a 2 dimensional array containing all the properties of sfGuardUser in a first dimension and the properties of my second table in a second dimension. To be clear the result of print_r is like:
Array (
[table1-prop1] => value
[table1-prop2] => value
[table1-prop3] => value
[Table2] => Array (
[table2-prop1] => value
[table2-prop2] => value
[table2-prop3] => value
[table2-prop4] => value
)
)
So to access for example the table2-prop3 property I have to do:
$var[Table2][table2-prop3];
Which is not what I want because I want to consider all the properties as part as the sameobjet or array (as if there were only one table.)
If you're still with me the above array should look like:
Array (
[table1-prop1] => value
[table1-prop2] => value
[table1-prop3] => value
[table2-prop1] => value
[table2-prop2] => value
[table2-prop3] => value
[table2-prop4] => value
)
)
Hope that helps to understand my problem.
EDIT2 (answer to DuoSRX and DrColossos)
Thank you both for your interesting answers.
Well, the reason why I'd rather not to have a Mishmember property in my sfGuardUser is that both tables/classes stand for the same entity(the user). Though this fragmentation is inevitable in my database (it wouldn't be wise to edit directly the sfGuardPlugin to add my properties), I'd like that the code of the application would be as if I had one table, because that would be much more sensible and logical to use (imagine that another developer who doesn't know the model would have to work on the controller or the templates...)
What would you think of adding a Doctrine class User that inherits from sfGuardUser and Mishmember (is there multiple inheritance in PHP5?) so that my controller would have only one user class to deal with?
It would make much more sense to me to ask all the attributes of any user without bothering to find out in which table they are stored.
I'm not sure about how doctrine inheritance works but it seem the neatest solution to me (please tell me if I'm wrong!)
I'm not sure I've been clear enough so please don't hesitate to ask anything.
If you don't need objects but just arrays you can do :
$result = $query->fetchArray();
$mishmember = $result['Mishmember'];
unset($result['Mishmember']);
$user = array_merge($result, $mishmember);
That should do the trick, but I think this is too complex. What is the problem with having an multi-dimensional array ?
Edit:
Well then you could use simple or column_aggregation inheritance :
Mishmember:
inheritance:
type: simple (or column_aggregation)
extends: sfGuardUser
columns:
myfield:
type:integer
And then :
$mishmember = Doctrine::getTable('Mishmember')->find(1);
echo $mishmember->myfield;
See the doctrine documentation for more about inheritance.
This is not really how ORMs work: As you noted, you get a sfGuardUser Object/Array back. Now the Mishmember is nothing more than a property to sfGuardUser (it's an coincindent, that is is an object iteself). So instead of having just a e.g. string property, you have an object/array property.
edit: Of yourse you can merge/combine/re-create the array/object (as the other answer suggests), if you don't like the multi-dimensional aspect. But keep in mind, that these operations can get pretty complex when you have larger amounts of returned data.

Categories