I have a Class object which is used in many modules in my zend structure :
/module/
--|Aplication
--|MyClassModule
----|config
----|src
------|Factory
------|Model
---------|> MyObjectClass.php
----Module.php
--|AnotherModule
So my idea is to use this MyObjectClass.php in other modules so I can avoid duplication and have its own configuration. So far, for this is ok, however I want to get the variables set from my config/autoload files injected in this class but I don't know how.
How can I load this config data into my class model? Which is the best approach ? I can load it by accessing this directly but I don't think this is very elegant
e.g: $configArray = require './config/autoload/config.local.php';
I am not very experienced with zend so I dont know where to start with. I have seen many tutorials of how to do this via controllers, views.. etc but not in specific classes.
Thank you.
All config files are merged into one config, when your ZF2 application is bootstrapped. That includes local.php, global.php from config/autoload and all used modules' module.config.php. With a bit of more research, you can overwrite the standard loading, e.g. loading custom configs.
After bootstrapping, your are able to access the config from the ServiceManager. There are preserved keys for some ZF2-specific configs, service_manager, etc.
$serviceManager->get('config');
There is a "standard" service pattern in ZF2: Factory. This can be applied for Controllers, Services. What ever you want.
namespace Application\Factory;
use Application\Model\MyObjectClass;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class MyObjectFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
// get some config parameter, inject it into model
$config = $serviceLocator->get('config');
$myObjectClass = new MyObjectClass();
// ... e.g. $myObjectClass->setConfig($config);
return $myObjectClass;
}
}
It should be clear, what this factory is made for: create and return an instance of your custom object ;) You may configure your instance with some config params. With ServiceLocator as method param, you are able to access the config, other services etc.
Further, you have to register your own service/factory in the factories section of service_manager config in your module's module.config.php:
return array(
'service_manager' => array(
'factories' => array(
'MyObjectFactory' => 'Application\Factory\MyObjectFactory',
),
),
);
Now you should be able to access your factory, e.g. in an ActionController or wherever you have access to ServiceManager. That means, you can also access this factory from different modules.
public function someCustomAction() {
$myObjectClass = $this->getServiceLocator()->get('MyObjectFactory');
$myObjectClass2 = $this->getServiceLocator()->get('MyObjectFactory');
var_dump($myObjectClass);
var_dump($myObjectClass2);
if ($myObjectClass === $myObjectClass2) {
echo '<br />equal';
}
$myObjectClass = new MyObjectClass();
$myObjectClass2 = new MyObjectClass();
var_dump($myObjectClass);
var_dump($myObjectClass2);
}
Note:
Be aware, that ServiceManager returns the same instance of your object. So, that seems like what you ask for? In contrast, creating a new instance will create different objects.
Note 2:
Tested with ZF2 v2.4.9
Related
I'm using Apigility, built on ZF2. Once request is dispatched to the controller's action, I need to choose proper adapter to hanle request - based on incomming parameters.
Normally, Controller is instantiated by ControllerFactory, where you can provide all dependencies, let say I need som kind of mapper class to be injected. It's easy, if I know, which one I will use in within the controller. It's problematic if I need to let controller decide which mapper to use.
Let's say user is requesting something like getStatus with param 'adapter1' and another user is accessing same action, but with param 'adapter2'.
So, I need to inject adapter1 mapper OR adapter2 mapper, which has similar interface, but different constructor.
What's the proper way how to handle this situation ?
On possible solution is to supply some kind of factory method, which will provide requested adapter, but - using the SM int the model class should be avoided.
Another way is to use SM directly in within Controller's action, but this in not best approach, because I can't reuse 'switch-case' logic for another actions / controllers.
How to handle this, please ?
You could use controller plugins for this.
Like that you can get the adapter inside your controller when you need it without injecting a ServiceManager and without adding all the logic to the factory. The adapter will only be instantiated when you request it in your controller action method.
First you need to create your controller plugin class (extending Zend\Mvc\Controller\Plugin\AbstractPlugin):
<?php
namespace Application\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class AdapterPlugin extends AbstractPlugin{
protected $adapterProviderService;
public function __constuct(AdapterProviderService $adapterProviderService){
$this->adapterProviderService = $adapterProviderService;
}
public function getAdapter($param){
// get the adapter using the param passed from controller
}
}
Then a factory to inject your service in the class:
<?php
namespace Application\Controller\Plugin\Factory;
use Application\Controller\Plugin\AdapterPlugin;
class AdapterPluginFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceController
* #return AdapterPlugin
*/
public function createService(ServiceLocatorInterface $serviceController)
{
$serviceManager = $serviceController->getServiceLocator();
$adapterProvicerService = $serviceManager>get('Application\Service\AdapterProviderService');
return new AdapterPlugin($adapterProviderService);
}
}
Then you need to register your plugin in your module.config.php:
<?php
return array(
//...
'controller_plugins' => array(
'factories' => array(
'AdapterPlugin' => 'Application\Controller\Plugin\Factory\AdapterPluginFactory',
)
),
// ...
);
Now you can use it inside your controller action like this:
protected function controllerAction(){
$plugin = $this->plugin('AdapterPlugin');
// Get the param for getting the correct adapter
$param = $this->getParamForAdapter();
// now you can get the adapter using the plugin
$plugin->getAdapter($param);
}
Read more on controller plugins here in the documentation
The latest update of zend-mvc has caused a break in compatibility due to phasing out ServiceLocatorAwareInterface. I use the servicelocator within a controller to dynamically load dependencies, eg:
class IndexController extends AbstractActionController
/**
*
* #return \UserManagement\Form\User\Details
*/
protected function getUserDetailsForm(){
return $this->getFormManager()->get('User\Details');
}
/**
*
* #return FormElementManager
*/
protected function getFormManager(){
return $this->getServiceLocator()->get('FormElementManager');
}
}
This is now raising an exception (E_USER_DEPRECEATED) with the following message:
You are retrieving the service locator from within the class
User\Controller\IndexController. Please be aware that
ServiceLocatorAwareInterface is deprecated and will be removed in
version 3.0, along with the ServiceLocatorAwareInitializer. You will
need to update your class to accept all dependencies at creation,
either via constructor arguments or setters, and use a factory to
perform the injections.
My question is, what is the best way of getting the forms into the controller? My service layer and other controller-specific dependencies are injected into the constructor, but i don't really want to polluate a constructor with all the forms that a controller may need, nor do i want the overhead of creating form objects that will not be used. The forms cannot be created in the controller ie $form = new Form() as they are also created dynamically eg:
Module.php
public function getFormElementConfig ()
{
return array(
'factories' => array(
'User\Details' => function($sm){
$userMapper = $sm->getServiceLocator()->get('Model\User\Mapper');
$form = new \User\Form\Details($userMapper);
return $form;
}
)
);
}
Have more and more specific controllers.
That way you can instantiate one controller and then have to inject exactly all the objects that are definitely needed to perform any task you may need.
There is no use to combine actions into one single controller class if all they share is a common URL path fragment. From the software design point of view, a class that does plenty of independent things in different methods is just doing too much. And doing too much is reflected in the number of dependencies you have to inject.
There are few solutions.
Make controllers smaller. Less methods, less dependencies. However, more factories.
Use the ZF2 proxy manager. It essentially replaces expensive object instantiation with proxy objects.
Another option would be to add a wrapper or container that lazy loads the forms via the form element manager. You can then inject this container into the controller or service layer. Using a service locator in this way isn't "ideal" as you loose the explicitly defined class dependencies.
I have a FormElementLazyProvider class and its associated factory that might be worth checking out.
For example.
$elementConfig = [
'create' => 'MyModule\Form\CreateForm',
'edit' => 'MyModule\Form\EditForm',
'delete' => 'MyModule\Form\DeleteForm',
];
$factory = function($serviceName, $elementName) use ($formElementManager) {
if ($formElementManager->has($serviceName)) {
return $formElementManager->get($serviceName);
}
return false;
};
$provider = new \ArpForm\Service\FormElementLazyProvider($factory, $elementConfig);
$provider->hasElement('create'); // true
$provider->getElement('edit'); // Callback executed and form object return
The crux of the problem is the service locator hiding the dependencies of the controller class.
The Zend framework is instructing you to move away from the ServiceRepository pattern and use proper dependency injection using DI containers or using constructor injection or setter injection. You can use a factory to inject the dependencies as well.
Please read about Service Repository which many see it as an anti-pattern.
In my job I am dealing with a legacy app running on ZF2. There is a model which is sending out a variety of different emails to difference addresses. The one thing they have in common is they all need to BCC to one particular address.
At first, in my head I was cursing the previous developer, for foolishly hard coding the email address 20 different times in one file. I assumed that it would be a piece of cake to grab an application config with a simple call $this->config->get('x') (like in Laravel) or something along them lines. Now I find myself feeling bad, because I understand why the previous dev did hard code the email addresses.
So to the question, how the hell do I grab a config item from application.config.php inside the model? I keep reading about how I need to implement the ServiceLocaterAware Interface. Is this really necessary? There must be a way to grab configs easily, surely?!?
How the hell do I grab a config item from application.config.php inside the model?
You shouldn't do so inside, do it 'outside'.
Register your model class as a service in module.config.php.
'service_manager' => [
'factories' => [
'Email\Model\EmailModel' => 'Email\Model\EmailModelFactory',
]
],
Then create the factory Email\Model\EmailModelFactory, this uses the service manager to fetch the 'email' config key and injects it into the model's constructor.
namespace Email\Model;
use Email\Model\EmailModel;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class EmailModelFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new EmailModel($this->getEmailOptions($serviceLocator));
}
// Return the email options
public function getEmailOptions(ServiceLocatorInterface $serviceLocator)
{
$options = $serviceLocator->get('config');
return $options['email'];
}
}
The issue you will now have is all calls to your model classes will have to use $serviceManager->get('Email\Model\EmailModel') (rather than new \Email\Model\EmailModel) in order for the configuration to be injected. Even without seeing any of your legacy application my guess is that this would be difficult.
The model should not be responsible for sending emails; you could replace it with an service class, e.g. 'EmailService' and repeat the injection example above just for this class.
EmailService::send(EmailModel $email, array $options);
This would keep your model independent and there would be no need to replace the calls to new Model etc.
You need the service locator / service manager
Within your controller:
public function xxxAction()
{
$sm = $this->getServiceLocator();
$config = $sm->get('config');
}
I would like to override the default View::make() method in Laravel, which can be used to return a view-response to the user.
(I think) I've already figured out that this method is stored inside Illuminate\View\Factory.php, and I've been reading about IoC container, while trying to make it work using some similar tutorials, but it just won't work.
I've already created a file App\Lib\MyProject\Extensions\View\Factory.php, containing the following code:
<?php namespace MyProject\Extensions\View;
use Illuminate\View\Factory as OldFactory;
class Factory extends OldFactory {
public static function make() {
// My own implementation which should override the default
}
}
where the MyProject folder is autoloaded with Composer. But I don't know how to use this 'modified' version of the Factory class whenever a static method of View (in particular View::make()) is being called. Some help would be great!
Thanks!
The call to View::make is, in Laravel speak, a call to the View facade. Facades provide global static access to a service in the service container. Step 1 is lookup the actual class View points to
#File: app/config/app.php
'aliases' => array(
'View' => 'Illuminate\Support\Facades\View',
)
This means View is aliased to the class Illuminate\Support\Facades\View. If you look at the source of Illuminate\Support\Facades\View
#File: vendor/laravel/framework/src/Illuminate/Support/Facades/View.php
class View extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'view'; }
}
You can see the facade service accessor is view. This means a call to
View::make...
is (more or less) equivalent to a call to
$app = app();
$app['view']->make(...
That is, the facade provides access to the view service in the service container. To swap out a different class, all you need to do is bind a different object in as the view service. Laravel provides an extend method for doing this.
App::extend('view', function(){
$app = app();
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$env = new \MyProject\Extensions\View($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
Notice this is more than just instantiating an object. We need to instantiate it the same way as the original view service was instantiated and bound (usually with either bind or bindShared). You can find that code here
#File: vendor\laravel\framework\src\Illuminate\View\ViewServiceProvider.php
public function registerFactory()
{
$this->app->bindShared('view', function($app)
{
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$env = new Factory($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
}
You can test if your binding worked with code like this
var_dump(get_class(app()['view']));
You should see your class name. Once your sure the binding has "taken", you're free to redefine whatever methods you want.
I've been trying to figure it out by myself reading online and applying tons of answers form here, but to no avail unfortunately.
I have two Modules on my zf2 application, one called Services and one called Agent.
Now, in my Services module everything seems to work fine, I can get my serviceLocator, hence my configuration, and work with it. In my Agent Module's controller however, I don't seem to be able to do the same.
This is part of my AgentController:
use Zend\Mvc\Controller\AbstractActionController;
class AgentController extends AbstractActionController
{
protected $serviceLocator;
public function ValidateAction()
{
$this->serviceLocator = $this->getServiceLocator()->get('config');
//... Using the config
}
}
In my module.cofig.php I have the following:
'controllers' => array(
'invokables' => array(
'Agent\Controller\Agent' => 'Agent\Controller\AgentController',
),
),
I have tried many solutions: changing and adding methods to the Module.php, changing the module.config etc.. Where am I wrong?
Thanks,
Andrea
The class variable $this->serviceLocator is used by the controller class to hold the service locator instance. In your example you assigning the config array to this variable (thus replacing the service locator instance with an array). Subsequent calls to $this->getServiceLocator() will then return the config array instead of the service locator object, which is the likely cause of the error you're getting.
I'd suggest either using a local variable instead:
public function ValidateAction()
{
$config = $this->getServiceLocator()->get('config');
//... Using the config
}
or assigning to a class variable with a different name:
public function ValidateAction()
{
$this->config = $this->getServiceLocator()->get('config');
//... Using the config
}