I am using ZfcTwig module from https://github.com/ZF-Commons/ZfcTwig
Reference used: http://symfony.com/doc/current/cookbook/templating/twig_extension.html
Using above link, I was able to create a custom Twig Extension. In this class, I want to access the Entity Manager to fetch some data from database.
I am registering the custom extension via following code in module.config.php -
'zfctwig' => array(
'extensions' => array(
'Application\Extension\MyCustomTwigExtension'
)
)
But as I want to access a Doctrine Entity Manager in this class, I tried by creating a closure -
'zfctwig' => array(
'extensions' => array(
'Application\Extension\MyCustomTwigExtension' => function($sm) {
$entityManager = $sm->get('doctrine.entitymanager.orm_default');
$ext = new Application\Extension\MyCustomTwigExtension();
$ext->setEntityManager($entityManager);
return $ext;
},
)
),
And this gives the error message as -
Catchable fatal error: Argument 1 passed to Twig_Environment::addExtension() must implement interface Twig_ExtensionInterface, instance of Closure given....
I have tried for same via factory -
'zfctwig' => array(
'extensions' => array(
'Application\Extension\MyCustomTwigExtension' => 'Application\Extension\MyCustomTwigExtensionFactory'
)
),
Error message -
Catchable fatal error: Argument 1 passed to Twig_Environment::addExtension() must implement interface Twig_ExtensionInterface, instance of Application\Extension\MyCustomTwigExtensionFactory given...
Any ideas on injecting dependency into twig extension? It may not be just Entity Manager but access to View Helpers, etc.
Edit: Contents of MyCustomTwigExtension file -
namespace Application\Extension;
class MyCustomTwigExtension extends \Twig_Extension {
protected $_eventManager;
public function setEntityManager($eventManager = null) {
$this->_eventManager = $eventManager;
}
public function getEntityManager() {
return $this->_eventManager;
}
public function getFilters() {
return array(
new \Twig_SimpleFilter('list', array($this, 'listFilter')),
);
}
public function listFilter($content = '') {
$repository = $this->getEntityManager()->getRepository('Application\Entity\User');
$models = $repository->findAll();
$html = '<ul>List Extension';
foreach ($models as $model) {
$html .= "<li>" . $model->getId() . ' ' . $model->getFullname() . "</li>";
}
$html .= '</ul>';
return $html;
}
public function getName() {
return 'list_extension';
}
}
Thanks.
The only problem you have is the following:
As mentioned in module.config.php#32 you cann add Extension via Factory, but the given namespace Application\Extension\MyCustomTwigExtensionFactory must exist as key in your service_manager-config.
So you could do the following:
'zfctwig' => array(
'extensions' => array(
'customTwig' => 'MyCustomTwigExtensionFactory'
)
),
'service_manager' => array(
'factories' => array(
'MyCustomTwigExtensionFactory' => 'Application\Extension\MyCustomTwigExtensionFactory'
)
)
And everything should be ok.
You can implement ServiceLocatorAwareInterface:
class MyCustomTwigExtension extends \Twig_Extension implements ServiceLocatorAwareInterface {
}
for implementation you can use trait: ServiceLocatorAwareTrait
and you can add method to get entity manager:
public function getEntityManager() {
if($this->entityManager === null) {
$this->entityManager = $this->getServiceLocator()->get(''doctrine.entitymanager.orm_default'')
}
return $this->entityManager;
}
and last thing: in configuration you should add:
'service_manager' => array(
'factories' => array(
'MyCustomTwigExtensionFactory' => function() { return new MyCustomTwigExtension(); }
)
)
Related
I am getting null when using $sm=$this->getServiceLocator() as a result $sm->get("XXXXXXXXXXX") throwing a Fatal error: Call to a member function get() on null.
What i am doing is that, while receiving user data in controller i am calling another controller validatorController inside my requested controller which is signupController and in validatorController i am using $sm=$this->getServiceLocator() which gives the above error
Here is my work
Error comes when i use $check=$this->_getUserTable()->isUnique($email); in ValidatorController.php but not in SignupController.php
Module.php
<?php
namespace User;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\ResultSet\ResultSet;
use User\Controller\ValidatorController;
use User\Model\User;
use User\Model\UserTable;
class Module {
public function getConfig() {
return include __DIR__."/config/module.config.php";
}
public function getAutoloaderConfig() {
return array(
"Zend\loader\StandardAutoloader"=>array(
"namespaces"=>array(
__NAMESPACE__=>__DIR__."/src/".__NAMESPACE__
)
)
);
}
public function getServiceConfig() {
return array(
"factories"=>array(
'User\ValidatorController' => function ($sm) {
$validatorController = new ValidatorController();
return $validatorController;
},
"User\Model\UserTable"=>function($sm) {
$tableGateway=$sm->get("UserTableGateway");
$table=new UserTable($tableGateway);
return $table;
},
"UserTableGateway"=>function($sm) {
$dbAdapter=$sm->get("Zend\Db\Adapter\Adapter");
$resultSetPrototype=new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new User());
return new TableGateway("users",$dbAdapter,null,$resultSetPrototype);
}
)
);
}
}
module.config.php
<?php
return array(
"controllers"=>array(
"invokables"=>array(
"User\Controller\User"=>"User\Controller\UserController",
'User\Controller\Signup' => 'User\Controller\SignupController',
'User\Controller\Validator' => 'User\Controller\ValidatorController'
)
),
// The following section is new and should be added to your file
"router"=>array(
"routes"=>array(
"user"=>array(
"type"=>"segment",
"options"=>array(
"route" => "/user[/:action][/:id]",
"constraints" => array(
"id" => "[0-9]+",
),
"defaults"=>array(
"controller"=>"User\Controller\User"
)
)
),
'signup' => array(
'type' => 'segment',
'options' => array(
'route' => '/signup',
'defaults' => array(
'controller' => 'User\Controller\Signup',
)
)
),
)
),
'view_manager' => array(//Add this config
'strategies' => array(
'ViewJsonStrategy',
),
),
);
SignupController.php
<?php
namespace User\Controller;
use Zend\Mvc\Controller\AbstractRestfulController;
use Zend\View\Model\JsonModel;
class SignupController extends AbstractRestfulController{
private $_userTable;
public function create($data) {
/*
* The above error is not coming here
* $check=$this->_getUserTable()->isUnique($data['email']);
*
* But inside the below controller
*/
// Calling a validatorContoller
$validator=$this->getServiceLocator()->get('User\ValidatorController');
$response=$validator->validateEmail($data['email']);
return new JsonModel($response);
}
public function _getUserTable() {
if(!$this->_userTable) {
$sm=$this->getServiceLocator();
$this->_userTable=$sm->get("User\Model\UserTable");
}
return $this->_userTable;
}
}
ValidatorController.php
<?php
namespace User\Controller;
use Zend\Mvc\Controller\AbstractRestfulController;
use Zend\Validator\EmailAddress;
class ValidatorController extends AbstractRestfulController {
private $_userTable;
public function validateEmail($email) {
$validator = new EmailAddress();
if($validator->isValid($email)) {
// check if it is a unique entry in user table
// ***(THE SOURCE OF ERROR IS HERE)***
$check=$this->_getUserTable()->isUnique($email);
return $check;
}
}
public function _getUserTable() {
if(!$this->_userTable) {
$sm=$this->getServiceLocator();
$this->_userTable=$sm->get("User\Model\UserTable");
}
return $this->_userTable;
}
}
NOTE
Error comes when i use $check=$this->_getUserTable()->isUnique($email); in ValidatorController.php but not in SignupController.php
Thankyou
getServiceLocator() is deprecated in ZendFramework 2. You must inject _userTable in your ValidatorController from your Module.php like this :
class Module {
...
public function getServiceConfig() {
return array(
"factories"=>array(
'User\ValidatorController' => function ($sm) {
$userTable = $sm->get("User\Model\UserTable");
$validatorController = new ValidatorController();
$validatorController->setUserTable($userTable);
return $validatorController;
},
...
}
Then add a setUserTable() method in your ValidController and modify the getUserTable() method :
class ValidController {
public function setUserTable($suerTable) {
$this->_suerTable = $userTable
}
public function _getUserTable() {
return $this->_userTable;
}
}
I'm new in Zend FW 2 and I try to showing data from database in layout but I receive error:
Catchable fatal error: Argument 1 passed to Application\View\Helper\HotNews::__construct() must be an instance of Zend\Db\Adapter\Adapter, none given, called in C:\xampp\htdocs\webtruonghoc\vendor\ZF2\library\Zend\ServiceManager\AbstractPluginManager.php on line 207 and defined in C:\xampp\htdocs\webtruonghoc\module\Application\src\Application\View\Helper\HotNews.php on li
Function getViewHelperConfig in Module.php:
public function getViewHelperConfig()
{
return array(
'factories' => array(
'hotNews' => function($sm) {
$adapter = $sm->getServiceLocator()->get('Application\Model\NewsTable');
return new HotNews($adapter);
},
),
);
}
Add code in module.config.php:
'view_helpers' => array(
'invokables' => array(
'hotnews' => 'Application\View\Helper\HotNews',
),
File HotNews.php:
<?php
namespace Application\View\Helper;
use Zend\Authentication\AuthenticationService;
use Zend\View\Helper\AbstractHelper;
use Zend\Db\Adapter\Adapter;
class HotNews extends AbstractHelper
{
protected $adapter;
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
}
public function __invoke()
{
$sql="SELECT * FROM news order by date DESC limit 0,4";
return $resultSet = $this->adapter->query($sql, \Zend\Db\Adapter\Adapter::QUERY_MODE_EXECUTE);
}
}
and final I showing data in layout:
<?php $hotnews = $this->hotNews();
var_dump($hotnews);
?>
Do I miss something?
It looks like you are expecting your model to be set up as a service. but may not have set up the service correctly. In your module.config.php file, there should be an entry under 'service_manager' => 'factories' :
return array(
'service_manager' => array(
'factories' => array(
'Application\Model\NewsTable' => function (ServiceLocatorInterface $serviceLocator) {
//... returns an instance of Application\Model\NewsTable
}
)
)
);
Your SQL has an error in it. Also, you should not be executing SQL statements inside a view helper, and passing the entire result set of a select * to the view is bad JuJu as well. I would place the SQL inside a Repository class which returns DTO objects representing your data model. You could then inject the repository into your ViewHelper and and use those DTOs in your view.
I am creating a website using Zend Framework 2, and I'm using as an example the exercise from the official course of Zend Technology, Zend Framework 2: Fundamentals.
I have a table called posts and I want to show the table content in my home page, ordered by id. These are the codes I have written:
Controller/PostsTableTrait.php
trait PostsTableTrait
{
private $postsTable;
public function setPostsTable($postsTable)
{
$this->postsTable = $postsTable;
}
}
Controller/IndexController.php
class IndexController extends AbstractActionController
{
use PostsTableTrait;
public function indexAction()
{
return new ViewModel(array(
'post' => $this->postsTable->getPosts()
));
}
}
Factory/IndexControllerFactory.php
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator()->get('ServiceManager');
$indexController = new IndexController();
$indexController->setPostsTable($serviceManager->get('Rxe\Factory\PostsTable'));
return $indexController;
}
}
Factory/PostsTableFactory.php
class PostsTableFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new PostsTable(PostsTable::$tableName, $serviceLocator->get('Zend\Db\Adapter\AdapterService'));
}
}
Model/PostsTable.php
class PostsTable extends TableGateway
{
public static $tableName = "posts";
public function getPosts()
{
$select = new Select(self::$tableName);
$select->columns(array(
'date',
'title',
'text',
'category'
));
$select->order('id DESC');
return $select;
}
}
config/module.config.php
'controllers' => array(
'invokables' => array(
'Rxe\Controller\Index' => 'Rxe\Controller\IndexController',
'Rxe\Controller\Panel' => 'Rxe\Controller\PanelController'
),
'factories' => array(
'Rxe\Factory\PanelController' => 'Rxe\Factory\PanelControllerFactory'
)
),
'service_manager' => array(
'factories' => array(
'Rxe\Factory\PanelForm' => 'Rxe\Factory\PanelFormFactory',
'Rxe\Factory\PanelFilter' => 'Rxe\Factory\PanelFilterFactory',
'Rxe\Factory\PostsTable' => 'Rxe\Factory\PostsTableFactory',
'Zend\Db\Adapter\AdapterService' => 'Zend\Db\Adapter\AdapterServiceFactory'
)
),
I don't know if the error could be in the getPosts() method. I have tried many different ways to return the query but none of them made any difference, not even showed another error.
You have registered the controller as an 'invokable'. When the the controller manager creates IndexController it will do so without using the IndexControllerFactory; therefore the Rxe\Factory\PostsTable dependency is never set.
To fix this, update module.config.php and register the index controller with your factory class.
'controllers' => [
'factories' => [
'Rxe\Controller\Index' => 'Rxe\Factory\IndexControllerFactory',
],
],
Also (not an error as such) but the IndexControllerFactory calls ->get('ServiceManager') using the service manager.
You could update it to be like this.
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllerManager)
{
// #var \Zend\ServiceManager\ServiceManager
$serviceManager = $controllerManager->getServiceLocator();
$indexController = new IndexController();
$indexController->setPostsTable($serviceManager->get('Rxe\Factory\PostsTable'));
return $indexController;
}
}
I'm facing a problem when calling __invoke() on an object. Is __invoke() method agnostic to instance variables? I need to call __invoke() directly on my templates due to some ZF2 injection to call $this->getView()->render(...) (otherwise getView() returns null) and I would like to have instance variables setted there. Any workaround?
See my code:
namespace Person\Person\View\Helper;
use Zend\View\Helper\AbstractHelper;
class PersonShowWidget extends AbstractHelper
{
protected $model = null;
public function __construct(array $options = null)
{
$this->parseOptions($options);
}
public function __invoke()
{
var_dump($this->model); //returns null
return $this->getView()->render('person/show/show_widget', array(
'title' => 'Cliente',
'model' => $this->model,
)
);
}
public function setOptions(array $options = null)
{
$this->parseOptions($options);
}
protected function parseOptions(array $options = null)
{
if (!is_null($options) && is_array($options)) {
if (isset($options['model'])) {
$model = $options['model'];
if (isset($model['id'])) {
$this->model['id'] = $model['id'];
} else {
throw new \Exception;
}
if (isset($model['form'])) {
$this->model['form'] = $model['form'];
} else {
throw new \Exception;
}
}
}
var_dump($this->model); //returns valid data
}
}
I do have called the constructor with some options or the setOptions method before calling __invoke().
Thanks,
You have to initialize the view helper with a factory. In this way you can make sure the constructor is called before the __invoke method is called. And no..the __invoke() method is not agnostic to instance variables.
In the Module.php
public function getViewHelperConfig()
{
return array(
'factories' => array(
'personShowWidget' => function ($helpers) {
$array = array();
$helper = new Person\Person\View\Helper\PersonShowWidget($array);
return $helper;
},
)
);
}
Or in the module.config.php
'view_helpers' => array
(
'factories' => array(
'personShowWidget' => function ($helpers) {
$array = array();
$helper = new Person\Person\View\Helper\PersonShowWidget($array);
return $helper;
},
)
)
Performance-wise you'd better make a Factory class instead of a callable.
More info: http://framework.zend.com/manual/2.0/en/modules/zend.module-manager.module-manager.html
Edit:
It seems like you using the ViewHelper wrongly. You don't have to create the instance by yourself. Just use the ViewHelper in the view. So why not just give the $options as parameter to the __invoke method?
public function __invoke(array $options = null)
{
$this->setOptions($options);
return $this->getView()->render('person/show/show_widget', array(
'title' => 'Cliente',
'model' => $this->model,
)
);
}
In the Controller pass the options array to the view:
return array(
'options' => $options,
);
And call the ViewHelper in the view:
<?php echo $this->personShowWidget($this->options); ?>
Remember: In this way you don't need a Factory to init the ViewHelper. Just add it to the invokables.
module.config.php example:
'view_helpers' => array(
'invokables' => array(
'personShowWidget' => 'Person\Person\View\Helper\PersonShowWidget',
),
),
I'm having a problem with setting up a model table from a view helper. I have used the exact same code that I use within my regular controllers: e.g.:
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Application\Model\MenusTable;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
**snipped**
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function getMenusTable()
{
if (!$this->menusTable) {
$sm = $this->getServiceLocator();
$this->menusTable = $sm->get('Application\Model\MenusTable');
}
return $this->menusTable;
}
public function allLinks()
{
$all = $this->getMenusTable()->fetchAll();
return $all;
}
However I am met with this error:
Catchable fatal error: Argument 1 passed to Application\Model\MenusTable::__construct() must be an instance of Zend\Db\Adapter\Adapter, none given, called in C:\xampp\**snipped**\zend\library\Zend\ServiceManager\AbstractPluginManager.php on line 177 and defined in C:\xampp\**snipped**\Application\src\Application\Model\MenusTable.php on line 14
Everything works fine from the main controllers, but here I seem to hit a big problem - I'm new to Zend, but it appears to not be getting the factory from the Module.php file - is there any way to get it?
I have this in my Module.php - as said it works fine in a regular controller, but in a view helper it's not processed for some reason:
public function getServiceConfig()
{
return array
(
'factories' => array
(
'Application\Model\MenusTable' => function($sm)
{
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$table = new MenusTable($dbAdapter);
return $table;
},
),
);
}
After re-reading your question I realized that your using ZF2.
here is a tutorial on using ServiceLocators http://framework.zend.com/wiki/display/ZFDEV2/Proposal+for+ServiceLocator+and+DependencyInjector
From the Documentation you need to define your DB connection.
$services = new ServiceLocator();
// Registering an object:
$services->set('db', $db);
// Lazy-loading by registering a closure:
$services->set('db', function() use ($config) {
$db = Db::factory($config->db);
return $db;
});
// Retrieving:
$db = $services->get('db');
First you should use the getServiceConfig() in your module ::
public function getServiceConfig()
{
return array(
'factories' => array(
'MODULE\Model\MenusTable' => function($sm) {
$tableGateway = $sm->get('MenusTableGateway');
$table = new MenusTable($tableGateway);
return $table;
},
'MenusTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Menus());
return new TableGateway('menus', $dbAdapter, null, $resultSetPrototype);
},
),
);
}
THE ADAPTER might be in your /config/autoload/global.php, like this :
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory',
),
),
// CONNECTION DB
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=YOURDBNAME;host=localhost',
'username' => 'root',
'password' => '',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
),
),
Next your View helper must extend AbstractHelper but also implement ServiceLocatorAwareInterface
class MyViewHelper extends AbstractHelper implements ServiceLocatorAwareInterface
I'll put the code in my website