I am using DDD now for quiet some time, it took some time to get used to and separate everything. But am now stuck at updating my entity ...
Setup
Now I have a repo interface that defines the following methods
/**
* #param AccountEntity $account
* #return AccountEntity
*/
public function create(AccountEntity $account);
/**
* #param AccountEntity $account
* #return mixed
*/
public function update(AccountEntity $account);
My repo does exactly 0 in the function because it passes it to the mapper, which in turn creates/updates the data. So far so good.
The application service has the method create which accepts an array, the array is validated and if valid it will use an EntityBuilder to create the entity. (Entity requires the data by __construct). If the data is invalid it will throw an exception.
Now my issue is how to handle my update in the application service.
I get an array of data, just like the create, in the update and an id of the entity.
Should I use an hydrator to loop over the array and update the fields in the entity and then throw it to my repo.
cons, I then have an entity hydrator and builder.
pros, it is easy in use
Should I just validate the data and throw the array + id to the repo and let him figure out what to do
cons, just looks wrong
pros, again easy to make
Should I do toArray or something similar on my entity, merge that with the form data & then use the entity builder to build the entity again and throw the entity then to the repo
cons, don't think an entity should have a toArray function
pros, sounds like it encapsulates the task of building the entity in one place so that seems right.
?
So in short, how to convert an array with data to an entity, or how to save it. I am not using Doctrine in any way, and not planning to
First of all, your input data (array) should be validated somewhere at the controller level, to ensure is in the correct format (we're not talking about business rules, just formatting).
Then your entity can have something like this
class MyEntity
{
public function update($data)
{
//update properties, enforce the relevant business rules
//perhaps events are generated
}
}
The controller will probably use a service method to do the updating. The service will ask the repository for the entity, eventually creates the input format the entity expects it (if there is a difference) then calls the update method .
Then you send the entity to the repository which takes care of persisting it. Remember that the Repository is there to save/restore your entities not to change them.
This should probably have been a comment, but that requires 50 rep...
You should have a look at this article about datamappers: http://www.sitepoint.com/integrating-the-data-mappers/
I have been exactly in your situation, and the articles by that writer (Alejandro Gervasio) helped me immensely.
Related
I noticed some weird things using Doctrine ORM's ArrayCollection in a PostgreSQL database (using this in a Symfony 3 project).
Take my User class with roles, It's initiated with a default role ROLE_USER and must be of type array.
class User implements UserInterface, Serializable
{
/**
* #var ArrayCollection
*
* #ORM\Column(name="roles", type="array")
*/
private $roles;
public function __construct()
{
$this->roles = new ArrayCollection();
$this->roles->add('ROLE_USER');
}
}
This is stored in the database as follows.
O:43:"Doctrine\Common\Collections\ArrayCollection":1:{s:53:"
Which will give a Serialization error when trying to logon as a User because clearly part of the ArrayCollection is missing in the database.
After googling for a solution I came across this Github issue. As I understand it, it's a bug and you can't use the ArrayCollection in a PostgreSQL environment.
Not defining the type in the #ORM\Column tag doesn't help either, it then stores the following in the database and Symfony can't work with it to retrieve roles. Doctrine\Common\Collections\ArrayCollection#000000002300d10300000000545301a6
And using simple_array or json as type doesn't return an object so both are no-go's
Does anyone have a workaround or solution for this? I'm really starting to regret switching from MySQL to PostgreSQL
NOTE: the code was working fine in MySQL, I just switched databases.
You can always create your own mapping-type specially for ArrayCollection properties. The Doctrine DBAL docs and ORM cookbook describe how to create one. The Symfony documentation describes how to activate/register it.
You could for example extract the array and json-encode it when converting to a DB value:
json_encode($value->toArray())
And json-decode and wrap it back into an ArrayCollection when converting to a PHP value:
new ArrayCollection(json_decode($value))
But it's your mapping type, you can do whatever you want :)
I also had this problem myself, I'm not sure exactly why postgresql truncates the serialized string, even tho the field is marked as text. My suggestion is that you convert the column type to json_array because postgresql has a native type for that. Else you can also use custom types which
I'm starting a little project with DDD approach. I've created my domain model with Entities and ValueObjects in plain PHP. Entities have references to their associations - in my case, there is an Employee entity with collection of Teams he belongs to, and I keep them in Employee::teams property. Everything is going great, I've created mappings for those entities with associations in YAML, interfaces for repositories to be implemented in the Symfony2 and Doctrine2 layer, etc.
When I fetch Employees from repository (with Doctrine's EntityManager::findAll()) instead of array of Teams I receive PersistentCollection with those teams. It's built on PHP7, and Employee::getTeams() has return type of array so I'm getting the critical exception.
Is there any way to convert it into array with some external listeners (Symfony layer only, to not to mess in domain files) or any other core mechanism?
Thanks.
You get ArrayCollection
http://www.doctrine-project.org/api/common/2.5/class-Doctrine.Common.Collections.ArrayCollection.html
It has toArray() method so you can write
/**
* #return Team[]
*/
public function getTeams(): array
{
return $this->teams->toArray();
}
But I prefer
/**
* #return Team[]|ArrayCollection
*/
public function getTeams()
{
return $this->teams;
}
If you use good IDE like PhpStorm it will understand it correctly to autocomplete it like ArrayCollection and as Team if you make foreach ($employee->getTeams() as $team) {...
ArrayCollection are more powerful than plain arrays. For example you can filter and order them and Doctrine will make optimized SQL so you don't have to load all items
http://docs.doctrine-project.org/en/latest/reference/working-with-associations.html#filtering-collections
Kuba,
That doesn't seem to be a DDD question but a technical question regarding PHP implementation details.
However, please let me jump into your design and try to find a better solution for your domain. You are missing to model the relation between an employee and a team, let's call it employee in team. The way you did you are forcing an AR to reference another AR, saying this way that employee manages a team life cycle. This might not be the case but under a specific scenario another actor might change a status of a team and that could break the Employee invariant over the Team AR. Because of this when you make an AR to manage another AR life cycle then this dependency shouldn't be found through the repository and the root of the aggregate is in the position to now force invariant around the aggregated AR.
There are still some concerns to mention, but just to keep it simple, go ahead and model the concept you are missing: EmployeeInTeam or whatever you want to call it. It references the conceptual identity of the employee and the team so you can remove it safely in a future, you can query employees in a team and teams an employee is part of.
If you need frameworks or the DB to keep the consistence then you are not doing DDD. Use just objects, not the technology.
Regards,
Sebastian.
I have a lot of entities that have ManyToOne and OneToMany associations in Symfony2. As all know if you remove a record and it doesnt set a null value on the association in the other tables, things start to go haywire. So, what is the best way in Symfony2 to handle setting the value as null in other tables when a record is removed in Symfony2?
What do I need to set in my entities to ensure it persists across all associations.
First of all you can manually set ON DELETE behavior for the FK by using onDelete property for the joinColumn annotation in your entity:
/**
* #OneToOne(targetEntity="SomeEntity")
* #JoinColumn(name="some_entity_id", referencedColumnName="id", onDelete="cascade")
*/
private $someEntity;
Secondly, you always can implement event listener for the entity removing event. For example:
public function preRemove(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof SomeEntity) {
return;
}
// Here you can do whatever you want before
// entity record is removed from the DB.
}
You can read about the doctrine event system using following links:
Doctrine events (official manual)
How to Register Doctrine Event Listeners and Subscribers (Symfony2 cookbook)
The difference between them is that the first method works on the database layer. In other words, it would work even if you delete record using raw sql query in the console. Second one is more powerfull because you can also run any php code when entity is removed (for example you can send emails, etc.) but it will only be fired if record is removed via doctrine.
I have in Symfony a Entity of Post. That project has translations, stored in PostTranslation.
Each Post has a slug that is different for each language.
The code looks looks like:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #I18nDoctrine
* #Route("/blog/{slug}")
* #ParamConverter("post", class="SensioBlogBundle:Post")
*/
public function showAction(Post $post)
{
}
But how can I do a findOneBy('slug') kind of search? Because it is stored in the related Entity.
I guess besides slug property your PostTranslation entity stores some sort of language/locale, e.g. locale property, am I right? I also guess that your website is interlocalized based on some parameter stored in URL (different (sub)domain, query string parameter, path prefix - it's irrelevant), right?
There are at least three solutions that solve your problem:
Abandon the use of ParamConverter and do everything by yourself.
Override DoctrineParamConverter from SensionFrameworkExtraBundle and add support for finding by slug and locale for Post class. I would also suggest creating a little bit more robust solution:
Create Translatable interface that defines getTranslations() and setTranslations() methods and apply it to your Post class.
In your CustomDoctrineParamConverter check whether requested class implements this interface, if so, it means that you're going to search entity based on its translation, that is locale and some locale-unique property.
Now you need some way, to get the name of this locale-unique property. You could create another interface for your PostTranslation class that will define a single method, something like getLocaleIdentifier/UniqueProperty that for PostTranslation will return "slug".
If you know, that you need to find a Post property, based on locale and slug property from its translations assocation you can quite easily create DQL query to do that.
ParamConverter annotation has repository_method property that allows you to use custom method from PostRepository which should retrieve Post entity. The only problem is that built-in DoctrineParamConverter will pass only slug parameter to this method. Your repository will have to informed about current locale in some way. This could be achieved in many different ways, here's one of them:
Create LocaleAware interface with a single method setLocale($locale).
Implement that interface in PostRepository
Create event listener that listens for kernel.request event:
Iterate through all Doctrine's repositories.
Check if given repository implements LocaleAware interface.
Inject request's _locale attribute into repository.
The documentation warns a few times about ensuring wakeup and clone implementations on Doctrine 2 entities, e.g.:
http://docs.doctrine-project.org/en/latest/cookbook/implementing-wakeup-or-clone.html
It doesn't say why, however... I presume it has something to do with not messing with Doctrine's Entity Manager and the in-memory-cache it might maintain, but a cursory dive into the (enormous) code base doesn't say much more, nor did a quick search of the users list.
Might anyone knowledgeable with the Doctrine internals know precisely why Doctrine 2 needs safe wakeup and clone methods on entities?
Doctrine 2 needs to convert data fetched from the database into entities (php objects). This process is called hydration.
Doctrine 2 doesn't use the traditional method of using the new operator (thus constructing a class), but it uses the following method:
It builds a string which represents a serialized empty version of your class.
It unserializes that string to make it an actual object. This is why you need to safely implement __wakeup.
It stores that object as prototype.
This process is done once for each entity class.
Then whenever it needs to hydrate an entity, it will clone the prototype. This is why you need to safely implement __clone.
This is all done a small method newInstance() in ClassMetadataInfo.
The advantage of doing this is that the constructor isn't used when a new entity is hydrated. That leaves the developer free to do anything he/she wants in the constructor (including using parameters).
update
The reason why you need to safely implement the __wakeup and __clone methods is this:
Because Doctrine 2 unserializes and clones entities when it needs to hydrate them, those methods will get called. But when Doctrine 2 does this, the entities will not have any data set (not even the identifier(s)). The data will be set afterwards.
So when you implement them in such a way that your logic is only performed when the entity does have the identifier(s), you are sure that your logic is only performed when you unserialize or clone the entity, not when Doctrine 2 does it.
Jasper's answer nails the precise reason, but it doesn't give the background — as in why Doctrine is proceeding to unserialize and clone entities.
The rational for doing so is to allow entities to define whatever constructor they want, as explained in this blog post:
http://www.doctrine-project.org/2010/03/21/doctrine-2-give-me-my-constructor-back.html
At ConFoo 2010 during my presentation, someone asked about the constructor of entities in Doctrine 2 and whether or not it could be used. I think this is something worth writing about since in Doctrine 1 this was not possible. The constructor was hi-jacked from you and used internally by Doctrine.
In Doctrine 2 it is possible to define the constructor in your entity classes and is not required to be a zero argument constructor! That’s right, Doctrine 2 never instantiates the constructor of your entities so you have complete control!
This is possible due to a small trick which is used by two other projects, php-object-freezer and Flow3. The gist of it is we store a prototype class instance that is unserialized from a hand crafted serialized string where the class name is concatenated into the string. The result when we unserialize the string is an instance of the class which is stored as a prototype and cloned everytime we need a new instance during hydration.
Have a look at the method responsible for this:
<?php
public function newInstance()
{
if ($this->_prototype === null) {
$this->_prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($this->name), $this->name));
}
return clone $this->_prototype;
}