I have a Entity in my database (say Member) which has many relationships with other tables (6 relationships to be exact). Some of them I don't want mapped with the ORM (I mean linked to this Entity) because they may have many records (like MemberAccessLogs for example) and some other load many other entities.
Now I want this Member Entity to have an isDeletable method so I can disable exclude button in administration page.
If I where to do this the traditional way, I would have to declare the associations with all the other tables in the entity class, including MemberAccessLogs and I would put the method in it so I could test if these associations are empty.
But AFAIU, I would have to make a fetch (or at least a count) to the association's tables in order check for empty.
Another way would be to fetch the Members I want shown and then make a separate query to check for empty with a low cost exists(select * from table limit 1) in these sub-tables and then populate the isDeletable method in Member programmatically before pass it to Twig.
But I found this solution cumbersome. Anyone has a better way to do this ?
Just for the record: Some people may think this is "premature optimization". I maintain (contrary to some), that you should think ahead when you are programming and don't this this is bad. But I really think this isn't the place to discuss it. Please let's focus on the question asked ok ? :)
Edit
To easily prove that limit 1 is increadibly faster than count, I did a small test in a table in my database that has more than 20 million lines. Here are the results:
select count(*) from loga [20 million+ table]
20678473
1 row(s) fetched - 27023ms
select exists(select null from loga limit 1)
true
1 row(s) fetched - 2ms
I guess 13511,5 times faster is conclusive enough. :D
Extra lazy
You could look into extra-lazy associations.
Basically you map all associations as you normally would, and add fetch="EXTRA_LAZY":
/**
* #Entity
*/
class CmsGroup
{
/**
* #ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
*/
public $users;
}
Now Doctrine will not load the complete collection into memory the first time it's accessed, but performs specialized queries to load the parts you actually need at that moment.
So $users->count() (or count($users)) on the collection would trigger a simple count-query in stead of loading the complete collection into memory.
PostLoad
You could use an postLoad event to determine if such an entity is deletable. This postLoad event is called after an entity is constructed by the EntityManager, so when the entity is loaded.
Add an unmapped property ($isDeletable) to the entity that stores whether the entity can be deleted or not.
Create an entity listener that listens to the postLoad event. The listener can have the EntityManager, DBAL Connection, or anything else injected. With that dependency you could perform whatever query you want and use the result to set $isDeletable.
The result is a single additional query when the entity is loaded, after which the entity "knows" whether it's deletable or not.
An example of using the postLoad event can found in a Cookbook entry on the Strategy Pattern
Do note that when the conditions that determine whether it's deletable or not change, the value of $isDeletable could become incorrect. To resolve this issue, you could keep track of those conditions:
Keep track
Add a mapped property ($isDeletable) to the entity that stores whether the entity can be deleted or not. It would probably start with true.
When something is added to an association which would mean that the entity is no longer deletable, set $isDeletable to false.
When something is removed from an association that which would mean that the entity is deletable again, set $isDeletable to true.
In other words: with every change you keep track of whether the entity is deletable or not.
This way you won't need any additional queries at all.
There's a Cookbook entry on aggregate fields that explains this concept very well.
Related
I have 2 Doctrine Entities with many-to-many relations. When I edit the first entity I want to be able to select the checkboxes that have the data from the 2nd entity to establish the joins for particular entry.
It works fine on creating a new Entry (using Array Collection), but when I want to edit an Entry - it adds the ones that I have selected without removing the previous choice (unchecking).
Which way would be the correct way to do that and how?
Remove all the Join table data for the Entry that is being updated,
then set the new data. (How can I remove it from the join table that
is not an Entity?)
Pass all the data from the 2nd Entity and remove
those that aren't checked (seems super-clumsy?)
Some other way I am not aware of?
I am not using Symfony, just Doctrine.
Doctrine makes working with the many-to-many associations quite easy. Your associations are stored into an ArrayCollection class that has some methods that can help you. First of all, check all the available methods for the ArrayCollection here (Doctrine API - ArrayCollection)
In your case, I'd use this approach: use the clear method on your ArrayCollection that contains the relationship with the 2nd entity and populate it again with the checked elements. After this, call the flush method on the entitymanager.
Another approach consists in filtering your collection (with the filter method) for getting a brand new ArrayCollection that contains only the elements that are checked. Like the first approach, associate this new collection to the relationship's ArrayCollection and call the flush method on the entitymanager.
Using Doctrine 2 and Symfony 2.7 I want to use an automated count for a column in my db.
Example:
So when I update a report, I want to add the user (which is the parent of the report by a OneToMany relation) to the leaderboards with the column completed set to 1 (setCompleted). When the user was already on the leaderboards, I want to find him and add 1 to the completed tasks value.
if (!$lb) {
$new = New Leaderboard();
$new->setUsers($user)
->setCompleted('1');
$em->persist($new);
} else {
$update = $em->getRepository('AppBundle:Leaderboard')->findBy(array('user' => $user));
$update->setCompleted('2');
}
So basically I want to automate the $update->setCompleted('2'); so that it takes the current value and adds one to that and then flush that to the database.
I hope this makes any sense? No sure how to explain it or search for it online...
You could use an onFlush listener to watch your Report entity for updates and increment/create the leaderboard entry depending on the state of the Report.
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/events.html#onflush
Requires a bit of (careful) reading, but it will do what you want.
Here's a couple of questions I answered on more or less the same subject with examples that should get you started pretty quick
Track field changes on Doctrine entity
Persisting other entities inside preUpdate of Doctrine Entity Listener
The basic flow you want is to capture updates to Report (the simpler second example under my answer shows this being done for an entity). Then based on the state of Report, you create a new Leaderboard entity and attach it to Report, OR update the existing Leaderboard entity for that report.
Note it's important to not flush the entity, just add it to the UOW like so
$this->getEntityManager()->persist($entity);
$metaData = $this->getEntityManager()->getClassMetadata($className);
$this->getUnitOfWork()->computeChangeSet($metaData, $entity);
Hope that helps!
I've been thrown a 'can it be done in Doctrine' type question by my supervisor, and while I've been able to do some of it with external 'Mapper' objects, I'm wondering if there might be any way to fake-out the ORM to do it within Doctrine itself.
Essentially, the thing we are interested in doing is cutting down database clutter. We have a number of tables containing distinct properties or item sets in various categories and then a whole bunch of link-tables tying them to other properties or item sets. For example
We have a couple of tables such as 'Materials' and 'PaperTypes' which describe various Material and Paper options for our products. We then group these into 'MaterialCollections' and 'PaperFamilies' respectively. Then there has to be a one-to-many link table between MaterialCollection/Materials and PaperFamilies/PaperTypes respectively. Repeat these types of relationships a couple dozen more times and you see where our DB is starting to clutter up.
The link tables themselves are nothing more than a number of entries including multiple records with the PrimaryId from the parent table (collections/families) and unique-per-parent PrimaryId's from the sub-table (materials/papertypes). Link tables could be named something like MaterialCollectionsMaterials and PaperFamilyPaperTypes for example.
The idea was to get rid of this slew of link tables by using an abstract 3-table structure as follows:
Lists (consisting of a unique ListId, a ListName and a TypeId)
Types (consisting of a unique TypeId, a TypeName and an EntityName)
ListXref (consisting of ListIds and memberId which points to the primaryId from the Entity designated in the list type)
Multiple lists can exist for a given type, but the Xref pairs are unique. Different Types can also point to the same Entity. (e.g. there may be more than one type of Material list defined)
The 'ListName' would be the equivalent of the parent TableNames above and would allow for eliminating two of the tables in those relationships. So the records in 'MaterialCollections' would now instead be records in the List table of TypeName "MaterialCollection". The records that would have been in a link table (such as MaterialCollectionsMaterials) would now instead be pointed to from ListXRef.
As stated, I've gotten a basic mapper to make this work for rather basic list creation. But what I'm wondering is if there is any way to create Entities or things that behave like Doctrine Entities to establish the abstract relationships between a given list/listtype and the table referenced by EntityName and the corresponding memberIds?
In other words, it would be really nice if I had some means by which I could produce something that had the bulk of functionality of a Doctrine Entity which could be retrieved from the Service Manager or something like it that would behave (have similar properties/functions) like a Doctrine Entity.
I tried adding a wrapper object in the Entity tree that my mappers could try to retrieve which was basically an inherited version of the Xref entity with a few alias functions, but it can't be retrieved with the Entity manager.
Any help is appreciated.
P.S. While it is not a priority at the moment, longer term I also want to really throw a wrench into the works by trying to have some lists be capable of pointing back to the records produced by other lists. So, for example, a List "ProductXMaterials" of type "ProductMaterials" might point to some but not all results of "MaterialCollections". But I'll worry about this one later.
Well, Doctrine can't generate "pseudo entity classes" but you can.
First you need to hook into the Doctrine "loadClassMetadata" event.
Inside, you generate your entity classes and configuration from code on the fly and dump them into a specified directory (in the cache for example) maintaining the doctrine convention naming but with a custom namespace. For example : "AutoGeneratedNamespace\Entities"
So let's say you need to have a dynamic entity generated for an entity with name: "TmpUser", and you're using "yml" instead of annotations
<?php
namespace Example;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class DoctrineEventListener
{
/**
* Invoked on Doctrine loadClassMetadata event
*
* #var Doctrine\ORM\Event\LoadClassMetadataEventArgs
**/
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
{
$em = $args->getEntityManager();
$metadata = $args->getClassMetadata();
$factory = $em->getMetadataFactory();
$name = 'AutoGeneratedNamespace\Entities\TmpUser';
$tmpDirectory = '/path/to/cache/generated-classes';
// current class metadata is the same as the one we need. this means we already generated it
if($metadata->getName() == $name || $factory->hasClassMetadata($name)) {
return;
}
/**
Generate your class here and dump it into the cache directory with the name: TmpUser.php
(If you're using "yml" instead of annotations also create the TmpUser.orm.yml configuration and dump it in the same directory)
**/
// create a metadata for the newly created class
$metadata = $factory->newClassMetadataInstance($name);
// Register metadata so doctrine knows about it
$factory->setMetadataFor($name, $metadata);
}
}
Finally, you tell Doctrine to look for entities inside the cache directory as well using the configuration: (This is taken from the Symfony documentation so if you're not using Symfony it may vary a bit but you can find the precise configuration in the Doctrine docs)
doctrine:
orm:
mappings:
MyGeneratedEntitiesUniqueMappingsKey:
type: yml
dir: '/path/to/cache/generated-classes'
is_bundle: false
prefix: "AutoGeneratedNamespace\Entities"
alias: GeneratedEntities
I have a bunch of DNS records in a table, where the "type" column defines the type of the given record.
I would like to have a class hierarchy of models in Lithium representing these records such as:
RR - base resource record (abstract - sort of)
RR_SOA - SOA record, extends RR
RR_CNAME - CNAME record, extends RR
... etc ...
So one class for each record, all mapping to the same table.
Next I would like to use RR::find which would automagically give me a list of objects where the class of each object corresponds to the type of the actual record.
Is this even possible without too much black magic?
Yes (anything is possible) but you're approach may be too ambitious. In my POV, I would depend on #class RR to handle the basic querying of the target table, but by utilizing filters on RR (specifically on the find method inherited from Model), you can pass-by-reference the instance to sets of newly instantiated classes (SOA, CNAME, etc...) along with their position in the table (so SOA object is only associated with that specific record or primary key).
There is some black magic going on, but nothing the Lithium core developers didn't account for.
Bottom line, 1 base class for your table (RR model), multiple (possibly inherited from another base unrelated to Model) for SOA, CNAME, etc..., and a filter (put anywhere really) to intervene RR::find and/or the late binded RR::findby*
Does this make sense?
(This will require some trial and error. First to see if you can manipulate the data output, after that the rest is butter)
Is there a way to have something like this in doctrine:
class Entity {
/**
* #Column(name="related_entity_id")
*/
private $relatedEntityId;
/**
* #ManyToOne(targetEntity="RelatedEntitiy")
* #JoinColumn(name="related_entity_id", referencedColumnName="id")
*/
private $relatedEntity;
}
What I want to do I do something like this:
call Entity::setRelatedEntityId($someId), and persist the entity,
and have the entity return the related entity by calling Entity::getRelatedEntity().
The related entity is selected from a table which will be strictly limited and it will never dynamically grow at runtime, so there is a finite number of related entity ids.
At the time of creating a new Entity, I'd like to set the related entity id, but without having to fetch the whole related entity from the database.
As far as I could test this, it does not work, because if I set the relatedEntityId but not the relatedEntity, Doctrine automatically sets the related_entity_id column to null, since basically no relationship has been established.
I've tried to do something like this also:
remove the relatedEntityId property, and use
Entity::setRelatedEntity(new RelatedEntity($relEntId))
the constructor of the RelatedEntity will set the id, but not other values.
I do not want to persist the RelatedEntity (it's values are already set in the DB for the given $relEntId), but this time Doctrine signals an error at flush, because it has an unpersisted entity.
Basically, what I want to do is create a relationship without knowing anyhing but the Id of the related entity. If there is some other way this can be done, please share.
Thanks in advance
EDIT:
I've found a workaround. Since the RelatedEntities will be a limited set of immutable objects, I've done the following:
use the entityManager to find all RelatedEntities;
inject the list to the object that will be creating new Entities
when creating a new Entity, select one of the RelatedEntities from the list as its RelatedEntity
I'll leave the question open for a day or two, just in case somebody comes up with something better.
Use the entity proxy:
Entity::setRelatedEntity($entityManager->getReference('RelatedEntity', $relEntId))
I don't think this is supposed to work like how you described :)
The entity you add must be a Doctrine managed object, so that means you have to load it yourself first using the find() family of methods.
Based on my experience with Doctrine 2 further elaborated here http://ssmusoke.wordpress.com/2012/03/25/doctrine2-day-3-proxies-associations-relationships/
My approach is as follows:
a) Have only the $relatedEntity property
b) Add a getRelatedEntityId() function which returns the id value from $relatedEntity
c) Add a setRelatedEntityId() which sets the $relatedEntity object - you may need to load it from the database, saves you from polluting other layers when u only have the id of the related entity
d) Add getRelatedEntity() and setRelatedEntity() functions
BOTTOM LINE: You cannot have a property for the foreign key column and the mapped property as Doctrine gets confused