Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support fetching entities by aliased name #1181

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
51528fb
set metadata for interface to be able to fetch entites by interface name
Jun 29, 2012
19c52e4
added failing test
Jun 29, 2012
437f812
Added new event to allow actions to be done before trying to load cla…
Burgov Jul 4, 2012
3be43a1
Rebased and renamed PreLoadClassMetadata to OnClassMetadataNotFound
Nov 23, 2012
d663565
added use statement
Nov 23, 2012
efd4500
`ClassMetadataFactory` should support fallback (event-based) logic fo…
Ocramius Nov 11, 2014
f5eb20b
`OnClassMetadataNotFoundEventArgs` should support setting the actuall…
Ocramius Nov 11, 2014
d91b0b4
Minor CS fixes in the `ResolveTargetEntityListener`
Ocramius Nov 11, 2014
e4cbdb5
Enabling fallback logic in metadata loading
Ocramius Nov 11, 2014
ce4df76
Explicitly requiring `doctrine/common` with at least version `2.5`
Ocramius Nov 11, 2014
a710706
#385 #1181 DDC-3385 - optimized imports, removing duplicate imports (…
Ocramius Jan 13, 2015
8ea394e
#385 #1181 DDC-3385 - removing `@todo` that was discussed/cleared
Ocramius Jan 13, 2015
eaa9187
#385 #1181 DDC-3385 - minor CS fix (spacing)
Ocramius Jan 13, 2015
94a2036
#385 #1181 DDC-3385 - adding `@group` annotation to newly introduced …
Ocramius Jan 13, 2015
f14063d
#385 #1181 DDC-3385 - splitting test case to verify interface-first f…
Ocramius Jan 13, 2015
06f256b
#385 #1181 DDC-3385 - documenting `onClassMetadataNotFound` event
Ocramius Jan 13, 2015
6021e3a
#385 #1181 DDC-3385 - Updating docblock of `OnClassMetadataNotFoundEv…
Ocramius Jan 13, 2015
762e798
#385 #1181 DDC-3385 - extending `OnClassMetadataNotFoundEventArgs` fr…
Ocramius Jan 13, 2015
6debf2c
#385 #1181 DDC-3385 - aligning tests to new `OnClassMetadataNotFoundE…
Ocramius Jan 13, 2015
662b496
#385 #1181 DDC-3385 - aligning tests and implementation to new `OnCla…
Ocramius Jan 13, 2015
d712ad9
#385 #1181 DDC-3385 - test coverage for `OnClassMetadataNotFoundEvent…
Ocramius Jan 13, 2015
ae8ab08
#385 #1181 DDC-3385 - fixing `OnClassMetadataNotFoundEventArgs` docbl…
Ocramius Jan 13, 2015
ba5378f
#385 #1181 DDC-3385 - fixing `Events` docblocks as per @deeky666's re…
Ocramius Jan 13, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"doctrine/collections": "~1.2",
"doctrine/dbal": ">=2.5-dev,<2.6-dev",
"doctrine/instantiator": "~1.0.1",
"doctrine/common": ">=2.5-dev,<2.6-dev",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this wasn't there: we depend on the package, therefore it must be in the list.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed, it must be there

"symfony/console": "~2.5"
},
"require-dev": {
Expand Down
4 changes: 4 additions & 0 deletions docs/en/reference/events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ the life-time of their registered entities.
- loadClassMetadata - The loadClassMetadata event occurs after the
mapping metadata for a class has been loaded from a mapping source
(annotations/xml/yaml). This event is not a lifecycle callback.
- onClassMetadataNotFound - Loading class metadata for a particular
requested class name failed. Manipulating the given event args instance
allows providing fallback metadata even when no actual metadata exists
or could be found. This event is not a lifecycle callback.
- preFlush - The preFlush event occurs at the very beginning of a flush
operation. This event is not a lifecycle callback.
- onFlush - The onFlush event occurs after the change-sets of all
Expand Down
89 changes: 89 additions & 0 deletions lib/Doctrine/ORM/Event/OnClassMetadataNotFoundEventArgs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/

namespace Doctrine\ORM\Event;

use Doctrine\Common\EventArgs;
use Doctrine\Common\Persistence\Event\ManagerEventArgs;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;

/**
* Class that holds event arguments for a `onClassMetadataNotFound` event.
*
* This object is mutable by design, allowing callbacks having access to it to set the
* found metadata in it, and therefore "cancelling" a `onClassMetadataNotFound` event
*
* @author Marco Pivetta <[email protected]>
* @since 2.5
*/
class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs
{
/**
* @var string
*/
private $className;

/**
* @var ClassMetadata|null
*/
private $foundMetadata;

/**
* Constructor.
*
* @param string $className
* @param ObjectManager $objectManager
*/
public function __construct($className, ObjectManager $objectManager)
{
$this->className = (string) $className;

parent::__construct($objectManager);
}

/**
* @param ClassMetadata|null $classMetadata
*/
public function setFoundMetadata(ClassMetadata $classMetadata = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the use case to set found metadata to null? Either you set it or you don't and it is null anyways then.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just want to allow full mutability here. Having a failure in a different listener should cause a reset to null eventually.

{
$this->foundMetadata = $classMetadata;
}

/**
* @return ClassMetadata|null
*/
public function getFoundMetadata()
{
return $this->foundMetadata;
}

/**
* Retrieve class name for which a failed metadata fetch attempt was executed
*
* @return string
*/
public function getClassName()
{
return $this->className;
}
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EOF EOL

8 changes: 8 additions & 0 deletions lib/Doctrine/ORM/Events.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ private function __construct()
*/
const loadClassMetadata = 'loadClassMetadata';

/**
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newline before this line

* The onClassMetadataNotFound event occurs whenever loading metadata for a class
* failed.
*
* @var string
*/
const onClassMetadataNotFound = 'onClassMetadataNotFound';

/**
* The preFlush event occurs when the EntityManager#flush() operation is invoked,
* but before any changes to managed entities have been calculated. This event is
Expand Down
19 changes: 18 additions & 1 deletion lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use Doctrine\DBAL\Platforms;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
use Doctrine\ORM\Events;
use Doctrine\ORM\Id\BigIntegerIdentityGenerator;
use Doctrine\ORM\Id\IdentityGenerator;
Expand Down Expand Up @@ -78,7 +79,7 @@ public function setEntityManager(EntityManagerInterface $em)
}

/**
* {@inheritDoc}.
* {@inheritDoc}
*/
protected function initialize()
{
Expand All @@ -88,6 +89,22 @@ protected function initialize()
$this->initialized = true;
}

/**
* {@inheritDoc}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no parent method here as far as I can see. Can you provide a descriptive DocBlock?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is actually an override as per doctrine/common#342

*/
protected function onNotFoundMetadata($className)
{
if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
return;
}

$eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em);

$this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);

return $eventArgs->getFoundMetadata();
}

/**
* {@inheritDoc}
*/
Expand Down
47 changes: 44 additions & 3 deletions lib/Doctrine/ORM/Tools/ResolveTargetEntityListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
namespace Doctrine\ORM\Tools;

use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;

/**
* ResolveTargetEntityListener
Expand All @@ -31,13 +34,24 @@
* @author Benjamin Eberlei <[email protected]>
* @since 2.2
*/
class ResolveTargetEntityListener
class ResolveTargetEntityListener implements EventSubscriber
{
/**
* @var array
* @var array[] indexed by original entity name
*/
private $resolveTargetEntities = array();

/**
* {@inheritDoc}
*/
public function getSubscribedEvents()
{
return array(
Events::loadClassMetadata,
Events::onClassMetadataNotFound
);
}

/**
* Adds a target-entity class name to resolve to a new class name.
*
Expand All @@ -49,26 +63,53 @@ class ResolveTargetEntityListener
*/
public function addResolveTargetEntity($originalEntity, $newEntity, array $mapping)
{
$mapping['targetEntity'] = ltrim($newEntity, "\\");
$mapping['targetEntity'] = ltrim($newEntity, "\\");
$this->resolveTargetEntities[ltrim($originalEntity, "\\")] = $mapping;
}

/**
* @param OnClassMetadataNotFoundEventArgs $args
*
* @internal this is an event callback, and should not be called directly
*
* @return void
*/
public function onClassMetadataNotFound(OnClassMetadataNotFoundEventArgs $args)
{
if (array_key_exists($args->getClassName(), $this->resolveTargetEntities)) {
$args->setFoundMetadata(
$args
->getObjectManager()
->getClassMetadata($this->resolveTargetEntities[$args->getClassname()]['targetEntity'])
);
}
}

/**
* Processes event and resolves new target entity names.
*
* @param LoadClassMetadataEventArgs $args
*
* @return void
*
* @internal this is an event callback, and should not be called directly
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
{
/* @var $cm \Doctrine\ORM\Mapping\ClassMetadata */
$cm = $args->getClassMetadata();

foreach ($cm->associationMappings as $mapping) {
if (isset($this->resolveTargetEntities[$mapping['targetEntity']])) {
$this->remapAssociation($cm, $mapping);
}
}

foreach ($this->resolveTargetEntities as $interface => $data) {
if ($data['targetEntity'] == $cm->getName()) {
$args->getEntityManager()->getMetadataFactory()->setMetadataFor($interface, $cm);
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Doctrine\Tests\ORM;

use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
use PHPUnit_Framework_TestCase;

/**
* Tests for {@see \Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs}
*
* @covers \Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs
*/
class OnClassMetadataNotFoundEventArgsTest extends PHPUnit_Framework_TestCase
{
public function testEventArgsMutability()
{
/* @var $objectManager \Doctrine\Common\Persistence\ObjectManager */
$objectManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager');

$args = new OnClassMetadataNotFoundEventArgs('foo', $objectManager);

$this->assertSame('foo', $args->getClassName());
$this->assertSame($objectManager, $args->getObjectManager());

$this->assertNull($args->getFoundMetadata());

/* @var $metadata \Doctrine\Common\Persistence\Mapping\ClassMetadata */
$metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata');

$args->setFoundMetadata($metadata);

$this->assertSame($metadata, $args->getFoundMetadata());

$args->setFoundMetadata(null);

$this->assertNull($args->getFoundMetadata());
}
}
36 changes: 36 additions & 0 deletions tests/Doctrine/Tests/ORM/Mapping/ClassMetadataFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Doctrine\Tests\ORM\Mapping;

use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
use Doctrine\ORM\Events;
use Doctrine\Tests\Mocks\MetadataDriverMock;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\Mocks\ConnectionMock;
Expand Down Expand Up @@ -322,6 +324,40 @@ public function testQuoteMetadata()
$this->assertEquals('group-id', $groups['joinTable']['inverseJoinColumns'][0]['referencedColumnName']);
}

/**
* @group DDC-3385
* @group 1181
* @group 385
*/
public function testFallbackLoadingCausesEventTriggeringThatCanModifyFetchedMetadata()
{
$test = $this;
/* @var $metadata \Doctrine\Common\Persistence\Mapping\ClassMetadata */
$metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata');
$cmf = new ClassMetadataFactory();
$mockDriver = new MetadataDriverMock();
$em = $this->_createEntityManager($mockDriver);
$listener = $this->getMock('stdClass', array('onClassMetadataNotFound'));
$eventManager = $em->getEventManager();

$cmf->setEntityManager($em);

$listener
->expects($this->any())
->method('onClassMetadataNotFound')
->will($this->returnCallback(function (OnClassMetadataNotFoundEventArgs $args) use ($metadata, $em, $test) {
$test->assertNull($args->getFoundMetadata());
$test->assertSame('Foo', $args->getClassName());
$test->assertSame($em, $args->getObjectManager());

$args->setFoundMetadata($metadata);
}));

$eventManager->addEventListener(array(Events::onClassMetadataNotFound), $listener);

$this->assertSame($metadata, $cmf->getMetadataFor('Foo'));
}

/**
* @group DDC-3427
*/
Expand Down
Loading