I'm trying to understand some principles of the ZendFramework 2, and I want to know why to inject services on the ZF2 module.config.php if I can load instance a object using composer autoloader. The same thing for zend factories, why to use this if I can easily implement a Factory class and call it in a controller?
问题:
回答1:
To be clear up front - the ServiceLocator
is not a must use
way for creating objects. You still can create objects directly, and in quite a few cases this is a perfectly valid approach.
With that being said, there are a few reasons why you want to use the ServiceLocator
for some objects. Unfortunately the Wikipedia Page on this doesn't help much with understanding the reasons to use a service locator. I found the description and examples here much more useful.
For the remainder of this, I'll use the terms class
and service
interchangeable. I'll name the class/object requiring a service Consumer
, the factory responsible for creating a service a factory
.
There is one very important fact to know about service managers. The service name does not need to be a class name. It can be any string (e.g. the services' name).
The major reasons why you want to use a service locator are:
- Use the same instance of a service and many places (shared services)
- Abstract the means of service
constructions
from the consumer (is it created by a factory or instantiated directly) - Decouple modules from each other
- Create abstract services (identified by any string), and let others decide on the concrete implementation of this service
- Extend service creation using delegators
Re-use services
This is probably the easiest to see. Typically you have a service and want the same instance to be used across your application. Use-cases for this could be anything from a Configuration
service to Caching Service
.
We sure could use a Singleton for making sure that only one instance is created globally - and this would work as well. But this doesn't provide the benefits of the other arguments following.
In Zf2, all services are shared by default. So each time you request a service by name, you will get the same instance. This can be turned of on a per-service level (that's the shared
config key for in the service_manager
configuration).
Abstract means of creation
Ideally, the consumer should not need to know how a required service is to be created. This makes it more decoupled from the required service. All it needs to know, is to say I require an instance of this service
.
Using a service manager, you can achieve this: you ask the service manager to fetch a specific service, But you don't care how the service manager actually creates this. It could be that the service manager instantiates it directly. It could be a factory. The consumer doesn't care - all it only cares about the result, an instance of that service.
The benefit of doing so is that you can replace the mean of creating that service transparently and don't have to update the consumer code to the new way of creating that service.
Decoupling modules
Hiding the way of how a service is created leads to decoupling modules. Let's assume you have two modules Cache
and User
. The user
module requires a Cache
Service from the Cache
module. Instead of having to rely on the Cache
Module and implementation directly, you hide that fact by a service named cache
. The user
module doesn't care at all how the Cache
module creates that service. In fact we could even replace the Cache
module with another module AdvancedCache
providing the same service, without having to update the User
module. The User
module isn't tied to a particular module - only the the existence of a service named cache
.
Abstract service names
In the previous example, I already introduced this concept. Each service has a global name - a simple string. In many cases that is the class name, but we are not limited to this.
To stick the the previous example of a cache
service: in here we didn't specify which type of cache the User
Module actually wants - it doesn't care. It could be an array
(in-memory) cache, a file
cache, or memcache
cache.
In some other place we decide which cache we want to use globally and assign that concrete implementation to the name of cache
. All modules requiring that cache
service now automatically receive that implementation without needing to be updated.
In Zf2, this is used quite extensively. Take the Request
Service for example. In many cases this is an instance of the Http\Request
. But when using the CLI, this would be an Console\Request
instead. But when requesting the Request
service, we literally don't care about which one re receive, as long as they behave the same.
Another example for this would be the Renderer
. Depending on the environment we want to render the output for, we receive a different implementation (HTML/Json/Console usage). But our consumer doesn't need to know which concrete implementation we are rendering for/.
Delegators
Delegators can be used to extend
a factory. I'll leave this out of scope for now though as this is a more advanced use-case. So much be said, you can extend/proxy service creations upon creation time.
回答2:
In completion of @Fge answer iI will show you an example I experimented myself on how service manager can be useful, and when it's not.
I make a dynamic form wich on a few elements I had to add some filters and some validators.
I show you my custom service's method that doi this job for me :
public function setValidators($form, $name, $validator, $nameFieldset)
{
$optionsSpace = [
'translator' => $this->translator,
'type' => NotEmpty::SPACE
];
$optionsString = [
'translator' => $this->translator,
'type' => NotEmpty::STRING
];
$optionsDigits = [
'translator' => $this->translator,
];
$form->getInputFilter()->get('items')
->get($nameFieldset)
->get($name)
->setRequired(true)
->getValidatorChain()
->attach($validator, true, 1)
->attach(new NotEmpty($optionsSpace), true, 2)
->attach(new NotEmpty($optionsString), true, 2)
->attach(new Digits($optionsDigits), true, 2);
return $form;
}
You can see that I use some arrays, and some Zend\Validators.
You also see that I use $this->translator
on each options Array that I pass to those validators. Why I pass the translator ? Let's have a look to the validator :
class NotEmpty extends AbstractValidator
And the abstract Validator
abstract class AbstractValidator implements
Translator\TranslatorAwareInterface,
ValidatorInterface
If you use the service manager the TranslatorAwareInterface
will be instanciated by an initializer. Wich, if you declared the new validator directly in you service, will not be the case, so you have to set it in your options.