Doctrine 2 Can't Seem to Remove Many to Many Relationships - doctrine-orm

I have the following setup "Many Users can have Many Projects (Collaborators)"
/**
* #Entity #HasLifeCycleCallbacks
* #Table(name="projects")
*/
class Project implements \Zend_Acl_Resource_Interface {
/**
* #ManyToMany(targetEntity="User", mappedBy="projects")
* #OrderBy({"displayName" = "ASC", "username" = "ASC"})
*/
protected $collaborators;
..
}
/**
* #Entity
* #Table(name="users")
*/
class User implements \Zend_Acl_Role_Interface {
/**
* #ManyToMany(targetEntity="Project", inversedBy="collaborators")
*/
protected $projects;
...
}
I tried to remove a collaborator using the following
$user = Application_DAO_User::findById($this->_getParam('userid'));
$proj = Application_DAO_Project::getProjectById($this->_getParam('id'));
Application_DAO_Project::removeCollaborator($proj, $user); // <---
// Application_DAO_User
public static function findById($id) {
return self::getStaticEm()->find('Application\Models\User', $id);
}
// Application_DAO_Project
public static function getProjectById($id) {
return self::getStaticEm()->find('Application\Models\Project', $id);
}
public static function removeCollaborator(Project $proj, User $collaborator) { // <---
$proj->getCollaborators()->remove($collaborator);
$collaborator->getProjects()->remove($proj);
self::getStaticEm()->flush();
}
And there isn't any errors but the database stays the same ...

This may be well over due but was just experiencing the same problem myself... According to the doctrine 2 documents, the function ArrayCollection->remove($i) is for removing by array index.
What you are after is:
getCollaborators()->removeElement($collaborator);

I went round in circles trying to figure this out until I realised that for this to work:
getCollaborators()->removeElement($collaborator);
$collaborator would have to be the actual object from the collaborators ArrayCollection. That is, if you pass in a new Collaborator object with the same parameters it won't remove it. That's because ArrayCollection uses array_search to look for the object you want to remove.
Hope that saves someone else a few hours...

Related

Map a discriminator column to a field with Doctrine 2

In my project I have several class table inheritances like this:
namespace MyProject\Model;
/**
* #Entity
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/** #Entity */
class Employee extends Person
{
// ...
}
I have a method which converts entities to arrays based on the fields which have public getters. The problem here is that I lose the inheritance information in my array because the discriminator value isn't stored in a field.
So what I tried was the following, hoping doctrine would automatically set $disc:
class Person
{
// can I automatically populate this field with 'person' or 'employee'?
protected $discr;
public function getDiscr() { return $this->discr; }
public function setDiscr($disc) { $this->discr; }
// ...
}
Is there a way to make this work in doctrine? Or would I need to read the class metadata in my entity-to-array method?
Sadly, there is no documented way to map the discr column to an entity. That's because the discr column is really part of the database and not the entity.
However, it's quite common to just put the discr value directly in your class definition. It's not going to change and you will always get the same class for the same value anyways.
class Person
{
protected $discr = 'person';
class Employee extends Person
{
protected $discr = 'employee';
Here's a small example of what I have in one of my ZF2 projects (using Doctrine MongoDB ODM):
// an instance of your entity
$entity = ...;
/** #var \Doctrine\ODM\MongoDB\DocumentManager $documentManager */
$documentManager = $serviceManager->get('DocumentManager');
/** #var \Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory $factory */
$factory = $documentManager->getMetadataFactory()
/** #var \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $metadata */
$metadata = $factory->getMetadataFor(get_class($object));
if ($metadata->hasDiscriminator()) {
// assuming $data is result of the previous extraction
$data[$metadata->discriminatorField] = $metadata->discriminatorValue;
}
What I have done is I've implemented a custom interface DiscriminatorAwareInterface and I only apply the checks to classes that implement it (in your case it would be the class that all "discriminated" classes extend.
As a result I end up with code that looks like this:
// add value of the discrinimator field to entities that support it
if ($object instanceof DiscriminatorAwareInterface) {
/** #var \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $metadata */
$metadata = $factory->getMetadataFor(get_class($object));
if ($metadata->hasDiscriminator()) {
$data[$metadata->discriminatorField] = $metadata->discriminatorValue;
}
}
I'm pretty sure it will be the same if you use the standard ORM, except instead of a document manager you will have entity manager.
Just got this problem and solved it without defining the discriminator as a real member:
abstract class MyEntity {
const TYPE_FOO = 'foo';
const TYPE_BAR = 'bar';
const TYPE_BUZ = 'buz';
...
/**
* #return string
*/
public function getMyDiscriminator()
{
$myDiscriminator = null;
switch (get_class($this)) {
case MyEntityFoo::class:
$myDiscriminator = self::TYPE_FOO;
break;
case MyEntityBar::class:
$myDiscriminator = self::TYPE_BAR;
break;
case MyEntityBuz::class:
$myDiscriminator = self::TYPE_BUZ;
break;
}
return $myDiscriminator;
}
...
}
class MyEntityFoo extends MyEntity {}
class MyEntityBar extends MyEntity {}
class MyEntityBuz extends MyEntity {}
You can use the following solution:
`$`$metadata = \Doctrine\ORM\Mapping\ClassMetadata((string)$entityName);
print_r($metadata->discriminatorValue);`

ZF2 Doctrine Entity findAll

Being able to use Doctrine speeds up a lot of things however it feels somewhat clunky to me having to set / use the entity manager in all of my controllers. I would prefer to have all of the database logic in 1 specific module. Perhaps I'm just thinking about this the wrong way, and someone can point me in the right direction.
Currently I have my Entity which functions just fine and I can do insertions into the database fine with the following
namespace Manage\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class ViewController extends AbstractActionController {
public function somethingAction(){
$objectManager = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$user = new \Manage\Entity\User();
$user->setname('foo');
$user->settitle('bar');
$objectManager->persist($user);
$objectManager->flush();
}
}
However whenever I want to select something from the database I have to make sure to add
use Doctrine\ORM\EntityManager;
And then the following list of Controller functions...
/**
* #var EntityManager
*/
protected $entityManager;
/**
* Sets the EntityManager
*
* #param EntityManager $em
* #access protected
* #return PostController
*/
protected function setEntityManager(EntityManager $em) {
$this->entityManager = $em;
return $this;
}
/**
* Returns the EntityManager
*
* Fetches the EntityManager from ServiceLocator if it has not been initiated
* and then returns it
*
* #access protected
* #return EntityManager
*/
protected function getEntityManager() {
if (null === $this->entityManager) {
$this->setEntityManager($this->getServiceLocator()->get('Doctrine\ORM\EntityManager'));
}
return $this->entityManager;
}
Once I have added all of that I can now do a query in my getsomethingAction like so...
public function getsomethingAction() {
$repository = $this->getEntityManager()->getRepository('Manage\Entity\User');
$list = $repository->findAll();
var_dump($list);
return new ViewModel();
}
To me that feels very clunky... I can do an insert without needing all the extra functions but I cannot do a select? Is it possible to extend the Entity class in order to get the find / findAll etc functions that is provided by calling $repository = $this->getEntityManager()->getRepository('Manage\Entity\User'); directly inside the entity?
By that I mean I would prefer to be able to run the find directly on the entity as I would when I set the data... like below:
public function getsomethingAction(){
$list = new \Manage\Entity\User();
$l = $list->findAll();
var_dump($l);
return new ViewModel();
}
Ok so my main objective so far has been to move the complex logic out of the controllers into a re-usable model. So with this example answer I'm creating an interface where the complex logic would live however it also allows me to still use the model in a controller to get data from the database... here is the Model...
namespace Manage\Model;
use Doctrine\ORM\EntityManager;
class ApiInterface {
/**
* #var EntityManager
*/
protected $entityManager;
protected $sl;
/**
* Sets the EntityManager
*
* #param EntityManager $em
* #access protected
* #return PostController
*/
protected function setEntityManager(EntityManager $em) {
$this->entityManager = $em;
return $this;
}
/**
* Returns the EntityManager
*
* Fetches the EntityManager from ServiceLocator if it has not been initiated
* and then returns it
*
* #access protected
* #return EntityManager
*/
protected function getEntityManager() {
if (null === $this->entityManager) {
$this->setEntityManager($this->sl->get('Doctrine\ORM\EntityManager'));
}
return $this->entityManager;
}
public function __construct($ServiceLocator) {
$this->sl = $ServiceLocator;
}
public function get() {
$repository = $this->getEntityManager()->getRepository('Manage\Entity\ApiList');
return $repository;
}
public function set() {
return new \Manage\Entity\ApiList();
}
public function save($data) {
$objectManager = $this->sl->get('Doctrine\ORM\EntityManager');
$objectManager->persist($data);
$objectManager->flush();
}
public function doComplexLogic($foo,$bar){
// Can now use both set() and get() to inspect/modify/add data
}
}
So now inside my controller I can do something that gets some basic data from the table like:
public function getapiAction() {
$api = new \Manage\Model\ApiInterface($this->getServiceLocator());
var_dump($api->get()->findAll());
return new ViewModel();
}
And to quickly set data from a controller I can do:
public function setapiAction() {
$apiInterface = new \Manage\Model\ApiInterface($this->getServiceLocator());
$api= $apiInterface->set();
$user->setfoo('blah');
$user->setbar('moo');
$apiInterface->save($api);
return new ViewModel();
}
And it also allows me to run complex logic from the controller by taking the complexity out of the controller like so...
public function complexAction(){
$foo = $this->params()->fromQuery();
$bar = $this->params()->fromPost();
$apiInterface = new \Manage\Model\ApiInterface($this->getServiceLocator());
$apiInterface->doComplexLogic($foo, $bar);
}
Please let me know in comments if this answer would be the proper way to do things, I realize it's very simple and generic but I wanted to keep it that way so others can understand what / why and if this is a good approach / not etc.

Doctrine "reverse" orphan removal

I have two entities (simplified):
class EncryptedMasterKey {
/**
* #ORM\ManyToOne(targetEntity="ExchangeFile", inversedBy="encryptedMasterKeys")
* #ORM\JoinColumn(name="exchange_file_id", referencedColumnName="id")
*
* #var ExchangeFile
*/
protected $exchangeFile;
}
and
class ExchangeFile {
/**
* #ORM\OneToMany(targetEntity="EncryptedMasterKey", mappedBy="exchangeFile", orphanRemoval=true, cascade={"persist", "remove"})
*/
protected $encryptedMasterKeys;
}
There can be many EncryptedMasterKeys for one ExchangeFile in the database. If the ExchangeFile is deleted, all related encrypted MasterKeys are deleted (orphanRemoval=true and cascade={"persist", "remove"} make sure this is the case). So far, so good.
Now as the actual file lies encrypted on the hard disk, there must be at least one EncryptedMasterKey so that the file can be decrypted. So when a EncryptedMasterKey is deleted and I discover that it is the last one for it's ExchangeFile, I also have to delete the ExchangeFile because it cannot be decrypted any more. An ExchangeFile cannot live without at least one EncryptedMasterKey.
How do I achieve this? #ORM\PreRemove in the EncryptedMasterKey class does't really help me because I don't have access to the Entity Manager:
class EncryptedMasterKey {
//...
/** #ORM\PreRemove */
public function removeOrphanExchangeFile()
{
if ($this->exchangeFile->isTheOnlyMasterKey($this))
// I don't have access to the Entity Manager,
// so how do I delete the ExchangeFile?
}
}
Is there any elegant solution to this?
Thanks for your time.
You can use an event subscriber and create a class like following:
class MyEncryptedMasterSubscriber implements \Doctrine\Common\EventSubscriber
{
public function getSubscribedEvents()
{
return array(\Doctrine\ORM\Events::onFlush);
}
public function onFlush(\Doctrine\ORM\Events\OnFlushEventArgs $eventArgs)
{
$uow = $eventArgs->getEntityManager()->getUnitOfWork();
foreach ($uow->getScheduledEntityDeletions() AS $entity) {
if (
$entity instanceof EncryptedMasterKey
&& $entity->getExchangeFile()->isTheOnlyMasterKey($entity)
) {
$uow->scheduleForDelete($entity->getExchangeFile());
}
}
}
}
You can read more about how to register subscribers in the particular case of Symfony 2 on the documentation for it.

$this->assertEquals error : Failed asserting that null matches expected

I'm doing test unit of my entity:
namespace PathtomyBundle\Tests;
require_once dirname(__DIR__).'/../../../app/AppKernel.php';
use Doctrine\ORM\Tools\SchemaTool;
abstract class TestCase extends \PHPUnit_Framework_TestCase
{
/**
* #var Symfony\Component\HttpKernel\AppKernel
*/
protected $kernel;
/**
* #var Doctrine\ORM\EntityManager
*/
protected $entityManager;
/**
* #var Symfony\Component\DependencyInjection\Container
*/
protected $container;
public function setUp()
{
// Boot the AppKernel in the test environment and with the debug.
$this->kernel = new \AppKernel('test', true);
$this->kernel->boot();
// Store the container and the entity manager in test case properties
$this->container = $this->kernel->getContainer();
$this->entityManager = $this->container->get('doctrine')->getEntityManager();
// Build the schema for sqlite
//$this->generateSchema();
parent::setUp();
}
public function tearDown()
{
// Shutdown the kernel.
$this->kernel->shutdown();
parent::tearDown();
}
protected function generateSchema()
{
// Get the metadatas of the application to create the schema.
$metadatas = $this->getMetadatas();
if ( ! empty($metadatas)) {
// Create SchemaTool
$tool = new SchemaTool($this->entityManager);
$tool->createSchema($metadatas);
} else {
throw new Doctrine\DBAL\Schema\SchemaException('No Metadata Classes to process.');
}
}
/**
* Overwrite this method to get specific metadatas.
*
* #return Array
*/
protected function getMetadatas()
{
return $this->entityManager->getMetadataFactory()->getAllMetadata();
}
}
and also:
namespace pathtomybundle\Tests\Entity;
use pathtomybundle\Tests\TestCase;
use pathtomybundle\Entity\Calendars;
require_once dirname(__DIR__).'/TestCase.php';
class CalendarsDbTest extends TestCase
{
protected $Calendars;
public function setUp()
{
parent::setUp();
$this->Calendars = new Calendars();
}
public function testGenerateCalendars()
{
$this->Calendars->setBeginDate(new \DateTime('now'));
$this->Calendars->setDescription('Description');
$this->Calendars->setEndDate(new \DateTime('now'));
$this->Calendars->setType('sur titre');
// Save the ExCalendars
$this->entityManager->persist($this->Calendars);
$this->entityManager->flush();
}
public function testUser(){
$this->assertEquals('Description', $this->Calendars->getDescription() );
}
So my questions are:
Why does it raise this error "Failed asserting that null matches expected"?
Why getDescription() returns NULL?
How to test two table with One-to-Many relationship for example my Table Calendars with another table in database?
Edit
For the third question :
For example I have two Tables Job and Calenders with Many-to-One relationship so I will have a Job_Id field in Calendars Table,so how I will do my test Unit with a foreign key "job_id"
In Calendars Entity :
/**
* #var Job
*
* #ORM\ManyToOne(targetEntity="Job")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="job_id", referencedColumnName="job_id")
* })
*/
private $jobId;
Edit-2-
when I run my phpunit test "phpunit -c app" to test setters function and persist in database so I have a with every test a new data insered in databse, my question is it possible to do a lot of test but I insert data in database just for one time because actually I must remove data from database with every test.
2 - another question : to create a database_test i use "$this->generateSchema();
" so after create a database for the first time and when the test call "TestCase"class (the code above) again so he tried to create the database_test again then I must remove the line after the first time and it's not good,so what I can do to run this line just for one time in the first time when i run my test?
Edit-3
/**
* #var Job
*
* #ORM\ManyToOne(targetEntity="Job")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="job_id", referencedColumnName="id")
* })
*/
private $job;
it's normal?
Every test in test case creates his own CalendarsDbTest object. So, in fact, $this->Calendar is different object in each test (if you want share it between tests you need create it in setUp method)
Is the same as above (there is null because you never call setDescription with $this->Calendars - it's different object than it is in first test)
I'm not sure what exactly you mean. Can you show more precise (for example method you want test) what you mean?
edit:
The answer is: you don't test it. Why? Because unit test is UNIT test - you should test here only your entity. Persistence, keeping relations etc. are Doctrine resposibility and should be tested there - you don't worry about it.
The only thing you should test is setter/getter for $jobId property (btw. it should be "$job" rather than "$jobId" because it's object of Job class - not an integer), eg.:
class CalendarTest extends \PHPUnit_Framework_TestCase
{
(...)
public function testSetGetJob()
{
$job = new Job();
$job->setSomeProperty('someValue');
$expectedJob = clone $job; // you clone this because in setter you pass object by reference
$calendar = new Calendar();
$calendar->setJob($job);
$this->assertEquals($expectedJob, $calendar->getJob());
}
(...)
}

Doctrine2 __constructor not called when using $em->find(); ? How to load entity properly?

I'm learning doctrine2, and having a problem how to call constructor automatically.
For example, in my entity I have
/**
* #Entity
*/
class User{
....
public function __construct() {
exit('in');
}
}
and when I get the object this way:
$userObj = $em->find('User', 1);
I do get that object from database, but constructor is never called.
I want to put some common things in constructor, like validation rules, or even to put sample code from the doctrine documentation like
$this->comments = new ArrayCollection();
This ofcourse works when I create new object in code for creating a user like
$user = new User(); //now constructor works just fine
Now, what is the "proper" way of getting the entity? I doubt I have to call constructor manually each time I user $em->find() with $user0bj->__construct(); ? This would kinda sucks then... Or I should use something other then ->find() to get single entity properly?
I know I can user #PrePersist, and I am using it to actually do validation checks etc.
I guess that I'm probably missing something here, or I'm trying to use constructor in a poor way. Thanks for any explanations and guides!
I'm pretty certain that find or similar isn't expected to call the constructor...
You need to hook into the #PostLoad event.
Why would you want to call the constuctor of already persisted entity? When you need to validate it you should have done the validation or initializations before you have persisted it. So When you call a already persisted entity there is no point to validate it.
The right place to put validation and other initializations is the constructor method of entity.
Eg.
/**
* #Entity
*/
class User{
protected $name;
public function __construct($name) {
if (isset($name)) {
//** validate the name here */
$this->name=$name;
} else {
throw new Exception("no user name set!");
}
}
}
According to the doctrine2 documentation Doctrine2 never calls __construct() method of entities.
http://www.doctrine-project.org/docs/orm/2.0/en/reference/architecture.html?highlight=construct
doctrine uses reflection to instantiate your object without invoking your constructor.
Since PHP 5.4 , you can use reflection to instanciate a class without
calling the constructor using
ReflectionClass::newInstanceWithoutConstructor
the instantiator of doctrine use it like :
private function buildFactory(string $className) : callable
{
$reflectionClass = $this->getReflectionClass($className);
if ($this->isInstantiableViaReflection($reflectionClass)) {
return [$reflectionClass, 'newInstanceWithoutConstructor'];
}
$serializedString = sprintf(
'%s:%d:"%s":0:{}',
is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER,
strlen($className),
$className
);
$this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString);
return static function () use ($serializedString) {
return unserialize($serializedString);
};
}
Doctrine ORM will "rewrite" your class, it generate a new class that implement \Doctrine\ORM\Proxy\Proxy
And it rewrite the construct method:
/**
* #param \Closure $initializer
* #param \Closure $cloner
*/
public function __construct($initializer = null, $cloner = null)
{
$this->__initializer__ = $initializer;
$this->__cloner__ = $cloner;
}
You can see it inside the cache folder ${CACHE}/doctrine/orm/Proxies.
You will need both #ORM\HasLifecycleCallbacks on the class + #ORM\PostLoad on a specific function of your choice.
Beware! If you put it on the constructor it will override loaded database data!
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="dossier")
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
*/
class Dossier
{
// ...
/**
* The normal constructor stays as usual
*/
public function __construct()
{
$this->takenActions = new ArrayCollection();
$this->classifications = new ArrayCollection();
$this->dossierProblems = new ArrayCollection();
$this->internalNotes = new ArrayCollection();
}
/**
* Triggers after the entity has been loaded in the EntityManager (e.g. Doctrine's ->find() etc...)
* The constructor does not get called. Some variables still need a default value
* Must be in combination with "ORM\HasLifecycleCallbacks" on the class
*
* #ORM\PostLoad
*/
public function postLoadCallback(): void
{
// Only put a default value when it has none yet
if (!$this->dossierProblems)
$this->dossierProblems = new ArrayCollection();
if (!$this->internalNotes)
$this->internalNotes = new ArrayCollection();
}
// ...
}