In Drupal 6 you could use code similar to the following one:
function example_user($op, &$edit, &$account, $category = NULL) {
switch($op) {
case 'load':
$account->fb_id ='xyz'
break;
}
}
In Drupal 7, the documentation for hook_user_load() states the following:
Due to the static cache in user_load_multiple() you should not use this hook to modify the user properties returned by the {users} table itself since this may result in unreliable results when loading from cache.
Why do I get users and not just a user?
Is it ok to add properties to this?
http://api.drupal.org/api/drupal/modules--user--user.api.php/function/hook_user_load/7
You get an array of user objects because the hook is called from user_load_multiple(), which generally calls DrupalDefaultEntityController::load(), which then calls DrupalDefaultEntityController::attachLoad().
It is fine to add custom properties, but not to override the default properties that are loaded from the {users} table; as reported from the documentation, in that case you could get some problem when loading the user object from the cache, which is what the entity API normally does.
Related
I am working an Symfony 2.8 based web app project which currently uses Doctrine 2. The project is basically a simple ToDo list application which can be synced with a mobile app (iOS/Android).
While reading the Update notes of Doctrine 3 I discovered, that EntityManager::merge will no longer be supported.
An alternative to EntityManager#merge() is not provided by ORM 3.0,
since the merging semantics should be part of the business domain
rather than the persistence domain of an application. If your
application relies heavily on CRUD-alike interactions and/or PATCH
restful operations, you should look at alternatives such as
JMSSerializer.
I am not sure what is the best/correct way to replace EntityManager::merge?
Where do I use merge:
During the sync of the mobile apps with the web app the data is transferred as serialized JSON which is than de-serialized by JMSSerializer to an entity object. When the web app receives a ToDoEntry object this way, it can be a new ToDo-Entry (not known in the web app yet) or an updated existing entry. Either way, the received object is not managed by the EntityManager. Thus $em->persist($receivedObject) will always try to insert a new object. This will fail (due to the unique constraint of the id) if the ToDo-Entry already exists in the web app and needs to be updated.
Instead $em->merge($receivedObject) is used which automatically checks wether an insert or update is required.
Hot wo solve this?
Of course I could check for every received objects if an entity with the same ID already exists. In this case could load the existing object and update its properties manually. However this would be very cumbersome. The real project of course uses many different entities and each entity type/class would need its own handling to check which properties needs to be updated. Isn't there a better solution?
You can try to use registerManaged() method of Doctrine\ORM\UnitOfWork.
// $this->em <--- Doctrine Entity Manager
// $entity <--- detached Entity (and we know that this entity already exists in DB for example)
$id = [$entity->getId()]; //array
$data = $entity->toArray(); //array
$this->em->getUnitOfWork()->registerManaged($entity, $id, $data);
Of course, You can check the state of Your Entity using getEntityState() of Doctrine\ORM\UnitOfWork before/after perfoming needed actions.
$this->eM->getUnitOfWork()->getEntityState($entity, $assert = 3)
$assert <-- This parameter can be set to improve performance of entity state detection by potentially avoiding a database lookup if the distinction between NEW and DETACHED is either known or does not matter for the caller of the method.
While I have posted this question quite a while ago, it is still quite active. Until now my solution was to stick with Doctrine 2.9 and keep using the merge function. Now I am working on new project which should be Doctrine 3 ready and should thus not use the merge anymore.
My solution is of course specific for my special use case. However, maybe it is also useful for other:
My Solution:
As described in the question I use the merge method to sync deserialized, external entities into the web database where a version of this entity might already exist (UPDATE required) or not (INSERT required).
#Merge Annotation
In my case entities have different properties where some might be relevant for syncing and must be merged while others are only used for (web) internal housekeeping and must not be merged. To tell these properties appart, I have created a custom #Merge annotation:
use Doctrine\Common\Annotations\Annotation;
/**
* #Annotation
* #Target("PROPERTY")
*/
final class SyncMerge { }
This annotation is then be used to mark the entities properties which should be merged:
class ToDoEntry {
/*
* #Merge
*/
protected $date;
/*
* #Merge
*/
protected $title;
// only used internally, no need to merge
protected $someInternalValue;
...
}
Sync + Merge
During the sync process the annotation is used to merge the marked properties into existing entities:
public function mergeDeserialisedEntites(array $deserializedEntities, string $entityClass): void {
foreach ($deserializedEntities as $deserializedEntity) {
$classMergingInfos = $this->getMergingInfos($class);
$existingEntity = $this->entityManager->find($class, $deserializedEntity->getId());
if (null !== $existingEntity) {
// UPDATE existing entity
// ==> Apply all properties marked by the Merge annotation
foreach ($classMergingInfos as $propertyName => $reflectionProperty) {
$deserializedValue = $reflectionProperty->getValue($deserializedEntity);
$reflectionProperty->setValue($existingEntity, $deserializedEntity);
}
// Continue with existing entity to trigger update instead of insert on persist
$deserializedEntity = $existingEntity;
}
// If $existingEntity was used an UPDATE will be triggerd
// or an INSERT instead
$this->entityManager->persist($deserializedEntity);
}
$this->entityManager->flush();
}
private $mergingInfos = [];
private function getMergingInfos($class) {
if (!isset($this->mergingInfos[$class])) {
$reflectionClass = new \ReflectionClass($class);
$classProperties = $reflectionClass->getProperties();
$propertyInfos = [];
// Check which properties are marked by #Merge annotation and save information
foreach ($classProperties as $reflectionProperty) {
$annotation = $this->annotationReader->getPropertyAnnotation($reflectionProperty, Merge::class);
if ($annotation instanceof Merge) {
$reflectionProperty->setAccessible(true);
$propertyInfos[$reflectionProperty->getName()] = $reflectionProperty;
}
}
$this->mergingInfos[$class] = $propertyInfos;
}
return $this->mergingInfos[$class];
}
That's it. If new properties are added to an entity I have only to decide whether it should be merged or not and add the annotation if needed. No need to update the sync code.
Actually the code to handle this can be just a few lines. In background Doctrine will issue a query to search for your entity if not already in memory, so you can do the same by doing the query yourself with result cache enabled, and then just use PropertyAccessor to map the data.
https://symfony.com/doc/current/components/property_access.html
See this gist for a POC https://gist.github.com/stevro/99060106bbe54d64d3fbcf9a61e6a273
I have an entity called foo which has an OneToMany association with an entity called bar that is accessible as $foo->getBar() (an ArrayCollection). Normally calling $foo->getBar() would trigger a Lazy Loading of associated bar entities (if they weren't joined originally).
How can I check if bar has been loaded without triggering a Lazy Load? I don't need the associated entities, if they weren't loaded originally, and I don't want them to load, I just want to know IF they were loaded.
Example
In the fooRepository I have a method called getFooWithBar() and that has a join which loads all the bars as an ArrayCollection and returns foo with all the associated bar entities. But if I just call a simpler method like getFooById() with a simple query, the bar entities were not loaded with a join, so they are not contained in $foo.
So in another controller I have $foo, and I want to check if getBar() has associated entities loaded yet, but I do not want to trigger the Lazy Loading. If it doesn't have associated entities, I don't want them. I just need to know IF they have been loaded.
NOTE: I also do not want to turn off Lazy Loading on the entity association for all instances.
Method that Doesn't Work for Inverse side of OneToMany
I put this magic getter method in my entity:
public function __get($property) {
return isset($this->$property) ? $this->$property : null;
}
Which theoretically lets me check if the property is set (or if it's still the default private declaration). And this works when my entity is the owning side. But if it's the inverse side, $this->property is never set. Doctrine does some fancy stuff so that when you do getProperty() it's looking at the data somewhere else. I figured this out because this function works when it's the owning side (it returns a proxy of the associated entity), but it returns null when the associated entity is owned by the other entity.
After years of testing our code (responding to Doctrine changes) the following is the best solution we could come up with to check if an association has been loaded, WITHOUT trigger LazyLoad. None of this stuff is documented in Doctrine (unfortunately), so you have to look at the source code and/or play with the code.
The Solution
In the end there are many different types of different associations that could be loaded from *ToMany (PersistentCollection) or *ToOne associations (Proxy or direct entity). This means we need to create a method that checks for all the possibilities (that we are currently aware of in our app). We created a trait that we add to all our entities, so we can call $entity->isLoaded($propertyName) to check if it's loaded.
public function isLoaded($property)
{
// *ToMany Association are PersistentCollection and will have the isInitialized property as true if it's loaded
if ($this->{$property} instanceof PersistentCollection) {
return $this->{$property}->isInitialized();
}
// *ToOne Associations are (sometimes) Proxy and will be marked as __isInitialized() when they are loaded
if ($this->{$property} instanceof Proxy) {
return $this->{$property}->__isInitialized();
}
// NOTE: Doctrine Associations will not be ArrayCollections. And they don't implement isInitalized so we really
// can tell with certainty whether it's initialized or loaded. But if you join entities manually and want to check
// you will need to set an internal mapper that records when you've loaded them. You could return true if count > 0
if ($this->{$property} instanceof ArrayCollection) {
// NOTE: __isLoaded[$property] is an internal property we record on the Setter of special properties we know are ArrayCollections
return (!empty($this->__isLoaded[$property]) || $this->{$property}->count() > 0);
}
// NOTE: there are never any Collections that aren't ArrayCollection or PersistentCollection (and it does no good to check because they won't have isInitialized() on them anyway
// If it's an object after the checks above, we know it's not NULL and thus it is "probably" loaded because we know it's not a Proxy, PersistentCollection or ArrayCollection
if (is_object($this->{$property})) {
return true;
}
// If it's not null, return true, otherwise false. A null regular property could return false, but it's not an Entity or Collection so indeed it is not loaded.
return !is_null($this->{$property});
}
When you load your foo object, bar will be an instance of Doctrine\ORM\PersistentCollection. You can call the isInitialized() method on this collection to find out if has been initialized.
For Associations that are an ArrayCollection:
$initialized = $foo->getBar()->isInitialized();
If you have newest version of Doctrine, you can try extra lazy load on column.
More Extra lazy associations
I've got dataobjects called attribute and attribute set
The attribute set has this many_many relation to attribute
private static $many_many = array(
'Attributes' => 'Attribute'
);
on attribute I've got this
private static $belongs_many_many = array(
'Sets' => 'AttributeSet'
);
You can add attributes to an set either directly from the set or on the attribute.
Now I need to know when a new attribute is added to a set, to update another content afterwards. I tried it with
public function onBeforeWrite(){
parent::onBeforeWrite();
if( $this->isChanged('Attributes') ){
$this->Title = 'test';
}
}
on the attribute set, but like presumed, it doesn't work, because the set get's not written if a new attribute is added.
Is there a way to do this?
Thank you in advance
You can serialize in some way like json_encode the ManyManyList and store it in a private variable during the init stage, then you can deserialize it during the onBeforeWrite and check for differences.
It's not an efficient task, but I think it's the only way you have to achieve your goal.
Couldn't you do something like this?
public function onBeforeWrite(){
parent::onBeforeWrite();
foreach($this->Attributes() as $attribute) {
if($attribute->isChanged()) {
$this->Title = 'test';
break;
}
}
}
Update: I now realise that this will not work for objects that are deleted. Maybe it is an option to do things the other way around. So do an onBeforeDelete on the many_many objects that sets the field in the "parent(s)" and then saves it. You could even do this for onbeforeWrite as well...
update 2:
It is a little unclear what you want. Do you want to know if the many_many objects have changed, regardless of when this happens, or do you just want to know if they change during the current page load?
isChanged only works when you load the object from the database, and then change something during the same cycle. The remainder of the current execution cycle, isChanged will return true. The next cycle, the object is reloaded, and isChanged returns back to false.
If you want to know if something changed since the last time you opened the parent object, you should store it in the database itself, or in the parent object (also in the db). This is quite easy, by just changing the parent object(s) with a boolean flag, and then saving again. If you want to track changes you need to implement something like #g4b0 suggests, or maybe try to add versioning to your objects. But the latter would probably force you to do a lot of custom coding.
I have two tables with a Many-To-Many relationship like this:
User(emailaddress, Name)
UserAlerts(emailaddress, AlertId)
Alert(AlertId,Title)
Alerts have already been added to the database. When inserting a new user, I am doing a lookup on the AlertRepository. The problem is, Instead of creating a record in the User and the UsertAlerts tables only, its also adding an extra Alert record.
I am using the following code:
public ActionResult Register(UserModel model, int[] Alerts)
User user = new MidTier.Models.User();
user.Name = model.Name;
user.EmailAddress = model.EmailAddress;
if (Alerts!=null)
{
IRepository<Alert> alertRepository = new AlertRepository();
foreach (int alertId in Alerts)
{
Alert alert = alertRepository.First(a=>a.ID== alertId);
alertRepository.Detach(alert);
if (alert != null)
{
alert.Enabled = true;
user.Alerts.Add(alert);
}
}
}
userRepository.Attach(user);
userRepository.Add(user);
userRepository.Save();
Why don't you try to search little bit before you ask a question? This problem is asked several times per week. In your previous question I said you that you should use same context for loading Alert and storing User. You didn't do it and complicated whole situation.
The context doesn't know anything about existence of the alert. Once you call Add for user it will add all entities which are not tracked yet. There are three ways to solve this:
Use the same context in both repositories and do not detach alerts. Because of loading alerts, context will know about their existence and doesn't insert them again.
If you don't use the same context for loading you must attach the Alert to the new context before you add it to User. That is hard to do when you wrap EF code to repositories.
If you don't use the same context and you will not attach Alert to the new context before you add it to User you must modify your Add method for User and after adding User to the context you must iterate every alert and change its state to Unchanged.
I am doing some access to content using hook_form_alter as there are problems using hook_access with content types defined outside of your module. If a user does not have access I am setting a message at the top of the page and I don't want to output the form. I have the following code.
function mymodule_form_alter(&$form, $form_state, $form_id) {
switch ($form_id) {
case 'cmyformm':
dsm($form);
$from = null;
dsm($form);
drupal_set_message('You do not have access to this page');
break;
}
}
The dsm however is returning the same information for $form even if I set it to null. How do I not display the form but a message?
I don't quite understand the question, but for starters you are setting $fROm to null and not $fORm. Setting for to null or just doing unset($form); should do the trick.
Access control shouldn't be done at the form level. Access control should be implemented with hook_access or in the router's access arguments. If you must do this at the form level, use user_access() along with the defined roles that you have.
In addition to the typo as pointed out by #zeroFIG, you are really doing this access check in the wrong way. I have used the node_example module with sucess on all node types - none were defined in the same module as the hook_access. Have a look here: http://api.drupal.org/api/examples/node_example--node_example.module/6