Different uses of ServiceLocatorInterface in ZF2 Application - php

I have 2 factories.
The first is a Controller Factory:
<?php
namespace Blog\Factory;
use Blog\Controller\ListController;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class ListControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
$postService = $realServiceLocator->get('Blog\Service\PostServiceInterface');
return new ListController($postService);
}
}
The second is a Post ServiceFactory:
<?php
namespace Blog\Factory;
use Blog\Service\PostService;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class PostServiceFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new PostService(
$serviceLocator->get('Blog\Mapper\PostMapperInterface')
);
}
}
Here is my module config:
<?php
return array(
'service_manager' => array(
'factories' => array(
'Blog\Service\PostServiceInterface' => 'Blog\Factory\PostServiceFactory'
)
),
'controllers' => array(
'factories' => array(
'Blog\Controller\List' => 'Blog\Factory\ListControllerFactory'
)
),
'router' => array(
// Open configuration for all possible routes
'routes' => array(
// Define a new route called "post"
'post' => array(
// Define the routes type to be "Zend\Mvc\Router\Http\Literal", which is basically just a string
'type' => 'literal',
// Configure the route itself
'options' => array(
// Listen to "/blog" as uri
'route' => '/blog',
// Define default controller and action to be called when this route is matched
'defaults' => array(
'controller' => 'Blog\Controller\List',
'action' => 'index',
)
)
)
)
),
'view_manager' => array(
'template_path_stack' => array(
__DIR__ . '/../view',
),
)
);
In the controller factory, I have to call getServiceLocator against the ServiceLocatorInterface, followed by the get call. however in the post service factory i just call get. I did a dump and it looks like both are the Zend\ServiceManager\ServiceManager classes. When I tried performing the getServiceLocator call against the post service factory service locator it errored no method found.
Im not quite understanding whats going on?

Controller factories are called in a different way than casual service factories. The ServiceLocator passed to createService is actually not the ServiceManager you are looking for but an instance of ControllerManager.
If you try this:
public function createService(ServiceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
$postService = $realServiceLocator->get('Blog\Service\PostServiceInterface');
var_dump(get_class($serviceLocator));
return new ListController(postService );
}
you'll get the output:
string(37) "Zend\Mvc\Controller\ControllerManager"
while the same dump in your PostServiceFactory will give you:
string(34) "Zend\ServiceManager\ServiceManager"
From the Zend 2 documentation:
http://framework.zend.com/manual/current/en/in-depth-guide/services-and-servicemanager.html#writing-a-factory-class
When using a Factory-Class that will be called from the ControllerManager it will actually inject itself as the $serviceLocator. However, we need the real ServiceManager to get to our Service-Classes. This is why we call the function getServiceLocator() who will give us the real ServiceManager.

Related

Zend framework - get config inside controller

I'm using this to build zend application. http://github.com/zendframework/ZendSkeletonApplication
I'm trying to get the config data I put inside the config/autoload/global.php and config/local.php.dist with the bottom line but it returns
Zend\ServiceManager\Exception\ServiceNotFoundException
and also
A plugin by the name "getServiceLocator" was not found in the plugin manager Zend\Mvc\Controller\PluginManager
Any idea how I can get the config?
$config = $this->getServiceLocator()->get('config');
The Master branch of ZendSkeletonApplication at the moment using Zend Framework 3. And getServiceLocator() in controller had been remove in Zend Framework 3.
So, if you wanna pass some variables from service to controller, you should create a factory. And pass the variables when instantiate the controller in factory.
Example:
Your controller name is IndexController from Application Module. And the factory class is IndexControllerFactory.
Application\Controller\IndexControllerFactory
<?php
namespace Application\Controller;
use Zend\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;
class IndexControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$config = $container->get("Config");
return new IndexController($config);
}
}
Application\Controller\IndexController
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class IndexController extends AbstractActionController
{
private $config;
public function __construct(array $config)
{
$this->config = $config;
}
public function indexAction()
{
// use $this->config here
}
}
and here the configuration in module.config.php
'controllers' => [
'factories' => [
Controller\IndexController::class => Controller\IndexControllerFactory::class
],
],
Hope this help
This is for clarification
In ZF3, if you are creating any classes that need in your application, make them serviceable, make them available in your application via ServiceManager. ServiceManager implements a container which stores registered services. So how is that? ZF uses a method called factory (in short, it creates object). It helps store services into container. We can then pull services from that container using ServiceManager. Let's see how?
ServiceManager is itself a service.
So using a factory let's make ServiceManager instance available in a controller (For example, IndexController). So that we can get any service using it.
Application\Controller\IndexControllerFactory
<?php
namespace Application\Controller;
// This is the container
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class IndexControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = NULL)
{
$serviceManager = $container->get('ServiceManager');
return new IndexController($serviceManager);
}
}
Let's register the IndexControllerFactory as a factory for IndexController so that we can use it. Make the following change in the module.config.php
'controllers' => [
'factories' => [
Controller\IndexController::class => Controller\IndexControllerFactory::class,
],
],
Once the IndexController is instantiated by IndexControllerFactory (by above configurations) the ServiceManager instance becomes available through IndexController's constructor.
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\ServiceManager\ServiceManager;
class IndexController extends AbstractActionController
{
protected $serviceManager;
public function __construct(ServiceManager $serviceManager)
{
// Here we set the service manager instance
$this->serviceManager = $serviceManager;
}
public function indexAction()
{
// Use this as you want
$config = $this->serviceManager->get('config');
return new ViewModel();
}
What if we need something from config service inside another class instead of the controller? For example, we want to upload images into a specific destination. So how would we fix the upload path? See the following example.
We will upload images through RenameUpload filter. It has an option named target which specifies the destination of upload path. Let's create another factory for upload filter.
Application\Controller\Form\Filter\UploadFilterFactory
<?php
namespace Application\Form\Filter;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Form\Filter\UploadFilter;
class UploadFilterFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = NULL)
{
$config = $container->get('config');
// Look! here we fix the upload path
$uploadPath = $config['module_config']['upload_path'];
// Here we're injecting that path
return new UploadFilter($uploadPath);
}
}
Do the same for the UploadForm if you need. This will be UploadFormFactory
Put the following two snippets in the module.config.php. This is for UploadFilterFactory.
'service_manager' => [
'factories' => [
// UploadForm::class => UploadFormFactory::class,
UploadFilter::class => UploadFilterFactory::class,
],
// Make an alias so that we can use it where we need
// it could be uploadAction() inside any controller
// $inputForm = $this->serviceManager->get('UploadForm');
// $inputFilter = $this->serviceManager->get('UploadFilter');
// $uploadForm->setInputFilter($inputFilter), for example
'aliases' => [
// 'UploadForm' => UploadForm::class,
'UploadFilter' => UploadFilter::class,
],
],
and this one for the upload path wherever you want to upload.
'module_config' => [
// Set the path as you want
'upload_path' => __DIR__ . '/../data/upload',
],
This is the Application\Form\Filter\UploadFilter.
<?php
namespace Application\Form\Filter;
use Zend\InputFilter\InputFilter;
use Zend\Filter\File\RenameUpload;
class UploadFilter extends InputFilter
{
protected $uploadPath;
public function __construct(string $uploadPath)
{
// We're assigning here so that we can use it
// on the filter section.
$this->uploadPath = $uploadPath;
$this->prepareFilters();
}
public function prepareFilters()
{
$this->add(array(
'name' => 'image',
'required' => true,
'filters' => array(
array(
'name' => RenameUpload::class,
'options' => array(
// Thus here we use it
'target' => $this->uploadPath,
'overwrite' => true,
'randomize' => true,
'use_upload_extension' => true,
),
),
),
'validators' => array(),
));
}
}
This is a one way of making things serviceable. So why is ServiceManager? This is for making scattered uses of objects stop. It removes hidden dependencies. This makes code clean and easier to understand. The principle is Good Design.
In order to do that you need to inject the config, as the getServiceLocator (and all other locators) have been removed from ZF3.
In your module configuration you have this:
'controllers' => [
'factories' => [
Controller\IndexController::class => InvokableFactory::class,
],
],
You can change the factory to create your own.
Controller\IndexController::class => Controller\IndexControllerFactory::class,
Here's the code:
final class IndexControllerFactory
{
public function __invoke(Container $container) : IndexController
{
$config = $container->get('config');
if (!isset($config['stuff']['stuff']) {
throw new \Exception('Please add the stuff.stuff parameter in the config');
}
$myParam = $config['stuff']['stuff'];
return new IndexController($myParam);
}
}
Container is a PSR container.
In your controller add a constructor to receive the config you need:
public function __construct(string $param)
{
$this->param = $param;
}
And here you have your config in your class, as an attribute.

How can I access Database Adapter in ZF2 Field Set?

I have followed an example and would like to pass the Database adapter to a fieldset to create a drop down menu.
The code below is how i call the fieldset.
How can i access the database adapter in the BrandFieldset class?
$this->add(array(
'type' => 'Application\Form\BrandFieldset',
'name' => 'brand',
'options' => array(
'label' => 'Brand of the product',
),
));
Instantiating a fieldset is responsibility of the FormElementManager. When you try to access a form, form element or fieldset, the FormElementManager knows where to find and how to create it. This behaviour summerized in Default Services section of the framework.
Since the proper way of accessing form elements is retrieving them from FormElementManager, I would write a BrandFieldsetFactory to inject that DB adapter or further dependencies to fieldset on construction to achieve this.
A ZF3 friendly fieldset factory would look like:
<?php
namespace Application\Form\Factory;
use Application\Form\BrandFieldset;
use Interop\Container\ContainerInterface;
class BrandFieldsetFactory
{
/**
* #return BrandFieldset
*/
public function __invoke(ContainerInterface $fem, $name, array $options = null)
{
// FormElementManager is child of AbstractPluginManager
// which makes it a ContainerInterface instance
$adapter = $fem->getServiceLocator()->get('Your\Db\Adapter');
return new BrandFieldset($adapter);
}
}
At this point, BrandFieldset should extend the Zend\Form\Fieldset\Fieldset and it's constructor may look like following:
private $dbAdapter;
/**
* {#inheritdoc}
*/
public function __construct(My/Db/Adapter $db, $options = [])
{
$this->dbAdapter = $db;
return parent::__construct('brand-fieldset', $options);
}
Finally, in module.config.php file I'd have a configuration to tell FormElementManager about this factory:
<?php
use Application\Form\BrandFieldset;
use Application\Form\Factory\BrandFieldsetFactory;
return [
// other config
// Configuration for form element manager
'form_elements' => [
'factories' => [
BrandFieldset::class => BrandFieldsetFactory::class
],
],
];
HINT: The BrandFieldset::init() method will be called automatically by FormElementManager after construction. You can put any post-initialization logic into this method.
Based of these docs I was able to find a solution.
https://framework.zend.com/manual/2.1/en/modules/zend.form.advanced-use-of-forms.html
'form_elements' => array(
'invokables' => array(
'fieldset' => BrandFieldsetFactory::class
)
)
I needed to call the form using the service locator in the controller like below.
$sl = $this->getServiceLocator();
$form = $sl->get('FormElementManager')->get('Application\Form\CreateForm');
In addition I changed the __construct to init.

How to add Javascript and CSS files to layout in ZF2

I am trying to learn ZF2 and I just want to specify Javascript and CSS files to be included in my layout. I currently pass an array of paths relative to my public directory to my view and then loop through them. I would like to make use of the built in ZF2 solution using:
$this->headScript();
$this->headStyle();
I have tried many suggested methods on similar questions, but I must not be following them correctly.
One of the solutions I tried which seemed to make sense was here by using either of these in my controller:
$this->getServiceLocator()->get('Zend\View\HelperPluginManager')->get('headLink')->appendStylesheet('/css/style.css');
$this->getServiceLocator()->get('viewhelpermanager')->get('headLink')->appendStylesheet('/css/style.css');
I am not sure what viewhelpermanager it seems like a placeholder the poster used, but I have seen it in more than one question. I went ahead and found the location of Zend\View\HelperPluginManager but that did not work either.
By "not working" I mean my page is displayed without CSS and there is zero output from these:
$this->headScript();
$this->headStyle();
It seems like such a simple task and I do not know why I am having this much of a difficulty.
EDIT #1:
Here is my controller:
<?php
namespace CSAdmin\Controller;
use Zend\View\Model\ViewModel;
use Zend\View\HelperPluginManager;
class LoginController extends AdminController
{
public function __construct() {
parent::__construct();
}
public function indexAction()
{
//Set Action specific Styles and Scripts
$viewHelperManager = $this->getServiceLocator()->get(`ViewHelperManager`);
$headLinkHelper = $viewHelperManager->get('HeadLink');
$headLinkHelper->appendStylesheet('/css/admin/form.css','text/css',array());
$headLinkHelper->appendStylesheet('/css/admin/styles.css','text/css',array());
//Override view to use predefined Admin Views
$view = new ViewModel(array('data'=>$this->data));
$view->setTemplate('CSAdmin/login/login.phtml'); // path to phtml file under view folder
//Set the Admin Layout
$layout = $this->layout();
$layout->setVariable('layout', $this->layoutVars);
$layout->setTemplate('layout/CSAdmin/login.phtml');
//Render Page
return $view;
}
My AdminController:
<?php
namespace CSAdmin\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class AdminController extends AbstractActionController
{
protected $data = array();
protected $layoutVars = array();
protected $viewHelper;
public function __construct() {
$this->layoutVars['customStyles'] = array();
$this->layoutVars['customScripts'] = array();
$this->layoutVars['miscCode'] = array();
//$this->viewHelper = $viewHelper;
}
}
EDIT #2:
#Wilt Error message for the above to controllers:
Line 19 is
$viewHelperManager = $this->getServiceLocator()->get("ViewHelperManager");
EDIT #3:
There are two modules involved here. Admin and CSAdmin, the controllers from Admin extend the controllers from CSAdmin and all of the controllers from CSAdmin extend a base controller within CSAdmin AdminController. AdminController extends AbstractActionController.
My controller and service_manager arrays for each module.config.php for both modules are below:
Admin:
'service_manager' => array(
'invokables' => array(
'CSAdmin\Form\LoginForm' => 'CSAdmin\Form\LoginForm'
),
'factories' => array(
)
),
'controllers' => array(
'invokables' => array(
),
'factories' => array(
'Admin\Controller\Login' => 'Admin\Factory\LoginControllerFactory',
)
),
// This lines opens the configuration for the RouteManager
'router' => array(
// Open configuration for all possible routes
'routes' => array(
'admin' => array(
'type' => 'literal',
'options' => array(
'route' => '/admin',
'defaults' => array(
'controller' => 'Admin\Controller\Login',
'action' => 'index',
),
),
'may_terminate' => true,
'child_routes' => array(
'home' => array(
'type' => 'literal',
'options' => array(
'route' => '/home',
'defaults' => array(
'controller' => 'Admin\Controller\Login',
'action' => 'home'
)
)
),
)
)
)
)
CSAdmin:
'service_manager' => array(
'invokables' => array(
),
'factories' => array(
'CSAdmin\Mapper\LoginMapperInterface' => 'CSAdmin\Factory\LoginMapperFactory',
'CSAdmin\Service\LoginServiceInterface' => 'CSAdmin\Factory\LoginServiceFactory'
)
),
'controllers' => array(
'invokables' => array(
),
'factories' => array(
'CSAdmin\Controller\Admin' => 'CSAdmin\Factory\AdminControllerFactory',
'CSAdmin\Controller\Login' => 'CSAdmin\Factory\LoginControllerFactory',
)
)
EDIT #4:
/module/Admin/src/Admin/Factory/LoginControllerFactory.php:
namespace Admin\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Admin\Controller\LoginController;
use CSAdmin\Service\LoginServiceInterface;
class LoginControllerFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
$loginService = $realServiceLocator->get('CSAdmin\Service\LoginServiceInterface');
$loginForm = $realServiceLocator->get('FormElementManager')->get('CSAdmin\Form\LoginForm');
return new LoginController(
$loginService,
$loginForm
);
}
}
/module/CSAdmin/src/CSAdmin/Factory/AdminControllerFactory.php:
namespace CSAdmin\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use CSAdmin\Controller\AdminController;
use Zend\View\Helper\BasePath;
class AdminControllerFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
//$viewHelper = $realServiceLocator->get('Zend\View\Helper\BasePath');
//return new AdminController($viewHelper);
return new AdminController();
}
}
/module/CSAdmin/src/CSAdmin/Factory/LoginControllerFactory.php:
namespace CSAdmin\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use CSAdmin\Controller\LoginController;
class LoginControllerFactory implements FactoryInterface
{
/**
* Create service
*
* #param ServiceLocatorInterface $serviceLocator
* #return mixed
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
$loginService = $realServiceLocator->get('CSAdmin\Service\LoginServiceInterface');
$loginForm = $realServiceLocator->get('FormElementManager')->get('CSAdmin\Form\LoginForm');
return new LoginController(
$loginService,
$loginForm
);
}
}
EDIT #5:
After correcting the type of quotes being used I am still not getting the stylesheets in my layout. As a test I change ->appendStylesheet() to ->someMethod() and it properly reports that the method does not exist. So it definitely has an instance of the HeadLink object. As a next step I decided to just try defining everything in the layout file and it still does not use the stylesheets. See below for the exact code used in the <head> tag of my layout file.
<?php echo $this->doctype(); ?>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?php echo $this->layout['title']; ?></title> //Intend to change eventually.
<?php
$this->headLink()->appendStylesheet('/css/admin/form.css');
$this->headLink()->appendStylesheet('/css/admin/styles.css');
echo $this->headScript();
echo $this->headStyle(); //This outputs nothing when viewing with the chrome debugger.
</head>
EDIT #6:
In order to get it to work, instead of using:
echo $this->headScript();
echo $this->headStyle();
I just had to do:
echo $this->headLink();
You will have to add echo to output the result...
echo $this->headScript();
echo $this->headStyle();
echo $this->headLink();
UPDATE
To get the Zend\View\HelperPluginManager in your controller you can do like this:
$viewHelperManager = $this->getServiceLocator()->get('ViewHelperManager');
Then you can do:
$headLinkHelper = $viewHelperManager->get('headLink');
UPDATE 2
Another thing, but it is a bit ridiculous, no wonder it was hard to find.
You used wrong quotes:
`ViewHelperManager` //You cannot use these: `
Try like this:
'ViewHelperManager'
or like this:
"ViewHelperManager"
you need to append file in this way
$this->headScript()->appendFile(
'/js/prototype.js',
'text/javascript',
array('conditional' => 'lt IE 7')
);
then you write it
echo $this->headScript();
note echo head script is required only one time. otherwise you inser js more time
more info at
http://framework.zend.com/manual/current/en/modules/zend.view.helpers.head-script.html

ZF2 Get ServiceManager from Controller Plugin Factory

I'm trying to create a new Controller Plugin using a factory to inject a dependency.
public function createService(ServiceLocatorInterface $serviceLocator) {
$services = $serviceLocator->getServiceLocator();
/** #var \Zend\Mvc\Controller\PluginManager */
$plugin = new MyPlugin();
if ($services->has('my_service')) {
$plugin->setService($services->get('my_service'));
}
return $plugin;
}
The problem is $services can't find 'my_service'
I've added the proper configurations in my service manager
'services' => array(
'invokables' => array(
'my_service' => 'Application\Service\MyService'
)
),
'controller_plugins' => array(
'factories' => array(
'my_plugin' => 'Application\Controller\Plugin\Factory\MyPlugin'
)
)
My thinking is it's a bug in the PluginManager where it isn't injecting the service manager properly.
I've added the proper configurations in my service manager
The key for service manager configuration is service_manager, not services which is why your invokable is not found, change the key ...
'service_manager' => array(
'invokables' => array(
'my_service' => 'Application\Service\MyService'
)
),
// ...

how to create a factory in zend framework 2?

in my Module.php i have the fallowing methods that i would like to move them in a factory class so that i wont clutter the Module class:
public function getControllerConfig()
{
return array(
'factories' => array(
'account-index' => function ($controllerManager) {
$serviceManager = $controllerManager->getServiceLocator();
$accountService = $serviceManager->get('account-service');
return new Controller\IndexController($accountService);
}
)
);
}
public function getServiceConfig()
{
return array(
'factories' => array(
'account-service' => function ($serviceManages) {
return new Service\Account;
}
)
);
}
right now i have:
and where shall i put this factory class, maybe in a Factory folder?
any ideas?
I usually put my factories into ../module/yourmodule/src/yourmodule/Factory.
in your ../module/yourmodule/config/module.config.php you then have to configure your service_manager like so:
'service_manager' => array(
'factories' => array(
'yourfactory' => 'yourmodule\Factory\yourfactory',
),
),
in yourfactory.php You then have to implent the FactoryInterface and set the service locator. Once you done this you should be able to call the service the usual way for controllers, forms etc.
namespace Application\Factory;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class yourfactory implements FactoryInterface
{
private $config;
private $serviceLocator;
public function createService(ServiceLocatorInterface $serviceLocator)
{
return $servicelocator->get('Your\Service');
}
After that you can just define functions in your yourfactory.php. In your Controller you call functions like so $serviceManager->get('yourfactory')->yourfunction(yourarguments);

Categories