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

BUGFIXES: Processing of preconfigured settings and invokable resolution #242

Closed

Conversation

fhein
Copy link

@fhein fhein commented Jan 28, 2018

This PR adds tests and fixes for several issues around configuration and resolution of Invokables.

  1. Preconfigured abstract factories are not resolved.
  2. Preconfigured initializers are not resolved.
  3. Preconfigured invokables are not resolved.
  4. Invokables defined as name => InvokableClass can override delegators and factories.

A performance gain more than 100% in FetchNewServiceManagerBench.php is a welcome side effect.

@fhein fhein changed the title Test prefconfiguration gets applied ServiceManager does not respect confugaration supplied by child classes members Jan 28, 2018
@fhein fhein changed the title ServiceManager does not respect confugaration supplied by child classes members BUGFIXES: Processing of preconfigured settings and invokable resolution Jan 29, 2018
@fhein
Copy link
Author

fhein commented Jan 29, 2018

This is ready for review.

fhein added a commit to fhein/zend-servicemanager that referenced this pull request Jan 29, 2018
…igured settings

are processed. Resolves bug which could cause invokables to overwrite
delegators and factories. Removed tests which explicitly tested that
invokables were tranformed to aliases and factories (they are now stored
in a member array).
@fhein fhein force-pushed the TEST-Prefconfiguration-gets-applied branch from d2b4709 to 5aa73cc Compare January 29, 2018 22:48
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Jan 30, 2018
…igured settings

are processed. Resolves bug which could cause invokables to overwrite
delegators and factories. Removed tests which explicitly tested that
invokables were tranformed to aliases and factories (they are now stored
in a member array).
@fhein
Copy link
Author

fhein commented Jan 31, 2018

Heres the benchmark for ServiceManagerCreation. Done with --revs=500 --iterations=50.

$ vendor\bin\phpbench report --file=..\master.FetchNewServiceManager.xml --file=..\PR242.FetchNewServiceManager.xml --report=compare
benchmark: FetchNewServiceManagerBench
+----------------------------------+-------------------+------------------+
| subject                          | suite:master:mean | suite:PR242:mean |
+----------------------------------+-------------------+------------------+
| benchFetchServiceManagerCreation | 878.050µs         | 387.422µs        |
+----------------------------------+-------------------+------------------+

@fhein
Copy link
Author

fhein commented Jan 31, 2018

Here are the comparison reports for the rest of the benchmarks. Tests which feature the creation of invokables profit from the fix.

$ vendor\bin\phpbench report --file=..\master.all.xml --file=..\PR242.all.xml --report=compare
benchmark: FetchCachedServicesBench
+----------------------------------+-------------------+------------------+
| subject                          | suite:master:mean | suite:PR242:mean |
+----------------------------------+-------------------+------------------+
| benchFetchFactory1               | 0.452µs           | 0.459µs          |
| benchFetchInvokable1             | 0.473µs           | 0.474µs          |
| benchFetchService1               | 0.457µs           | 0.461µs          |
| benchFetchAlias1                 | 0.458µs           | 0.459µs          |
| benchFetchRecursiveAlias1        | 0.474µs           | 0.477µs          |
| benchFetchRecursiveAlias2        | 0.468µs           | 0.473µs          |
| benchFetchAbstractFactoryService | 2.450µs           | 2.529µs          |
+----------------------------------+-------------------+------------------+

benchmark: FetchNewServiceUsingConfigAbstractFactoryAsFactoryBench
+-------------------------------------+-------------------+------------------+
| subject                             | suite:master:mean | suite:PR242:mean |
+-------------------------------------+-------------------+------------------+
| benchFetchServiceWithNoDependencies | 5.042µs           | 4.823µs          |
| benchBuildServiceWithNoDependencies | 4.613µs           | 4.414µs          |
| benchFetchServiceDependingOnConfig  | 5.744µs           | 5.443µs          |
| benchBuildServiceDependingOnConfig  | 5.306µs           | 5.021µs          |
| benchFetchServiceWithDependency     | 5.681µs           | 5.402µs          |
| benchBuildServiceWithDependency     | 5.210µs           | 4.970µs          |
+-------------------------------------+-------------------+------------------+

benchmark: FetchNewServiceUsingReflectionAbstractFactoryAsFactoryBench
+-------------------------------------+-------------------+------------------+
| subject                             | suite:master:mean | suite:PR242:mean |
+-------------------------------------+-------------------+------------------+
| benchFetchServiceWithNoDependencies | 3.963µs           | 3.779µs          |
| benchBuildServiceWithNoDependencies | 3.537µs           | 3.314µs          |
| benchFetchServiceDependingOnConfig  | 7.089µs           | 6.746µs          |
| benchBuildServiceDependingOnConfig  | 6.650µs           | 6.303µs          |
| benchFetchServiceWithDependency     | 8.432µs           | 7.954µs          |
| benchBuildServiceWithDependency     | 7.960µs           | 7.559µs          |
+-------------------------------------+-------------------+------------------+

benchmark: FetchNewServiceViaConfigAbstractFactoryBench
+-------------------------------------+-------------------+------------------+
| subject                             | suite:master:mean | suite:PR242:mean |
+-------------------------------------+-------------------+------------------+
| benchFetchServiceWithNoDependencies | 5.489µs           | 5.228µs          |
| benchBuildServiceWithNoDependencies | 4.922µs           | 4.681µs          |
| benchFetchServiceDependingOnConfig  | 6.143µs           | 6.225µs          |
| benchBuildServiceDependingOnConfig  | 5.601µs           | 5.322µs          |
| benchFetchServiceWithDependency     | 6.122µs           | 5.825µs          |
| benchBuildServiceWithDependency     | 5.564µs           | 5.267µs          |
+-------------------------------------+-------------------+------------------+

benchmark: FetchNewServiceViaReflectionAbstractFactoryBench
+-------------------------------------+-------------------+------------------+
| subject                             | suite:master:mean | suite:PR242:mean |
+-------------------------------------+-------------------+------------------+
| benchFetchServiceWithNoDependencies | 3.434µs           | 3.304µs          |
| benchBuildServiceWithNoDependencies | 2.919µs           | 2.702µs          |
| benchFetchServiceDependingOnConfig  | 6.766µs           | 6.397µs          |
| benchBuildServiceDependingOnConfig  | 6.221µs           | 5.844µs          |
| benchFetchServiceWithDependency     | 8.095µs           | 7.682µs          |
| benchBuildServiceWithDependency     | 7.555µs           | 7.149µs          |
+-------------------------------------+-------------------+------------------+

benchmark: FetchNewServicesBench
+----------------------------------+-------------------+------------------+
| subject                          | suite:master:mean | suite:PR242:mean |
+----------------------------------+-------------------+------------------+
| benchFetchFactory1               | 2.820µs           | 2.839µs          |
| benchBuildFactory1               | 2.395µs           | 2.184µs          |
| benchFetchInvokable1             | 3.315µs           | 2.340µs          |
| benchBuildInvokable1             | 2.620µs           | 1.938µs          |
| benchFetchService1               | 0.455µs           | 0.468µs          |
| benchFetchFactoryAlias1          | 2.454µs           | 2.341µs          |
| benchBuildFactoryAlias1          | 2.461µs           | 2.340µs          |
| benchFetchRecursiveFactoryAlias1 | 2.475µs           | 2.340µs          |
| benchBuildRecursiveFactoryAlias1 | 2.490µs           | 2.341µs          |
| benchFetchRecursiveFactoryAlias2 | 2.497µs           | 2.340µs          |
| benchBuildRecursiveFactoryAlias2 | 2.473µs           | 2.392µs          |
| benchFetchAbstractFactoryFoo     | 2.407µs           | 2.408µs          |
| benchBuildAbstractFactoryFoo     | 1.947µs           | 1.892µs          |
+----------------------------------+-------------------+------------------+

benchmark: HasBench
+-------------------------+-------------------+------------------+
| subject                 | suite:master:mean | suite:PR242:mean |
+-------------------------+-------------------+------------------+
| benchHasFactory1        | 0.526µs           | 0.546µs          |
| benchHasInvokable1      | 0.603µs           | 0.589µs          |
| benchHasService1        | 0.482µs           | 0.511µs          |
| benchHasAlias1          | 0.584µs           | 0.612µs          |
| benchHasRecursiveAlias1 | 0.605µs           | 0.633µs          |
| benchHasRecursiveAlias2 | 0.603µs           | 0.629µs          |
| benchHasAbstractFactory | 0.839µs           | 0.895µs          |
| benchHasNot             | 0.851µs           | 0.911µs          |
+-------------------------+-------------------+------------------+

benchmark: SetNewServicesBench
+------------------------------------+-------------------+------------------+
| subject                            | suite:master:mean | suite:PR242:mean |
+------------------------------------+-------------------+------------------+
| benchSetService                    | 2.027µs           | 2.047µs          |
| benchSetFactory                    | 4.350µs           | 4.285µs          |
| benchSetAlias                      | 11.946µs          | 11.693µs         |
| benchOverrideAlias                 | 36.493µs          | 36.031µs         |
| benchSetInvokableClass             | 5.359µs           | 1.976µs          |
| benchAddDelegator                  | 2.090µs           | 2.109µs          |
| benchAddInitializerByClassName     | 2.473µs           | 2.518µs          |
| benchAddInitializerByInstance      | 1.764µs           | 1.815µs          |
| benchAddAbstractFactoryByClassName | 3.488µs           | 3.544µs          |
| benchAddAbstractFactoryByInstance  | 3.118µs           | 3.116µs          |
+------------------------------------+-------------------+------------------+

Copy link
Member

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

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

Overall, looks sane. My one hesitation is the change to how invokables are handled internally, as it results in test deletions. That said, the public behavior is well-tested, and the behavior changes have good tests.

@Ocramius — thoughts?

(Marking as requiring changes, as a commented-out line needs removal.)

@@ -640,7 +640,7 @@ public function testCanInjectInvokables()
$container = $this->createContainer();
$container->setInvokableClass('foo', stdClass::class);
$this->assertTrue($container->has('foo'));
$this->assertTrue($container->has(stdClass::class));
// $this->assertTrue($container->has(stdClass::class));
Copy link
Member

Choose a reason for hiding this comment

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

Please remove the line entirely, instead of commenting it out.

@fhein
Copy link
Author

fhein commented Feb 1, 2018

The tests deleted checked the particular implementation of invokables (i.e. the transformation to alias and factory). Kind of white box testing. As the implementation changed, these tests got obsolete. To be complete, I could have introduced a white box test verifying that an invokable really gets stored in the invokables array. I believe, that would be overdone a bit.

combination of alias and factory, which has changed.
Copy link
Member

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

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

One more change needed, as indicated, but I'll do that during merge.

Thanks!

*/
public function __invoke(ContainerInterface $container, $name, callable $callback, array $options = null)
{
return $callback($options);
Copy link
Member

Choose a reason for hiding this comment

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

This isn't correct.

$callback() should be called without any arguments; it essentially executes any previous delegators and/or the factory that produces the original service and returns the service.

$options is present for abstract plugin managers, to indicate what $options were provided to build(). Delegators will typically ignore them.

@weierophinney
Copy link
Member

@fhein I've merged locally. However, this patch is wildly different than what is on develop currently, with regards to invokables, which is making merging there quite difficult.

Should I leave the code on develop as-is, or attempt a merge?

@fhein
Copy link
Author

fhein commented Feb 1, 2018

Is there a way you could leave the pain up to me? I think, the bug fix is most important. I offer to take care of develop by rebasing if that's possible.

The main difference of this to develop is that alias handling was introduced to develop which is not contained in this PR.

But I will have a closer look on the diffs.

@fhein
Copy link
Author

fhein commented Feb 1, 2018

Puh. Aliases and setter optimization and minor adjustments.

Would a process like this work?

1, Merge this with master.
2, I'd create a PR based on master applying all differences of current develop.
3, Next? I am not gitalist enough to know. :(

*
* @var string[]|callable[]
*/
protected $invokables = [];
Copy link
Member

Choose a reason for hiding this comment

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

Since this is a new property, we should use private to avoid introducing even more BC issues.

];

for ($i = 0; $i <= self::NUM_SERVICES; $i++) {
$config['factories']["factory_$i"] = BenchAsset\FactoryFoo::class;
$config['aliases']["alias_$i"] = "service_$i";
$config['abstract_factories'][] = BenchAsset\AbstractFactoryFoo::class;
$config['invokables']['invokable_$i'] = BenchAsset\Foo::class;
Copy link
Member

Choose a reason for hiding this comment

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

$i is not being interpolated here

];

for ($i = 0; $i <= self::NUM_SERVICES; $i++) {
$config['factories']["factory_$i"] = BenchAsset\FactoryFoo::class;
$config['aliases']["alias_$i"] = "service_$i";
$config['abstract_factories'][] = BenchAsset\AbstractFactoryFoo::class;
$config['invokables']['invokable_$i'] = BenchAsset\Foo::class;
$config['delegators']['delegator_$i'] = [ DelegatorFactoryFoo::class ];
Copy link
Member

Choose a reason for hiding this comment

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

$i is not being interpolated here

$this->configure($config);
$this->configured = true;
Copy link
Member

Choose a reason for hiding this comment

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

This should be moved back into configure() to avoid BC issues - calling just the parent constructor may still lead a child class to change the value to false afterwards.

@@ -425,7 +434,7 @@ public function setAlias($alias, $target)
*/
public function setInvokableClass($name, $class = null)
{
$this->configure(['invokables' => [$name => $class ?: $name]]);
$this->configure(['invokables' => [ $name => (isset($class) ? $class : $name)]]);
Copy link
Member

Choose a reason for hiding this comment

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

To be replaced by ??

Copy link
Author

Choose a reason for hiding this comment

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

?? is PHP 7. Am I wrong?

Copy link
Member

Choose a reason for hiding this comment

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

This package has PHP 7.1 as minimum supported PHP version, so you can use all the fancy stuff

Copy link
Author

Choose a reason for hiding this comment

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

This is a PR against master.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, totally missed that =_=

@@ -151,40 +155,6 @@ public function testShareability($sharedByDefault, $serviceShared, $serviceDefin
$this->assertEquals($shouldBeSameInstance, $a === $b);
}

public function testMapsOneToOneInvokablesAsInvokableFactoriesInternally()
Copy link
Member

Choose a reason for hiding this comment

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

What is the reason for the removal of this test? While it is true that we should always treat the SUT as a black box, this test protected us against BC issues with subclassing (factories property is protected)

Copy link
Author

Choose a reason for hiding this comment

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

The reason for removal is that the implementation does not map invokables to factories at configuration time any more. See my comment above your review.

], 'factories', $serviceManager, 'Invokable object factory not found');
}

public function testMapsNonSymmetricInvokablesAsAliasPlusInvokableFactory()
Copy link
Member

Choose a reason for hiding this comment

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

Same here - while the test was indeed ugly (accessing object state) it exposes the BC boundary around protected $aliases

Copy link
Author

Choose a reason for hiding this comment

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

Same reason.

@@ -283,4 +253,92 @@ public function testFactoryMayBeStaticMethodDescribedByCallableString()
$serviceManager = new SimpleServiceManager($config);
$this->assertEquals(stdClass::class, get_class($serviceManager->get(stdClass::class)));
}

public function testMemberBasedConfigurationGetsApplied()
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to split this test into multiple ones? Ideally, the comments you put above each block should be the test method name

Copy link
Author

Choose a reason for hiding this comment

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

On command. :)


$object1 = $sm->build('factory1');
// assert delegated object is produced by delegator factory
$this->assertTrue(isset($object1->delegatorTag));
Copy link
Member

Choose a reason for hiding this comment

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

assertPropertyExist should probably be used here

$this->assertFalse(isset($object2->delegatorTag));
$this->assertInstanceOf(InvokableObject::class, $object2);

$sm->setInvokableClass('factory1', stdClass::class);
Copy link
Member

Choose a reason for hiding this comment

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

This should raise an exception due to already set service

@weierophinney
Copy link
Member

Would a process like this work?

1, Merge this with master.
2, I'd create a PR based on master applying all differences of current develop.
3, Next? I am not gitalist enough to know. :(

No, this doesn't work. It creates a merge conflict when we're ready to merge develop back to master in order to prep the next minor release, which means having to resolve it all again anyways. (Learned this the hard way.)

Looks like there are other changes requested, so I'll wait for now.

fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 1, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 2, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 2, 2018
…igured settings

are processed. Resolves bug which could cause invokables to overwrite
delegators and factories. Removed tests which explicitly tested that
invokables were tranformed to aliases and factories (they are now stored
in a member array).
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 2, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 2, 2018
@fhein
Copy link
Author

fhein commented Feb 6, 2018

Something more I should do?

fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 7, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 7, 2018
@fhein
Copy link
Author

fhein commented Feb 7, 2018

I noticed that is quite a lot of work to rebase develop to master after #242 was applied to master.

With PR #251 I provide a branch which represents a rebase of current develop onto (current master with PR #242 applied). This can obviously not get automatically be merged with develop, but it delivers a reference, how develop should look like after integration of PR #242.

I hope this helps. If I can do more, please let me know.

fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 8, 2018
@fhein
Copy link
Author

fhein commented Feb 8, 2018

This was replaced by #253. Please see documentation there.

@fhein fhein closed this Feb 8, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants