This repository has been archived on 2025-08-22. You can view files and clone it, but cannot push or open issues or pull requests.
Files
dumbo/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/ProxyLogicTest.php
2014-11-25 16:42:40 +01:00

696 lines
26 KiB
PHP

<?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\Tests\Common\Proxy;
use Doctrine\Common\Proxy\ProxyGenerator;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\Common\Proxy\Exception\UnexpectedValueException;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use PHPUnit_Framework_TestCase;
/**
* Test the generated proxies behavior. These tests make assumptions about the structure of LazyLoadableObject
*
* @author Marco Pivetta <ocramius@gmail.com>
*/
class ProxyLogicTest extends PHPUnit_Framework_TestCase
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $proxyLoader;
/**
* @var ClassMetadata
*/
protected $lazyLoadableObjectMetadata;
/**
* @var LazyLoadableObject|Proxy
*/
protected $lazyObject;
protected $identifier = array(
'publicIdentifierField' => 'publicIdentifierFieldValue',
'protectedIdentifierField' => 'protectedIdentifierFieldValue',
);
/**
* @var \PHPUnit_Framework_MockObject_MockObject|Callable
*/
protected $initializerCallbackMock;
/**
* {@inheritDoc}
*/
public function setUp()
{
$this->proxyLoader = $loader = $this->getMock('stdClass', array('load'), array(), '', false);
$this->initializerCallbackMock = $this->getMock('stdClass', array('__invoke'));
$identifier = $this->identifier;
$this->lazyLoadableObjectMetadata = $metadata = new LazyLoadableObjectClassMetadata();
// emulating what should happen in a proxy factory
$cloner = function (LazyLoadableObject $proxy) use ($loader, $identifier, $metadata) {
/* @var $proxy LazyLoadableObject|Proxy */
if ($proxy->__isInitialized()) {
return;
}
$proxy->__setInitialized(true);
$proxy->__setInitializer(null);
$original = $loader->load($identifier);
if (null === $original) {
throw new UnexpectedValueException();
}
foreach ($metadata->getReflectionClass()->getProperties() as $reflProperty) {
$propertyName = $reflProperty->getName();
if ($metadata->hasField($propertyName) || $metadata->hasAssociation($propertyName)) {
$reflProperty->setAccessible(true);
$reflProperty->setValue($proxy, $reflProperty->getValue($original));
}
}
};
$proxyClassName = 'Doctrine\Tests\Common\ProxyProxy\__CG__\Doctrine\Tests\Common\Proxy\LazyLoadableObject';
// creating the proxy class
if (!class_exists($proxyClassName, false)) {
$proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true);
$proxyGenerator->generateProxyClass($metadata);
require_once $proxyGenerator->getProxyFileName($metadata->getName());
}
$this->lazyObject = new $proxyClassName($this->getClosure($this->initializerCallbackMock), $cloner);
// setting identifiers in the proxy via reflection
foreach ($metadata->getIdentifierFieldNames() as $idField) {
$prop = $metadata->getReflectionClass()->getProperty($idField);
$prop->setAccessible(true);
$prop->setValue($this->lazyObject, $identifier[$idField]);
}
$this->assertFalse($this->lazyObject->__isInitialized());
}
public function testFetchingPublicIdentifierDoesNotCauseLazyLoading()
{
$this->configureInitializerMock(0);
$this->assertSame('publicIdentifierFieldValue', $this->lazyObject->publicIdentifierField);
}
public function testFetchingIdentifiersViaPublicGetterDoesNotCauseLazyLoading()
{
$this->configureInitializerMock(0);
$this->assertSame('protectedIdentifierFieldValue', $this->lazyObject->getProtectedIdentifierField());
}
public function testCallingMethodCausesLazyLoading()
{
$this->configureInitializerMock(
1,
array($this->lazyObject, 'testInitializationTriggeringMethod', array()),
function (Proxy $proxy) {
$proxy->__setInitializer(null);
}
);
$this->lazyObject->testInitializationTriggeringMethod();
$this->lazyObject->testInitializationTriggeringMethod();
}
public function testFetchingPublicFieldsCausesLazyLoading()
{
$test = $this;
$this->configureInitializerMock(
1,
array($this->lazyObject, '__get', array('publicPersistentField')),
function () use ($test) {
$test->setProxyValue('publicPersistentField', 'loadedValue');
}
);
$this->assertSame('loadedValue', $this->lazyObject->publicPersistentField);
$this->assertSame('loadedValue', $this->lazyObject->publicPersistentField);
}
public function testFetchingPublicAssociationCausesLazyLoading()
{
$test = $this;
$this->configureInitializerMock(
1,
array($this->lazyObject, '__get', array('publicAssociation')),
function () use ($test) {
$test->setProxyValue('publicAssociation', 'loadedAssociation');
}
);
$this->assertSame('loadedAssociation', $this->lazyObject->publicAssociation);
$this->assertSame('loadedAssociation', $this->lazyObject->publicAssociation);
}
public function testFetchingProtectedAssociationViaPublicGetterCausesLazyLoading()
{
$this->configureInitializerMock(
1,
array($this->lazyObject, 'getProtectedAssociation', array()),
function (Proxy $proxy) {
$proxy->__setInitializer(null);
}
);
$this->assertSame('protectedAssociationValue', $this->lazyObject->getProtectedAssociation());
$this->assertSame('protectedAssociationValue', $this->lazyObject->getProtectedAssociation());
}
public function testLazyLoadingTriggeredOnlyAtFirstPublicPropertyRead()
{
$test = $this;
$this->configureInitializerMock(
1,
array($this->lazyObject, '__get', array('publicPersistentField')),
function () use ($test) {
$test->setProxyValue('publicPersistentField', 'loadedValue');
$test->setProxyValue('publicAssociation', 'publicAssociationValue');
}
);
$this->assertSame('loadedValue', $this->lazyObject->publicPersistentField);
$this->assertSame('publicAssociationValue', $this->lazyObject->publicAssociation);
}
public function testNoticeWhenReadingNonExistentPublicProperties()
{
$this->configureInitializerMock(0);
$class = get_class($this->lazyObject);
$this->setExpectedException(
'PHPUnit_Framework_Error_Notice',
'Undefined property: ' . $class . '::$non_existing_property'
);
$this->lazyObject->non_existing_property;
}
public function testFalseWhenCheckingNonExistentProperty()
{
$this->configureInitializerMock(0);
$this->assertFalse(isset($this->lazyObject->non_existing_property));
}
public function testNoErrorWhenSettingNonExistentProperty()
{
$this->configureInitializerMock(0);
$this->lazyObject->non_existing_property = 'now has a value';
$this->assertSame('now has a value', $this->lazyObject->non_existing_property);
}
public function testCloningCallsClonerWithClonedObject()
{
$lazyObject = $this->lazyObject;
$test = $this;
$cb = $this->getMock('stdClass', array('cb'));
$cb
->expects($this->once())
->method('cb')
->will($this->returnCallback(function (LazyLoadableObject $proxy) use ($lazyObject, $test) {
/* @var $proxy LazyLoadableObject|Proxy */
$test->assertNotSame($proxy, $lazyObject);
$proxy->__setInitializer(null);
$proxy->publicAssociation = 'clonedAssociation';
}));
$this->lazyObject->__setCloner($this->getClosure(array($cb, 'cb')));
$cloned = clone $this->lazyObject;
$this->assertSame('clonedAssociation', $cloned->publicAssociation);
$this->assertNotSame($cloned, $lazyObject, 'a clone of the lazy object is retrieved');
}
public function testFetchingTransientPropertiesWillNotTriggerLazyLoading()
{
$this->configureInitializerMock(0);
$this->assertSame(
'publicTransientFieldValue',
$this->lazyObject->publicTransientField,
'fetching public transient field won\'t trigger lazy loading'
);
$property = $this
->lazyLoadableObjectMetadata
->getReflectionClass()
->getProperty('protectedTransientField');
$property->setAccessible(true);
$this->assertSame(
'protectedTransientFieldValue',
$property->getValue($this->lazyObject),
'fetching protected transient field via reflection won\'t trigger lazy loading'
);
}
/**
* Provided to guarantee backwards compatibility
*/
public function testLoadProxyMethod()
{
$this->configureInitializerMock(2, array($this->lazyObject, '__load', array()));
$this->lazyObject->__load();
$this->lazyObject->__load();
}
public function testLoadingWithPersisterWillBeTriggeredOnlyOnce()
{
$this
->proxyLoader
->expects($this->once())
->method('load')
->with(
array(
'publicIdentifierField' => 'publicIdentifierFieldValue',
'protectedIdentifierField' => 'protectedIdentifierFieldValue',
),
$this->lazyObject
)
->will($this->returnCallback(function ($id, LazyLoadableObject $lazyObject) {
// setting a value to verify that the persister can actually set something in the object
$lazyObject->publicAssociation = $id['publicIdentifierField'] . '-test';
return true;
}));
$this->lazyObject->__setInitializer($this->getSuggestedInitializerImplementation());
$this->lazyObject->__load();
$this->lazyObject->__load();
$this->assertSame('publicIdentifierFieldValue-test', $this->lazyObject->publicAssociation);
}
public function testFailedLoadingWillThrowException()
{
$this->proxyLoader->expects($this->any())->method('load')->will($this->returnValue(null));
$this->setExpectedException('UnexpectedValueException');
$this->lazyObject->__setInitializer($this->getSuggestedInitializerImplementation());
$this->lazyObject->__load();
}
public function testCloningWithPersister()
{
$this->lazyObject->publicTransientField = 'should-not-change';
$this
->proxyLoader
->expects($this->exactly(2))
->method('load')
->with(array(
'publicIdentifierField' => 'publicIdentifierFieldValue',
'protectedIdentifierField' => 'protectedIdentifierFieldValue',
))
->will($this->returnCallback(function () {
$blueprint = new LazyLoadableObject();
$blueprint->publicPersistentField = 'checked-persistent-field';
$blueprint->publicAssociation = 'checked-association-field';
$blueprint->publicTransientField = 'checked-transient-field';
return $blueprint;
}));
$firstClone = clone $this->lazyObject;
$this->assertSame(
'checked-persistent-field',
$firstClone->publicPersistentField,
'Persistent fields are cloned correctly'
);
$this->assertSame(
'checked-association-field',
$firstClone->publicAssociation,
'Associations are cloned correctly'
);
$this->assertSame(
'should-not-change',
$firstClone->publicTransientField,
'Transient fields are not overwritten'
);
$secondClone = clone $this->lazyObject;
$this->assertSame(
'checked-persistent-field',
$secondClone->publicPersistentField,
'Persistent fields are cloned correctly'
);
$this->assertSame(
'checked-association-field',
$secondClone->publicAssociation,
'Associations are cloned correctly'
);
$this->assertSame(
'should-not-change',
$secondClone->publicTransientField,
'Transient fields are not overwritten'
);
// those should not trigger lazy loading
$firstClone->__load();
$secondClone->__load();
}
public function testNotInitializedProxyUnserialization()
{
$this->configureInitializerMock();
$serialized = serialize($this->lazyObject);
/* @var $unserialized LazyLoadableObject|Proxy */
$unserialized = unserialize($serialized);
$reflClass = $this->lazyLoadableObjectMetadata->getReflectionClass();
$this->assertFalse($unserialized->__isInitialized(), 'serialization didn\'t cause intialization');
// Checking identifiers
$this->assertSame('publicIdentifierFieldValue', $unserialized->publicIdentifierField, 'identifiers are kept');
$protectedIdentifierField = $reflClass->getProperty('protectedIdentifierField');
$protectedIdentifierField->setAccessible(true);
$this->assertSame(
'protectedIdentifierFieldValue',
$protectedIdentifierField->getValue($unserialized),
'identifiers are kept'
);
// Checking transient fields
$this->assertSame(
'publicTransientFieldValue',
$unserialized->publicTransientField,
'transient fields are kept'
);
$protectedTransientField = $reflClass->getProperty('protectedTransientField');
$protectedTransientField->setAccessible(true);
$this->assertSame(
'protectedTransientFieldValue',
$protectedTransientField->getValue($unserialized),
'transient fields are kept'
);
// Checking persistent fields
$this->assertSame(
'publicPersistentFieldValue',
$unserialized->publicPersistentField,
'persistent fields are kept'
);
$protectedPersistentField = $reflClass->getProperty('protectedPersistentField');
$protectedPersistentField->setAccessible(true);
$this->assertSame(
'protectedPersistentFieldValue',
$protectedPersistentField->getValue($unserialized),
'persistent fields are kept'
);
// Checking associations
$this->assertSame('publicAssociationValue', $unserialized->publicAssociation, 'associations are kept');
$protectedAssociationField = $reflClass->getProperty('protectedAssociation');
$protectedAssociationField->setAccessible(true);
$this->assertSame(
'protectedAssociationValue',
$protectedAssociationField->getValue($unserialized),
'associations are kept'
);
}
public function testInitializedProxyUnserialization()
{
// persister will retrieve the lazy object itself, so that we don't have to re-define all field values
$this->proxyLoader->expects($this->once())->method('load')->will($this->returnValue($this->lazyObject));
$this->lazyObject->__setInitializer($this->getSuggestedInitializerImplementation());
$this->lazyObject->__load();
$serialized = serialize($this->lazyObject);
$reflClass = $this->lazyLoadableObjectMetadata->getReflectionClass();
/* @var $unserialized LazyLoadableObject|Proxy */
$unserialized = unserialize($serialized);
$this->assertTrue($unserialized->__isInitialized(), 'serialization didn\'t cause intialization');
// Checking transient fields
$this->assertSame(
'publicTransientFieldValue',
$unserialized->publicTransientField,
'transient fields are kept'
);
$protectedTransientField = $reflClass->getProperty('protectedTransientField');
$protectedTransientField->setAccessible(true);
$this->assertSame(
'protectedTransientFieldValue',
$protectedTransientField->getValue($unserialized),
'transient fields are kept'
);
// Checking persistent fields
$this->assertSame(
'publicPersistentFieldValue',
$unserialized->publicPersistentField,
'persistent fields are kept'
);
$protectedPersistentField = $reflClass->getProperty('protectedPersistentField');
$protectedPersistentField->setAccessible(true);
$this->assertSame(
'protectedPersistentFieldValue',
$protectedPersistentField->getValue($unserialized),
'persistent fields are kept'
);
// Checking identifiers
$this->assertSame(
'publicIdentifierFieldValue',
$unserialized->publicIdentifierField,
'identifiers are kept'
);
$protectedIdentifierField = $reflClass->getProperty('protectedIdentifierField');
$protectedIdentifierField->setAccessible(true);
$this->assertSame(
'protectedIdentifierFieldValue',
$protectedIdentifierField->getValue($unserialized),
'identifiers are kept'
);
// Checking associations
$this->assertSame('publicAssociationValue', $unserialized->publicAssociation, 'associations are kept');
$protectedAssociationField = $reflClass->getProperty('protectedAssociation');
$protectedAssociationField->setAccessible(true);
$this->assertSame(
'protectedAssociationValue',
$protectedAssociationField->getValue($unserialized),
'associations are kept'
);
}
public function testInitializationRestoresDefaultPublicLazyLoadedFieldValues()
{
// setting noop persister
$this->proxyLoader->expects($this->once())->method('load')->will($this->returnValue($this->lazyObject));
$this->lazyObject->__setInitializer($this->getSuggestedInitializerImplementation());
$this->assertSame(
'publicPersistentFieldValue',
$this->lazyObject->publicPersistentField,
'Persistent field is restored to default value'
);
$this->assertSame(
'publicAssociationValue',
$this->lazyObject->publicAssociation,
'Association is restored to default value'
);
}
public function testSettingPublicFieldsCausesLazyLoading()
{
$test = $this;
$this->configureInitializerMock(
1,
array($this->lazyObject, '__set', array('publicPersistentField', 'newPublicPersistentFieldValue')),
function () use ($test) {
$test->setProxyValue('publicPersistentField', 'overrideValue');
$test->setProxyValue('publicAssociation', 'newAssociationValue');
}
);
$this->lazyObject->publicPersistentField = 'newPublicPersistentFieldValue';
$this->assertSame('newPublicPersistentFieldValue', $this->lazyObject->publicPersistentField);
$this->assertSame('newAssociationValue', $this->lazyObject->publicAssociation);
}
public function testSettingPublicAssociationCausesLazyLoading()
{
$test = $this;
$this->configureInitializerMock(
1,
array($this->lazyObject, '__set', array('publicAssociation', 'newPublicAssociationValue')),
function () use ($test) {
$test->setProxyValue('publicPersistentField', 'newPublicPersistentFieldValue');
$test->setProxyValue('publicAssociation', 'overrideValue');
}
);
$this->lazyObject->publicAssociation = 'newPublicAssociationValue';
$this->assertSame('newPublicAssociationValue', $this->lazyObject->publicAssociation);
$this->assertSame('newPublicPersistentFieldValue', $this->lazyObject->publicPersistentField);
}
public function testCheckingPublicFieldsCausesLazyLoading()
{
$test = $this;
$this->configureInitializerMock(
1,
array($this->lazyObject, '__isset', array('publicPersistentField')),
function () use ($test) {
$test->setProxyValue('publicPersistentField', null);
$test->setProxyValue('publicAssociation', 'setPublicAssociation');
}
);
$this->assertFalse(isset($this->lazyObject->publicPersistentField));
$this->assertNull($this->lazyObject->publicPersistentField);
$this->assertTrue(isset($this->lazyObject->publicAssociation));
$this->assertSame('setPublicAssociation', $this->lazyObject->publicAssociation);
}
public function testCheckingPublicAssociationCausesLazyLoading()
{
$test = $this;
$this->configureInitializerMock(
1,
array($this->lazyObject, '__isset', array('publicAssociation')),
function () use ($test) {
$test->setProxyValue('publicPersistentField', 'newPersistentFieldValue');
$test->setProxyValue('publicAssociation', 'setPublicAssociation');
}
);
$this->assertTrue(isset($this->lazyObject->publicAssociation));
$this->assertSame('setPublicAssociation', $this->lazyObject->publicAssociation);
$this->assertTrue(isset($this->lazyObject->publicPersistentField));
$this->assertSame('newPersistentFieldValue', $this->lazyObject->publicPersistentField);
}
/**
* Converts a given callable into a closure
*
* @param callable $callable
* @return \Closure
*/
public function getClosure($callable) {
return function () use ($callable) {
call_user_func_array($callable, func_get_args());
};
}
/**
* Configures the current initializer callback mock with provided matcher params
*
* @param int $expectedCallCount the number of invocations to be expected. If a value< 0 is provided, `any` is used
* @param array $callParamsMatch an ordered array of parameters to be expected
* @param callable $callbackClosure a return callback closure
*
* @return \PHPUnit_Framework_MockObject_MockObject|
*/
protected function configureInitializerMock(
$expectedCallCount = 0,
array $callParamsMatch = null,
\Closure $callbackClosure = null
) {
if (!$expectedCallCount) {
$invocationCountMatcher = $this->exactly((int) $expectedCallCount);
} else {
$invocationCountMatcher = $expectedCallCount < 0 ? $this->any() : $this->exactly($expectedCallCount);
}
$invocationMocker = $this->initializerCallbackMock->expects($invocationCountMatcher)->method('__invoke');
if (null !== $callParamsMatch) {
call_user_func_array(array($invocationMocker, 'with'), $callParamsMatch);
}
if ($callbackClosure) {
$invocationMocker->will($this->returnCallback($callbackClosure));
}
}
/**
* Sets a value in the current proxy object without triggering lazy loading through `__set`
*
* @link https://bugs.php.net/bug.php?id=63463
*
* @param string $property
* @param mixed $value
*/
public function setProxyValue($property, $value)
{
$reflectionProperty = new \ReflectionProperty($this->lazyObject, $property);
$initializer = $this->lazyObject->__getInitializer();
// disabling initializer since setting `publicPersistentField` triggers `__set`/`__get`
$this->lazyObject->__setInitializer(null);
$reflectionProperty->setValue($this->lazyObject, $value);
$this->lazyObject->__setInitializer($initializer);
}
/**
* Retrieves the suggested implementation of an initializer that proxy factories in O*M
* are currently following, and that should be used to initialize the current proxy object
*
* @return \Closure
*/
protected function getSuggestedInitializerImplementation()
{
$loader = $this->proxyLoader;
$identifier = $this->identifier;
return function (LazyLoadableObject $proxy) use ($loader, $identifier) {
/* @var $proxy LazyLoadableObject|Proxy */
$proxy->__setInitializer(null);
$proxy->__setCloner(null);
if ($proxy->__isInitialized()) {
return;
}
$properties = $proxy->__getLazyProperties();
foreach ($properties as $propertyName => $property) {
if (!isset($proxy->$propertyName)) {
$proxy->$propertyName = $properties[$propertyName];
}
}
$proxy->__setInitialized(true);
if (method_exists($proxy, '__wakeup')) {
$proxy->__wakeup();
}
if (null === $loader->load($identifier, $proxy)) {
throw new \UnexpectedValueException('Couldn\'t load');
}
};
}
}