Skip to content
This repository has been archived by the owner on Feb 6, 2020. It is now read-only.

[BUG] Own factories for plugins wont reset creationOptions #205

Merged
merged 4 commits into from
Nov 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ before_install:
- if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then composer require --dev --no-update satooshi/php-coveralls ; fi

install:
- COMPOSER_ROOT_VERSION=2.7.99 travis_retry composer install --no-interaction --ignore-platform-reqs
- COMPOSER_ROOT_VERSION=2.7.99 travis_retry composer install --no-interaction

script:
- if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/phpunit --coverage-clover clover.xml ; fi
Expand Down
42 changes: 39 additions & 3 deletions src/AbstractPluginManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ abstract public function validatePlugin($plugin);
public function get($name, $options = [], $usePeeringServiceManagers = true)
{
$isAutoInvokable = false;
$cName = null;
$sharedInstance = null;

// Allow specifying a class name directly; registers as an invokable class
if (!$this->has($name) && $this->autoAddInvokableClass && class_exists($name)) {
Expand All @@ -157,22 +159,58 @@ public function get($name, $options = [], $usePeeringServiceManagers = true)

$this->creationOptions = $options;

// If creation options were provided, we want to force creation of a
// new instance.
if (! empty($this->creationOptions)) {
$cName = isset($this->canonicalNames[$name])
? $this->canonicalNames[$name]
: $this->canonicalizeName($name);

if (isset($this->instances[$cName])) {
$sharedInstance = $this->instances[$cName];
unset($this->instances[$cName]);
}
}

try {
$instance = parent::get($name, $usePeeringServiceManagers);
} catch (Exception\ServiceNotFoundException $exception) {
if ($sharedInstance) {
$this->instances[$cName] = $sharedInstance;
}
$this->creationOptions = null;
$this->tryThrowingServiceLocatorUsageException($name, $isAutoInvokable, $exception);
} catch (Exception\ServiceNotCreatedException $exception) {
if ($sharedInstance) {
$this->instances[$cName] = $sharedInstance;
}
$this->creationOptions = null;
$this->tryThrowingServiceLocatorUsageException($name, $isAutoInvokable, $exception);
}

$this->creationOptions = null;

// If we had a previously shared instance, restore it.
if ($sharedInstance) {
$this->instances[$cName] = $sharedInstance;
}

try {
$this->validatePlugin($instance);
} catch (Exception\RuntimeException $exception) {
$this->tryThrowingServiceLocatorUsageException($name, $isAutoInvokable, $exception);
}

// If we created a new instance using creation options, and it was
// marked to share, we remove the shared instance
// (options === cannot share)
if ($cName
&& isset($this->instances[$cName])
&& $this->instances[$cName] === $instance
) {
unset($this->instances[$cName]);
}

return $instance;
}

Expand Down Expand Up @@ -321,10 +359,8 @@ protected function createServiceViaCallback($callable, $cName, $rName)
// duck-type MutableCreationOptionsInterface for forward compatibility
if (isset($factory)
&& method_exists($factory, 'setCreationOptions')
&& is_array($this->creationOptions)
&& !empty($this->creationOptions)
) {
$factory->setCreationOptions($this->creationOptions);
$factory->setCreationOptions(is_array($this->creationOptions) ? $this->creationOptions : []);
} elseif ($factory instanceof Factory\InvokableFactory) {
$factory->setCreationOptions(null);
}
Expand Down
71 changes: 68 additions & 3 deletions test/AbstractPluginManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Zend\ServiceManager\Factory\InvokableFactory;
use Zend\ServiceManager\ServiceManager;
use ZendTest\ServiceManager\TestAsset\Baz;
use ZendTest\ServiceManager\TestAsset\FactoryUsingCreationOptions;
use ZendTest\ServiceManager\TestAsset\FooPluginManager;
use ZendTest\ServiceManager\TestAsset\InvokableObject;
use ZendTest\ServiceManager\TestAsset\MockSelfReturningDelegatorFactory;
Expand Down Expand Up @@ -110,8 +111,9 @@ public function testMutableMethodNeverCalledWithoutCreationOptions()
{
$mock = 'ZendTest\ServiceManager\TestAsset\CallableWithMutableCreationOptions';
$callable = $this->getMock($mock, ['setCreationOptions']);
$callable->expects($this->never())
->method('setCreationOptions');
$callable->expects($this->once())
->method('setCreationOptions')
->with([]);

$ref = new ReflectionObject($this->pluginManager);

Expand Down Expand Up @@ -164,7 +166,70 @@ public function testInvokableFactoryNoOptionsResetsCreationOptions()
$plugin2 = $pluginManager->get(Baz::class);

$this->assertSame($creationOptions, $plugin1->options);
$this->assertNull($plugin2->options);
$this->assertEmpty($plugin2->options);
}

/**
* @group 205
*/
public function testRetrievingServicesViaFactoryThatUsesCreationOptionsShouldNotRememberServiceOnSubsequentRetrievals()
{
/** @var $pluginManager AbstractPluginManager */
$pluginManager = $this->getMockForAbstractClass('Zend\ServiceManager\AbstractPluginManager');
$pluginManager->setFactory(Baz::class, FactoryUsingCreationOptions::class);
$pluginManager->setShared(Baz::class, false);
$creationOptions = ['key1' => 'value1'];
$plugin1 = $pluginManager->get(Baz::class, $creationOptions);
$plugin2 = $pluginManager->get(Baz::class);

$this->assertNotSame($plugin1, $plugin2);

$this->assertSame($creationOptions, $plugin1->options);
$this->assertEmpty($plugin2->options);
}

/**
* @group 205
*/
public function testRetrievingServicesViaFactoryThatUsesCreationOptionsShouldReturnNewInstanceIfOptionsAreProvided()
{
/** @var $pluginManager AbstractPluginManager */
$pluginManager = $this->getMockForAbstractClass('Zend\ServiceManager\AbstractPluginManager');
$pluginManager->setFactory(Baz::class, FactoryUsingCreationOptions::class);
$pluginManager->setShared(Baz::class, false);
$creationOptions = ['key1' => 'value1'];
$plugin1 = $pluginManager->get(Baz::class);
$plugin2 = $pluginManager->get(Baz::class, $creationOptions);

$this->assertNotSame($plugin1, $plugin2);

$this->assertEmpty($plugin1->options);
$this->assertSame($creationOptions, $plugin2->options);
}

/**
* @group 205
*/
// @codingStandardsIgnoreStart
public function testRetrievingServicesViaFactoryThatUsesCreationOptionsShouldReturnNewInstanceEveryTimeOptionsAreProvided()
{
// @codingStandardsIgnoreEnd
/** @var $pluginManager AbstractPluginManager */
$pluginManager = $this->getMockForAbstractClass('Zend\ServiceManager\AbstractPluginManager');
$pluginManager->setFactory(Baz::class, FactoryUsingCreationOptions::class);
$pluginManager->setShared(Baz::class, false);
$creationOptions = ['key1' => 'value1'];
$plugin1 = $pluginManager->get(Baz::class, $creationOptions);
$plugin2 = $pluginManager->get(Baz::class);
$plugin3 = $pluginManager->get(Baz::class, $creationOptions);

$this->assertNotSame($plugin1, $plugin2);
$this->assertNotSame($plugin1, $plugin3);
$this->assertNotSame($plugin2, $plugin3);

$this->assertSame($creationOptions, $plugin1->options);
$this->assertEmpty($plugin2->options);
$this->assertSame($creationOptions, $plugin3->options);
}

public function testValidatePluginIsCalledWithDelegatorFactoryIfItsAService()
Expand Down
34 changes: 34 additions & 0 deletions test/TestAsset/FactoryUsingCreationOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* @see https://github.com/zendframework/zend-servicemanager for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://github.com/zendframework/zend-servicemanager/blob/master/LICENSE.md New BSD License
*/

namespace ZendTest\ServiceManager\TestAsset;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class FactoryUsingCreationOptions implements FactoryInterface
{
/**
* @var array
*/
private $creationOptions;

public function createService(ServiceLocatorInterface $serviceLocator)
{
return new Baz($this->creationOptions);
}

/**
* @param array $creationOptions
*
* @return void
*/
public function setCreationOptions(array $creationOptions)
{
$this->creationOptions = $creationOptions;
}
}