With Symfony 2.2 and Doctrine 2.2.* I can't save manyToMany relations with cascade: persist - symfony

I try to use Doctrine casecade feature tu automagicaly save relations between two entities, and it does'nt seem to work.
I've made a demo here : https://github.com/asakurayoh/demo_bug_doctrine
So I use the doctrine fixture to make my demo.
you need to create de database (app/console doctrine:database:create), migrate the tables (app/console doctrine:migrations:migrate) and then, load the fixtures (app/console doctrine:fixtures:load). The third fixture (src/Demo/MyBundle/DataFixtures/ORM/TagsNewsFixtures.php) is adding all tags entities to all the news. And if you go to the database, you will see that no relation was save in the news_tag table... I think my relation are well defined in my mapping (Resources/config/doctrine/News.orm.yml and Tag.orm.yml) and the cascade property is set.
Someone can find the problem with this code? I search everywhere (stackoverflow too) and I've done everythings everyone said... it should work...
Thanks to save my life (and my entities relations, ha!)
AsakuraYoh

The problem is in fixtures loading order - TagNewsFixtures is loaded first, therefore no tag nor news are in database at that time. Try forcing load order using ordere
namespace Acme\HelloBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
class LoadData extends AbstractFixture implements OrderedFixtureInterface
{
public function load(ObjectManager $manager)
{
// ...
}
public function getOrder()
{
return 1; // the order in which fixtures will be loaded
}
}

I found the problem.
The "joinTable" preperty need to be on the News side and news use the "inversedBy" property, no the MappedBy (that's the tag). So it work. And to add news to a tag (do the inverse, then), we need to specify in the Tag entity to add the tag to the news... I don't understand why Doctrine doesn't do that by default... weird...

Related

Symfony 3 fluid entity relations

I am trying to implement a time tracking mechanism in my custom project management app.
This app contains multiple entities (tickets, projects, wiki pages, sprints, ...)
I want my timetracking to be "generic" in the sense that I want users to be able to log time against a ticket, project, wiki page, ...well any entity actually.
Now, I am trying to figure out what database schema (relation) to use for my TimeLog entity.
I could theoretically create a relation to each entity I have in my app, but that will require me to keep updating schema when I introduce new entities later on.
Has anybody every implemented anything like this?
All suggestions are welcomed.
Many thanks in advance.
I faced a similar situation in my app while trying to add comments, likes and other types of elements whose behaviour would not really depend on the entity they are attached to.
The solution I eventually chose was to have two fields in my referring entities (e.g. Comment) to hold both the id of the entity being referred to and its type. Since I was using this multiple times, I put the properties into the following trait:
namespace AppBundle\Entity\Traits;
use Doctrine\ORM\Mapping as ORM;
trait EntityReferenceTrait
{
/**
* #ORM\Column(name="reference_id", type="integer")
*/
private $referenceId;
/**
* #ORM\Column(name="reference_type", type="integer")
*/
private $referenceType;
/* ... setters & getters ... */
}
Then I could use it in the entities holding those kind of references:
/**
* #ORM\Table(name="comments", indexes={#ORM\Index(name="references", columns={"reference_id", "reference_type"})})
* #ORM\Entity(repositoryClass="AppBundle\Repository\Comment\CommentRepository")
*/
class Comment
{
/* ... other traits ... */
use \AppBundle\Entity\Traits\EntityReferenceTrait;
/* ... other fields & methods ... */
}
Note: I added an index for the references but it is not necessary for the whole thing to work properly. If you use such an index, beware of the order of your WHERE clauses if you want to benefit from it
In order to improve performance a bit and add additional configurations depending on the type of the entity being referred to, I handled settings directly in the config of my app. Thus, I have something like:
commentables:
news:
classname: AppBundle\Entity\News\News
type_id: 1
browse_route: news_comments
multiple_locales: false
...
This allows me to know precisely what kind of entities my Comment entity can refer. It also allows me to automatically hook specific listeners to the entities being referred to so that the removal of a referred entity triggers the removal of the related comments for example. I do this by processing the configuration in AppBundle/DependencyInjection/AppExtension.php (more about this here) and saving the needed listeners list into a parameter. Then, by adding a listener to the loadClassMetadata event, I can effectively handle the removal of related entities for example.
Here is the listener that hooks the listeners for specific lifecycle events of referred entities by using addEntityListener on the ClassMetadata instance:
namespace AppBundle\Listener;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class MappingListener
{
private $entityListenersMapping = [];
/**
* #param array $mappingConfig Associative array with keys being listeners classnames and values being arrays associating an event to a method name
*/
public function __construct(array $mappingConfig)
{
$this->entityListenersMapping = $mappingConfig;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if(!array_key_exists($classMetadata->name, $this->entityListenersMapping))
{
return;
}
// Hook the entity listeners in the class metadata
foreach($this->entityListenersMapping[$classMetadata->name] as $listenerClassName => $eventsCallbacks)
{
foreach($eventsCallbacks as $event => $methodName)
{
$classMetadata->addEntityListener($event, $listenerClassName, $methodName);
}
}
}
}
Either way, for this part, it mainly depends on the specific needs of your entity but I guess it is quite a common need that these "soft" foreign keys emulate a ON DELETE CASCADE behaviour via preRemove and postRemove events.
Considering the handling of those references and the entities owning them, I also created a EntityRefererManagerTrait to easily create services that manage those entities so that the other components interacting with them would not have to worry about the underlying configuration.
The interface of most public methods of those managers thus usually require:
the classname of the entity being referred to
the numeric id of the entity being referred to
With those two info and the configuration retrieved in my manager service, I can easily interact with the database even if, in my case, it stores an integer defined in the configuration as the reference type in place of the classname of the entity being referred to.
Based on this, I can enable comments, likes, votes, subscriptions and so on for any of my app entities (as long as its primary key is a single integer) with just a few more lines in my configuration files. No need to update database schema and with proper lifecycle events listeners being hooked, no worries about orphan entries in the database.
On a side note, it should be mentioned that you won't be able to retrieve referring entities from the inverse side as it won't be a real association. You won't benefit from foreign keys behaviours either. Thus, even if you emulate the ON DELETE CASCADE behaviour by listening to remove events, you won't be able to ensure that there are no orphans in your database if some DELETE operations are performed directly via DQL for example.

How to implement Doctrine NotifyPropertyChanged in Symfony

Can anyone tell me how to implement Doctrine NotifyPropertyChanged in Symfony?
I have implemented this code in an Entity that will notify about is changes. But how to add listener for these changes in another Entity?
I mean I want to handle this situation: Entity Book has oneToMany to Authors. When something is being changed in a partcular Author, I want to react on it in a Book Entity. So I have implemented NotifyPropertyChanged in Author. In a setter I invoke notifications, but how to add this listener to a Book entity?
It looks like your use case is a little bit different: the ǸotifyPropertyChanged` is usefull to tell Doctrine your entity changed. By default, Doctrine2 will iterate over all properties and compare them (so that's "automatic"). IMO, drop it. Only use case I see for it right now are some very specific cases were using the default policy would cost too much.
Another way to do would be to use some lifecycle events. Changing other entities from a preUpdate or postUpdate is notoriously difficult. Doctrine already started calculting the "changeset", what to persist to DB, and there's no easy way to add another entity to it.
Since you are ok writing some code inside the setters, the easier path is something like this:
class Author
{
public function setName($name)
{
// ...
// for each setter, call onChanged
$this->onChange();
}
private function onChange()
{
foreach ($this->books as $book) {
// maybe call some method on your books, like onAuthorChanged() ?
}
}
}

Doctrine lifecycleCallbacks strange behaviour

I have defined lifecycleCallbacks in yaml as follows:
lifecycleCallbacks:
prePersist: [setCreatedAtValue]
preUpdate: [setUpdatedAtValue]
The above has generated entities with the respective functions as follows:
/**
* #ORM\PrePersist
*/
public function setCreatedAtValue()
{
if($this->created_at == null)
{
$this->created_at = new \DateTime();
}
}
Which looks all fine, right? However, when I try to open the sonata admin page, I get the following error
[Semantical Error] The annotation "#ORM\PrePersist" in method AppBundle\Entity\Article::setCreatedAtValue() was never imported. Did you maybe forget to add a "use" statement for this annotation?
I have never encountered this before and a bit confused about what to do. I am using symfony 2.7.6, Doctrine ORM version 2.5.1, Sonata Admin 2.3.7
Any help will be greatly appreciated
Since you defined your callbacks using yaml, you don´t need to define them again using annotations. Just remove the comments with the #ORM\PrePersist block before the function and everything will be fine.
If you wanted to use annotations to define your doctrine properties, you would need to import them before you can use them. To do so you would need to add this line at the beginning of your file:
use Doctrine\ORM\Mapping as ORM;
Same issue came with me.
In my case everything worked well until I did not Serialize my object in JsonResponse.
So problem was that previously I was not using that entity class (which was giving error) for sending JsonResponse, as soon as I tried to prepare JsonResponse containing that class, JsonResponse failed to serialize my class as it hadn't implemented Serializable interface.
This will happen if you will fetch objects with methods like findAll or findBy which returns Entity objects instead of php array.
So I just skipped those native methods and write doctrine query for fetching data.
You can also implement Serializable interface.

Doctrine EntityManagerDecorator

I've created custom decorator for EntityManager and now when I'm doing doctrine->getManager(), then I can get my custom manager class, but inside repository class I still have native EntityManager how can I fix this. Or maybe there is another way to set something inside repository classes from container?
Decorator calls getRepository on $wrapped(EntityManager) and then $wrapped pass $this inside RepositoryFactory $this == $wrapped == EntityManager
My solution is:
public function getRepository($className)
{
$repository = parent::getRepository($className);
if ($repository instanceof MyAbstractRepository) {
$repository->setDependency();
}
return $repository;
}
There are a couple of approaches:
Copy the static EntityManager::createRepository code to your entity manager class and adjust it accordingly. This is fragile since any change to the EntityManager code might break your code. You have to keep track of doctrine updates. However, it can be made to work.
A second approach is to define your repositories as services. You could then inject your entity manager in the repository. Bit of a hack but it avoids cloning the createRepository code.
The third approach is the recommended approach. Don't decorate the entity manager. Think carefully about what you are trying to do. In most cases, Doctrine events or a custom base repository class can handle your needs. And it saves you from fooling around with the internals.
One option would be to override the entity manager service classes or parameters via a compiler pass.

Add a custom function in a vendor repository

I'm using CCDNForum's ForumBundle (the bundle itself is not relevant, my question isn't related to this bundle specifically but is more general) in my Symfony website, and I want to customize it, so naturally I created a AcmeForumBundle whose parent is CCDNForumForumBundle. The only thing I want to change is to add a new custom function in let say the CategoryRepository associated to the Category entity.
So I created a CategoryRepository.php in my AcmeForumBundle extending the CategoryRepository.php of CCDNForum, and this is not sufficient because the default repository of the Category entity is CCDNForum's CategoryRepository.
The next thing I did was to create a new AcmeForumBundle Category entity extending CCDNForum's Category entity, changing of course the default associated repository
namespace Acme\ForumBundle\Entity;
use CCDNForum\ForumBundle\Entity\Category as BaseCategory;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection as ArrayCollection;
/**
* #ORM\Entity
* #ORM\Entity(repositoryClass="Acme\ForumBundle\Repository\CategoryRepository")
* #ORM\Table(name="CC_Forum_Category")
*/
class Category extends BaseCategory
{
}
I added the #ORM\Table line because I had an SQL error "acme_category" table not found. The class is of course empty as I have nothing to change in the entity.
Using this code everything works fine, I added my custom function in the CategoryRepository without problem, but the problem comes when I want to update the database schema. I have the error when running php app/console doctrine:schema:update --dump-sql
[Doctrine\DBAL\Schema\SchemaException]
The table with name 'acme.cc_forum_category' already exists.
which I can understand since CCDNForum's Category entity and my Acme Category entity are using the same table (the C_Forum_Category table).
My question is: am I doing all this right ? Isn't there a simpler way to add a custom function in a vendor repository ?
Thanks!

Resources