zendframework/zf2 — pull request #5535 by Thinkscape (2013-11-24 13:15:36+00:00)
Title: [SM] Fix AbstractServiceFactories with aliased services.

The point of **AbstractFactories** is to delegate creation of plethora of different services to a single factory. They receive the following arguments (per interface):

````php
interface AbstractFactoryInterface
{
public function canCreateServiceWithName(
ServiceLocatorInterface $serviceLocator,
$name,
$requestedName
);
public function createServiceWithName(
ServiceLocatorInterface $serviceLocator,
$name,
$requestedName
);
}
`````

Those arguments are used to determine what to create and return from the factory. For example, [DoctrineModule](https://github.com/doctrine/DoctrineModule) uses an [abstract factory to construct all doctrine namepaced components](https://github.com/doctrine/DoctrineModule/blob/master/src/DoctrineModule/ServiceFactory/AbstractDoctrineServiceFactory.php#L68) by regex-parsing the name of the service.

The problem surfaces when trying to use **Aliases on services created by abstract factory**.

### Example

We have 3 service classes associated with service names:

| Service Name | Real class name |
| --------------------- | --------------------- |
| service-foo | Application\Class\Foo |
| service-bar | Application\Class\Bar |
| service-baz | Application\Class\Baz |

.. and the following abstract factory

````php
class FooAliasedAbstractFactory implements AbstractFactoryInterface
{
public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $originalName = null)
{
$segments = explode('-', $requestedName);
if ($segments[0] != 'service') {
return false; // only
}
return class_exists('Application\Class\\' . $segments[1]);

}

public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $originalName = null)
{
$segments = explode('-', $requestedName);
$className = 'Application\Class\\' . $segments[1];

return new $className();
}
}
````

Now when we try to retrieve a service with `$sm->get()` we will receive the following arguments:

| Service Name | $name | $requestedName |
| --------------------- | ------------ | ---------------- |
| service-foo | servicefoo | service-foo |
| service-bar | servicebar | service-bar |
| service-baz | servicebaz | service-baz |

### What happens when we use aliases

Now let's add some aliases

````php
[
'aliases' => [
'Alias\Foo' => 'service-foo',
'Alias\Bar' => 'service-bar',
'Alias\Baz' => 'service-baz'
]
]
````

this will result in the following parameter values:

| Service Name | $name | $requestedName |
| --------------------- | ------------ | ---------------- |
| Alias\Foo | servicefoo | Alias\Foo |
| Alias\Bar | servicebar | Alias\Bar |
| Alias\Baz | servicebaz | Alias\Baz |

The `$requestedName` now contains the name of the requested alias. This is not a bug per se, because we could use it to create branching factories on alias names, however our abstract factory will stop working.

### Solution

I've modified ServiceManager to pass `$originalName` to the abstract factory methods, so that we know the name of the original class before aliasing and canonization.

This is one of the ways we could use this in our factory:

````php
class FooAliasedAbstractFactory implements AbstractFactoryInterface
{
public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $originalName = null)
{
if($originalName !== null){
$requestedName = $originalName;
}

$segments = explode('-', $requestedName);
if ($segments[0] != 'service') {
return false; // only handle service-*
}
return class_exists('Application\Class\\' . $segments[1]);

}

public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $originalName = null)
{
if($originalName !== null){
$requestedName = $originalName;
}

$segments = explode('-', $requestedName);
$className = 'Application\Class\\' . $segments[1];

return new $className();
}
}
````

And parameter values will become:

| Service Name | $name | $requestedName | $originalName |
| --------------------- | ------------ | ---------------- | ---------------- |
| Alias\Foo | servicefoo | Alias\Foo | service-foo |
| Alias\Bar | servicebar | Alias\Bar | service-bar |
| Alias\Baz | servicebaz | Alias\Baz | service-baz |

### What's changed

The PR is fully backward compatible. I have not modified `AbstractFactoryInterface` and the extra parameter is considered as optional for all internal SM methods. Existing factories work the same and require no changes.

We will probably deprecate name canonization in ZF3 so this will no longer be needed, however for 2.* there is full BC and allow for smarter abstract factories while remaining fully compatible with aliasing.