View helper plugin extending in ZF2 - php

I want to extend, for example, Zend\View\Helper\HeadMeta with my own class and I create a factory for it and call it by
public function getViewHelperConfig()
{
return array(
'factories' => array(
'MyHeadMeta' => __NAMESPACE__ . '\View\Helper\Service\MyHeadMetaService',
),
);
}
from Module.php, but I have
$this->view
is null in MyHeadMeta class if I call it by
$this->MyHeadMeta()->setCharset('utf-8');
in my view file.
How do I instantiate my view helper properly?
UPDATE
My class looks something like this:
MyHeadMeta.php
use Zend\View\Helper\HeadMeta;
class MyHeadMeta extends HeadMeta
{
//
}
UPDATE 2
MyHeadMetaService.php
class MyHeadMetaService implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$viewHelper = new MyHeadMeta();
// some settings to set...
return $viewHelper;
}
}

If you only extend the existing HeadMeta class then you should register it as an invokable as is done with the original HeadMeta view helper in the HelperPluginManager.
So change your config like this:
return array(
'invokables' => array(
'MyHeadMeta' => 'View\Helper\Service\MyHeadMetaService'
)
);
By the way I don't think it is necessary to use the full path with __NAMESPACE__. Just make sure the name points to the correct file and folder path of your class in the current module and declare the namespace constant in the class.

Related

ZF2 Getting Autoloaded config info in a custom class

I have been racking my brain now for the better part of two days. I'm using Zend Apigility to create a RESTful web API application. Apigility builds its application using ZF2.
I created a custom class that I use throughout my API.
I would like to read in some autoloaded configuration information to make a connection to an memcache server. The file that is being autoloaded into the service manager is:
memcache.config.local.php:
return array(
'memcache' => array(
'server' => '10.70.2.86',
'port' => '11211',
),
);
My custom class that my REST services are calling is called checkAuth:
checkAuth.php:
namespace equiAuth\V1\Rest\AuthTools;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class checkAuth implements ServiceLocatorAwareInterface{
protected $services;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->services = $serviceLocator;
}
public function getServiceLocator()
{
return $this->services;
}
public function userAuths() {
//** Some Code
$config = $this->getServiceLocator()->get('config');
// **
}
}
I believe I'm injecting the service manager into the class from my module.config.php with the following code:
'service_manager' => array(
'invokables' => array(
'checkAuth' => 'equiAuth\V1\Rest\AuthTools\checkAuth',
),
),
When I hit the code when I'm trying to read the 'config' from the get method of the ServiceLocator I get the following error:
Fatal error: Call to a member function get() on a non-object
I know I'm missing something, but I cant for the life of me figure out what.
Give your class an API that allow's you to 'set' the configuration from client code. This could be via the constructor or
a public setter.
namespace equiAuth\V1\Rest\AuthTools;
class CheckAuth
{
protected $config;
public function __construct(array $config = array())
{
$this->setConfig($config);
}
public function setConfig(array $config)
{
$this->config = $config;
}
public function doStuff()
{
$server = $this->config['server'];
}
}
In order to 'set' the configuration you would also need to also create a service factory class. The idea in the factory is to give you an area to inject the configuration in to the service; with the updates to CheckAuth above we can now do so very easily.
namespace equiAuth\V1\Rest\AuthTools;
use equiAuth\V1\Rest\AuthTools\CheckAuth;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class CheckAuthFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$config = $serviceLocator->get('config');
return new CheckAuth($config['memcache']);
}
}
Lastly, change the registered service with the service manager; the change here is service key form invokables to factories as we need to register the
above factory to create it.
// module.config.php
'service_manager' => array(
'factories' => array(
'checkAuth' => 'equiAuth\V1\Rest\AuthTools\CheckAuthFactory',
),
),
ZF2 use ServiceManager Container as well.
Your code is right at all, but
To auto-inject the servicelocator on your class you just need to use
$checkAuth = $this->getServiceLocator()->get('checkAuth');
then you can call
$checkAuth->userAuths();
and should work.
If you try to use:
$checkAuth = new \equiAuth\V1\Rest\AuthTools\checkAuth();
$checkAuth->userAuths(); //error
Will not work because what inject the serviceLocator into your class is just the
ServiceManager, once you use serviceManager you need to be evangelist with them.
But if you try:
$checkAuth = new \equiAuth\V1\Rest\AuthTools\checkAuth();
$checkAuth->setServiceLocator($serviceLocator)
//get $serviceLocator from ServiceManager Container
$checkAuth->userAuths();
Will work too.
Good job!

How to make a view helper

...//
EDIT:
I have resolved my problem and I want to share the solution.
The better way to make a Zend Framework 2 Helper:
Step 1.
<?php
//file : App_folder/module/Module_name/src/Module_name/View/Helper/SayHello.php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
class SayHello extends AbstractHelper{
public function __invoke($name = 'Unnamed'){
return "Hello $name,";
}
}
?>
Step 2.
<?php
//file : App_folder/module/Module_name/Module.php
public function getViewHelperConfig()
{
return array(
'factories' => array(
// the array key is the name of the invoke function that is called from view
'sayHello' => function($name) {
return new SayHello($name);
},
),
);
}
?>
Step 3.
<?php
//file : App_folder/module/Module_name/view/Module_name/index/index.phtml
$this->sayHello('Dimitri');
?>
Thanks you all for your helping.
Creating a ViewHelper is a simple task and it is divided in 2-3 steps, depending on your needs. Always the first step is to actually create a ViewHelper and that's where you have to make your first decisions.
Does your ViewHelper have dependencies? If yes, you'll need a __construct() function
Does your ViewHelper need arguments? If yes, you'll need to have arguments for your __invoke() function
Once you've evaluated both points, you write the ViewHelper
<?php
// FileName: /module/Application/src/Application/View/Helper/MyHelper.php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
class MyHelper extends AbstractHelper
{
// If you have dependencies:
public function __construct(DependencyInterface $dependency)
{
// store dependency as a local property inside class
}
// Put arguments if you need them, if not leave them blank
public function __invoke($arg1, $arg2, ..., $argN)
{
// Have a string being returned directly
// return 'some string';
// Or you could return a rendered view template and pass view variables
// return $this->getView()->render('foo/bar', array(
// $arg1, $arg2, ..., $argN
// ));
}
}
The second thing you have to do is to register this ViewHelper to the ViewHelperManager. If you have no dependencies, you put it up under the invokables configuration, if you do have dependencies, it will become a factories entry.
public function getViewHelperConfig()
{
return array(
'invokables' => array(
'MyHelper' => 'Application\View\Helper\MyHelper'
),
// or if it has dependencies
'factories' => array(
'MyHelper' => function($viewHelperManager) {
$serviceLocator = $viewHelperManager->getServiceLocator();
$dependency = $serviceLocator->get('SomeDependency');
return new MyHelper($dependency);
}
)
);
}
And then you're able to use your ViewHelper via $this->myHelper() inside your view files. Btw it doesn't matter if you camelcase your configuration for the ViewHelper name because it will be lower-cased internally. So $this->MyHelper() equals $this->myhelper() and MyHelper equals myhelper.

Getting module's name from model's name

I need to know a module name for a particular model, if I know only model's name.
For example, I have:
model Branch, stored in protected/modules/office/models/branch.php and
model BranchType stored in protected/modules/config/models/branchtype.php.
I want to know the module name of branch.php from the class of branchtype.php.
How to do this?
Unfortunately Yii does not provide any native method to determine the module name that model belongs to. You have to write your own algorithm to do this task.
I can suppose you two possible methods:
Store configuration for module's models in the module class.
Provide the name of your model using path aliases
First method:
MyModule.php:
class MyModule extends CWebModule
{
public $branchType = 'someType';
}
Branch.php
class Branch extends CActiveRecord
{
public function init() // Or somewhere else
{
$this->type = Yii::app()->getModule('my')->branchType;
}
}
In configuration:
'modules' =>
'my' => array(
'branchType' => 'otherType',
)
Second method:
In configuration:
'components' => array(
'modelConfigurator' => array(
'models' => array(
'my.models.Branch' => array(
'type' => 'someBranch'
),
),
),
)
You should write component ModelConfigurator that will store this configuration or maybe parse it in some way. Then you can do something like this:
BaseModel.php:
class BaseModel extends CActiveRecord
{
public $modelAlias;
public function init()
{
Yii::app()->modelConfigurator->configure($this, $this->modelAlias);
}
}
Branch.php:
class Branch extends BaseModel
{
public $modelAlias = 'my.models.Branch';
// Other code
}
Try this:
Yii::app()->controller->module->id.
Or inside a controller:
$this->module->id
in Yii2 try this:
echo Yii::$app->controller->module->id;
for more information see Get the current controller name, action, module

How can I use Zend\Di in Zend\ServiceManager in ZF2?

I'm trying to make the Zend\ServiceManager use Zend\Di to create my instances, since I have pre-scanned and cached DI definitions already. I realize this might come with a speed penalty but on the other hand, I need to write a lot less meta-code.
The ServiceManager documentation says that
the ServiceManager also provides optional ties to Zend\Di, allowing Di
to act as an initializer or an abstract factory for the manager.
But I don't find any examples of how make the ServiceManager use Zend\Di. I'm not even sure where I should set this up, maybe in Module::getServiceConfig()? Can anyone provide some example code?
The following works for me. In order to make Zend\Di compatible with Zend\ServiceManager, I extended a class MyLib\Di\Di from Zend\Di\Di which implements the AbstractFactoryInterface.
namespace MyLib\Di;
use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Di extends \Zend\Di\Di implements AbstractFactoryInterface
{
public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
return true;
}
public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
return $this->get($requestedName);
}
}
Now, I can use MyLib\Di\Di as a fallback abstract factory for Zend\ServiceManager. Here's an example of how I create my IndexController. The IndexController's dependencies (constructor parameters) are injected automatically.
class Module
{
...
public function getServiceConfig()
{
$this->di = new \MyLib\Di\Di;
$this->configureDi($this->di); // Set up definitions and shared instances
return array(
'abstract_factories' => array($this->di),
);
}
public function getControllerConfig()
{
return array(
'factories' => array(
'Survey\Controller\IndexController' => function() {
return $this->di->get('Survey\Controller\IndexController');
},
),
);
}
}
One option - add to config/module.config.php
'service_manager' => array(
'invokables' => array(
'Application\Service\User' => 'Application\Service\User',
),
),
then class needs to implement Zend\ServiceManager\ServiceManagerAwareInterface
When initiated, serviceManager instance is going to be injected, then you can use something like this in class:
$authService = $this->getServiceManager()->get('Zend\Authentication\AuthenticationService');
second option would be to put it into Module.php
public function getServiceConfig()

ZF2: How to get Zend\Navigation inside custom route?

I have custom router and I have to get access to Zend\Navigation inside this custom router. I was googling, asking and searching and no results :/
All I need is to find nodes with 'link' param using Zend\Navigation in my Alias::match function.
Here is my module.config.php:
'navigation' => array(
'default' => array(
'account' => array(
'label' => 'Account',
'route' => 'node',
'pages' => array(
'home' => array(
'label' => 'Dashboard',
'route' => 'node',
'params' => array(
'id' => '1',
'link' => '/about/gallery'
),
),
),
),
),
),
[...]
And here is my Alias class:
// file within ModuleName/src/ModuleName/Router/Alias.php
namespace Application\Router;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class Alias extends Http\Segment implements ServiceLocatorAwareInterface
{
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function match(Request $request, $pathOffset = null)
{
[...]
return parent::match($request, $pathOffset);
}
}
EDITED:
Now i know that I should inject service manager into my custom router. Let me know if You know how to do this :)
EDITED:
Ok, its not custom router but route. My bad. I was talking on #zftalk irc chanell and AliasSegment class should implements ServiceLocatorAwareInterface. Ok I've tried it but now there is another problem.
In setServiceLocator function i can't get service locator. It returns null object, however $serviceLocator is class Zend\Mvc\Router\RoutePluginManager.
public function setServiceLocator(ServiceLocatorInterface $serviceLocator){
$sl = $serviceLocator->getServiceLocator();
var_dump($sl); // NULL
}
Any ideas how to get Zend navigation from it ?
EDITED
Corresponding to what #mmmshuddup said, I've changed my custom router class. (New version is above). Also in my Module.php, within onBootstrap function, I added this line:
$sm->setFactory('Navigation', 'Zend\Navigation\Service\DefaultNavigationFactory', true);
Navigation works and its instantiated before route so it should be visible within my Alias class but it's not.
I've put into my match function in Alias class this line:
$servicesArray = $this->getServiceLocator()->getRegisteredServices();
and $servicesArray is almost empty. There is no service, no factories. The same line inserted into onBootstrap, just after setting new factory (as above) returns array with navigation and other services.
The question is: how can i share this array (or ServiceManager) with my custom router: Alias ?
I have to say that all I want to do was possible in ZF1 and it was quite easy.
EDIT
I found a solution. The answer is below
That is because the object itself really doesn't have any properties declared. But if you do this:
echo get_class($sl);
You will see that it is indeed an instance of Zend\ServiceManager\ServiceManager
You should be able to get your navigation instance by doing something like:
$nav = $sl->get('Navigation');
EDIT:
I just notice you have some stuff in the wrong location of your code. You're calling getServiceLocator() on $serviceLocator which is already the instance of that. Also you're calling it within setServiceLocator(). You should change it to:
// EDIT - file within ModuleName/src/Router/Alias.php
namespace Application\Router;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
class Alias extends Http\Segment implements ServiceLocatorAwareInterface
{
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function match(Request $request, $pathOffset = null)
{
$nav = $this->getServiceLocator()->get('Navigation');
// ...
return parent::match($request, $pathOffset);
}
}
I found the solution but this is NOT elegant solution i think. However everything works perfectly. If somebody knows disadvantages of this solution, please comment this answer or add another, better. I had to modify #mmmshuddup's idea (you can read the conversation).
First of all, the implementation of ServiceLocatorAwareInterface in custom route class is no more necessary.
In Module.php within onBootstrap function:
$app = $e->getApplication();
$sm = $app->getServiceManager();
$sm->get('translator');
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$sm->setFactory('Navigation',
'Zend\Navigation\Service\DefaultNavigationFactory', true);
$nav = $sm->get('Navigation');
$alias = $sm->get('Application\Router\Alias');
$alias->setNavigation($nav);
First we instantiate Navigation factory in ServiceManager and then our custom route. After that we can pass Navigation class into custom route using setNavigation function.
To complete instantiate of our custom route we need in getServiceConfig in the same file:
return array(
'factories' => array(
'Application\Router\Alias' => function($sm) {
$alias = new \Application\Router\Alias('/node[/:id]');
return $alias;
},
'db_adapter' => function($sm) {
$config = $sm->get('Configuration');
$dbAdapter = new \Zend\Db\Adapter\Adapter($config['db']);
return $dbAdapter;
},
)
);
And here is a tricky part. This instance is temporary. While routing, this class will be instantiated one more time and this is why, I think, it's not very elegant. We have to insert parameter into constructor however at this moment value of this parameter is not important.
The custom route class:
// file within ModuleName/src/ModuleName/Router/Alias.php
namespace Application\Router;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
class Alias extends Http\Segment
{
private static $_navigation = null;
public function match(Request $request, $pathOffset = null)
{
//some logic here
//get Navigation
$nav = self::$_navigation;
return parent::match($request, $pathOffset);
}
public function setNavigation($navigation){
self::$_navigation = $navigation;
}
}
Because first instance is temporary, we have to collect our Navigation class in static variable. It's awful but works nice. Maybe there is a way to instantiate it only once and in route configuration get instance of it, but at this moment this is best answer for my question. Simply enough and working correctly.

Categories