I'm writing a custom renderer and following this tutorial that explains how to instantiate PhpRenderer object.
class CustomRenderer implements RendererInterface
{
.....
public function render( $nameOrModel, $values = null )
{
.....
$renderer = new \Zend\View\Renderer\PhpRenderer();
$renderer->setResolver( $this->resolver ); // $this->resolver Zend\View\Resolver\AggregateResolver
return $renderer->render( $model ); // $model Zend\View\Model\ViewModel
.....
Renderer is constantly throwing errors like
'Zend\View\HelperPluginManager::get was unable to fetch or create an
instance for translate
or
'No base path provided' in Zend\View\Helper\BasePath
etc. Basically everything that has to do with helpers in template does not work. Also, tried this code in controller, or with clean model but no luck. Resolver and model are correct.
How to properly instantiate new PhpRenderer object.
Related
I know there are dozen of questions about PHPRenderer not finding the path of a template, but I think the problem is quite different here.
First, the goal is to render a view to a variable in order to send it to a PDF Renderer (I use ZF3 TCPDF module). If there is any better way to do that, please tell me.
Here is roughly the architecture of the project: https://imgur.com/UhQ7hgP
In AlertAction() of ToolsController, I return the view like this, and it works, which make me think the template path is alright.
$view = new ViewModel();
$view->setTemplate('tools/tools/alert');
return $view;
However, when I try to render the same view with the same path in exportPDFAction(), it does not work and gives the following error.
Zend\View\Renderer\PhpRenderer::render: Unable to render template "tools/tools/alert"; resolver could not resolve to a file
The code in exportPDFAction() is:
$view = new ViewModel();
$renderer = new PhpRenderer();
$view->setTemplate('tools/tools/alert');
$html = $renderer->render($view);
I assume the last line screws it as it is the difference, but I can't get why, does anyone have any clue ?
Quite all the topics about Template path on SO were talking about the template map in module.config.php, but I think this is not the problem here since it works perfectly in AlertAction().
EDIT
The PhpRenderer is injected in the controller directly in module.config.php:
'controllers' => [
'factories' => [
ToolsController::class => function($container) {
return new ToolsController(
$container->get(Adapter::class),
$container->get(\TCPDF::class),
$container->get(PhpRenderer::class)
);
},
],
],
EDIT 2
This is the controller constructor:
public function __construct($db, $tcpdf, $renderer)
{
$this->db = $db;
$this->tcpdf = $tcpdf;
$this->renderer = $renderer;
...
}
The error you're getting might be due to the fact your Renderer is not injected via the Factory.
Try:
class MyCustomControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var \Zend\View\Renderer\PhpRenderer $renderer */
$renderer = $container->get('ViewRenderer')
return new MyCustomController($renderer);
}
}
In the Controller, require it be set in the __construct() function:
public function __construct(PhpRenderer $renderer)
{
// ... set it somewhere, e.g.:
$this->setRenderer($renderer);
}
Then use it in your function:
$view = new ViewModel();
$renderer = $this->getRenderer();
$view->setTemplate('tools/tools/alert');
$html = $renderer->render($view);
Why, you ask?
Because the Renderer is configured via the Zend Configuration. You can find that in the \Zend\Mvc\Service\ServiceManageFactory class. The alias configuration provided is the following:
'ViewPhpRenderer' => 'Zend\View\Renderer\PhpRenderer',
'ViewRenderer' => 'Zend\View\Renderer\PhpRenderer',
'Zend\View\Renderer\RendererInterface' => 'Zend\View\Renderer\PhpRenderer',
The alias'es are mapped to Factory:
'Zend\View\Renderer\PhpRenderer' => ViewPhpRendererFactory::class,
That Factory is:
class ViewPhpRendererFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $name
* #param null|array $options
* #return PhpRenderer
*/
public function __invoke(ContainerInterface $container, $name, array $options = null)
{
$renderer = new PhpRenderer();
$renderer->setHelperPluginManager($container->get('ViewHelperManager'));
$renderer->setResolver($container->get('ViewResolver'));
return $renderer;
}
}
As such, it has some presets included when you use it with $this->getRenderer, namely it has the HelperPluginManager and the Resolver set. So it knows where to get additional resources (if needed) and it knows how to resolve (ie render) a View.
Try and data to database and get an error.:
Uncaught Error: Call to a member function persist() on null in
public function addNewPostAction()
{
// Create new Post entity..
// $entityManager = $container->get('doctrine.entitymanager.orm_default');
$post = new Post();
$post->setTitle('Top 10+ Books about Zend Framework 3');
$post->setContent('Post body goes here');
$post->setStatus(Post::STATUS_PUBLISHED);
$currentDate = date('Y-m-d H:i:s');
$post->setDateCreated($currentDate);
$this->entityManager->persist($post);
$this->entityManager->flush();
}
UPDATE:
Error:
Zend\Mvc\Controller\PluginManager::get was unable to fetch or create an instance for get
public function addNewPostAction()
{
// Create new Post entity..
// $entityManager = $container->get('doctrine.entitymanager.orm_default');
$post = new Post();
$post->setTitle('Top 10+ Books about Zend Framework 3');
$post->setContent('Post body goes here');
$post->setStatus(Post::STATUS_PUBLISHED);
$currentDate = date('Y-m-d H:i:s');
$dm = $this->get('doctrine.odm.mongodb.document_manager');
$dm->persist($post);
$dm->flush();
}
From the 2 samples above, it is obvious you are trying to get doctrine `s entity manager.
1st sample:
$this->entityManager
probably the property $entityManager of the controller is not set, also from the commented code
$entityManager = $container->get('doctrine.entitymanager.orm_default');
it is obvious you are trying to get entity manager.
2nd sample:
$this->get('doctrine.odm.mongodb.document_manager');
I assume this is from a symfony example.
Anyway to get the doctrine manager in your controller, you have to inject it, change your controller constructor to accept it as an argument:
class IndexController extends AbstractActionController
{
private $doctrineManager;
public function __construct($doctrineManager)
{
$this->doctrineManager = $doctrineManager;
}
and then inject the doctrine manager to your controller factory in your module.config.php:
'controllers' => [
'factories' => [
Controller\IndexController::class => function ($container) {
return new Controller\IndexController(
$container->get('doctrine.odm.mongodb.document_manager')
);
},
// ...
],
],
Side note: the error "Zend\Mvc\Controller\PluginManager::get was unable to fetch or create an instance for get" is thrown because zend tries any undefined methods to resolve them to a plugin, eg. if you define a plugin with name myAwesomePlugin, you can access it in your action as:
$this->myAwesomePlugin();
I have extended the PhpRenderer class in my ZF2 application like this:
namespace MyLib\View\Renderer;
class PhpRenderer extends \Zend\View\Renderer\PhpRenderer
{
}
I don't want to add a new rendering strategy, I just extend the PhpRenderer to add some #method phpdoc for my viewhelpers.
How can I replace the standard PhpRenderer with my extended PhpRenderer so it will be used to render my viewscripts?
The php renderer is a service inside the service manager. You can override this service directly or do it via the view manager (which instantiates and configures the renderer).
Override the service
In your module you define an onBootstrap() method. The "old" php renderer is already registered, you have to redefine it.
public function onBootstrap($e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$old = $sm->get('ViewRenderer');
$new = new MyCustomViewRenderer;
$new->setHelperPluginManager($old->getHelperPluginManager());
$new->setResolver($old->getResolver());
$sm->setAllowOverride(true);
$sm->setService('ViewRenderer', $new);
$sm->setAllowOverride(false);
}
Override the view manager
There is an alternative where you can redefine the view manager where the php renderer is instantiated. You have to redefine the view manager's factory for this:
In your application.config.php (note it is the application config, as the module config will not work here!)
service_manager => array(
'factories' => array(
'HttpViewManager' => 'MyModule\Service\HttpViewManagerFactory',
),
);
Then create your MyModule\Service\HttpViewManagerFactory:
use MyModule\View\Http\ViewManager as HttpViewManager;
class HttpViewManagerFactory implements FactoryInterface
{
/**
* Create and return a view manager for the HTTP environment
*
* #param ServiceLocatorInterface $serviceLocator
* #return HttpViewManager
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new HttpViewManager();
}
}
And then you can finally update the factory of the php renderer itself:
use Zend\Mvc\View\Http\ViewManager as BaseViewManager;
class ViewManager extends BaseViewManager
{
public function getRenderer()
{
if ($this->renderer) {
return $this->renderer;
}
$this->renderer = new MyCustomViewPhpRenderer;
$this->renderer->setHelperPluginManager($this->getHelperManager());
$this->renderer->setResolver($this->getResolver());
$model = $this->getViewModel();
$modelHelper = $this->renderer->plugin('view_model');
$modelHelper->setRoot($model);
$this->services->setService('ViewRenderer', $this->renderer);
$this->services->setAlias('Zend\View\Renderer\PhpRenderer', 'ViewRenderer');
$this->services->setAlias('Zend\View\Renderer\RendererInterface', 'ViewRenderer');
return $this->renderer;
}
}
Conclusion
The first method instantiates the normal php renderer already, so you instantiate two of them and replace the default with your own.
An alternative is to circumvent the instantiation of the default Zend's php renderer, but you have to do this inside the view manager class. The problem here is you have to redefine the factory for the view manager as well. This sounds as a detour, but it is the only way to get this done.
If all your custom class contains is #method declarations then you don't need to replace the php renderer class. Just make sure to use the #var docblock and your IDE will know what to do:
Document the type for the $this variable in your view files:
<!-- in a view file -->
<?php /* #var $this MyLib\View\Renderer\PhpRenderer */ ?>
<?= $this->myCustomViewHelper() ?>
Document individual variables or properties for view helpers, classes, etc:
class SomeHelper extends AbstractHelper
{
/** #var \MyLib\View\Renderer\PhpRenderer */
protected $view;
public function __invoke()
{
$this->view->myCustomViewHelper();
}
}
How to get translator in model?
Inside view we can get translator using this code
$this->translate('Text')
Inside controller we can get translator using this code
$translator=$this->getServiceLocator()->get('translator');
$translator->translate("Text") ;
But how to get translator in model?
I'd tried so many ways to get service locator in models
2 of those
1)Using MVC events
$e=new MvcEvent();
$sm=$e->getApplication()->getServiceManager();
$this->translator = $sm->get('translator');
if i pring $sm it is showing null. but it works fine in Model.php onBootstrap
2)Created one model which implements ServiceLocatorAwareInterface
SomeModel.php
<?php
namespace Web\Model;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class SomeModel implements ServiceLocatorAwareInterface
{
protected $services;
public function setServiceLocator(ServiceLocatorInterface $locator)
{
$this->services = $locator;
}
public function getServiceLocator()
{
return $this->services;
}
}
and used that inside my model
$sl = new SomeModel();
$sm=$sl->getServiceManager();
var_dump($sm); exit;
$this->translator = $sm->get('translator');
this is also printing null.
If you don't need the servicemanager instance in your model, simply inject translator instance to it.
For example:
// Your model's constructor
class MyModel {
// Use the trait if your php version >= 5.4.0
use \Zend\I18n\Translator\TranslatorAwareTrait;
public function __construct( $translator )
{
$this->setTranslator( $translator );
}
public function modelMethodWhichNeedsToUseTranslator()
{
// ...
$text = $this->getTranslator()->translate('lorem ipsum');
// ...
}
}
When you creating your model first time on service or controller level
class someClass implements ServiceLocatorAwareInterface {
public function theMethodWhichCreatesYourModelInstance()
{
// ...
$sm = $this->getServiceLocator();
$model = new \Namespace\MyModel( $sm->get('translator') )
// ...
}
}
If you need to instantiate your model (new MyModel();) on multiple methods/classes, consider to writing a factory for it.
Here is a nice article about Dependency Injection and PHP by Ralph Schindler for more detailed comments about this approach.
For your Model class to be ServiceLocatorAware, you not only need to implement the interface, you also need to make your model a service of the service manager, and fetch the model from there.
Add your model to the service manager, since it doesn't appear to need any constructor params, it's invokable, so you can add it to the invokables array in service manager config. You can do that by using the getServiceConfig() method in your Module class...
class Module
{
public function getServiceConfig()
{
return array(
'invokables' => array(
'SomeModel' => 'Fully\Qualified\ClassName\To\SomeModel',
),
);
}
}
Then, instead of calling the new keyword to create your model instance, you fetch it from the service manager, for instance, by calling the getServiceLocator() method in a controller action...
public function fooAction()
{
$sm = $this->getServiceLocator();
$model = $sm->get('SomeModel');
}
When your model is fetched from the service manager, a service initializer will look to see if it implements the ServiceLocatorAwareInterface and automatically call setServiceLocator() if it does, passing it an instance of the service manager.
I've been working with Doctrine_Record classes that autoload just fine for a while; but after some reading, I've decided I would like to implement both Doctrine_Records as well as Custom Table Classes.
So I added this to my bootstrap
$manager->setAttribute(
Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES,
true
);
Which has made the Custom table classes work just fine... but it breaks autoloading Records!
How to make both autoload?
I.E. new User.php gets my User Doctrine_Record class and Doctrine_Core::getTable('User') gets my Custom UserTable class.
Here's how it looked (working) before I tried implementing Custom Tables:
public function _initDoctrine() {
require_once 'Doctrine.php';
/*
* Autoload Doctrine Library and connect
* use appconfig.ini doctrine.dsn
*/
$this ->getApplication()
->getAutoloader()
->pushAutoloader(array(
'Doctrine',
'autoload'),
'Doctrine');
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(
Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE,
true
);
$manager->setAttribute(
Doctrine::ATTR_MODEL_LOADING,
Doctrine::MODEL_LOADING_CONSERVATIVE
);
// try these custom tables out!
// $manager->setAttribute( Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true );
$config = $this->getOption('doctrine');
$conn = Doctrine_Manager::connection($config['dsn'], 'doctrine');
return $conn;
// can call flush on the connection to save any unsaved records
}
Thanks
edit:
Let me clarify.
Not just custom classes.. I already use custom classes which extend Doctrine_Record.
class Model_User extends Doctrine_Record {}
$foo = new Model_User;
Much of my application currently works around this and will not be changing in that respect.
However, I would like to ALSO use Custom Tables
class UserTable extends Doctrine_Table {}
$bar = Doctrine_Core::getTable('User');
But, as soon as I enable this (custom table classes) feature to call classes of Doctrine_Table utilising the Table suffix. Any Doctrine_Record classes I've previously extended and called directly, stops working! I want to make use of both!
I don't really understand your problem about your custom classes but in any case here is my bootstrap for Doctrine 1.2.4 in ZF, using mysql and UTF-8. It doesn't need a require() and load all my models flawlessly.
protected function _initDoctrine()
{
$this->getApplication()->getAutoloader()
->pushAutoloader(array('Doctrine', 'autoload'));
spl_autoload_register(array('Doctrine', 'modelsAutoload'));
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
$manager->setAttribute (
Doctrine::ATTR_MODEL_LOADING,
Doctrine::MODEL_LOADING_CONSERVATIVE
);
$manager->setAttribute(Doctrine::ATTR_AUTOLOAD_TABLE_CLASSES, true);
$dsn = $this->getOption('dsn');
$conn = Doctrine_Manager::connection($dsn, 'doctrine');
$conn->setAttribute(Doctrine::ATTR_USE_NATIVE_ENUM, true);
$conn->setCollate('utf8_unicode_ci');
$conn->setCharset('utf8');
$conn->setAttribute(Doctrine_Core::ATTR_AUTO_FREE_QUERY_OBJECTS, true );
Doctrine::loadModels(APPLICATION_PATH . '/models');
return $manager;
}
Doctrine models are stored in "application/models"
Then in my application/configs/application.ini :
autoloadernamespaces[] = "Doctrine"
dsn = "mysql://your_login:your_pass#server_ip/database"
I found the problem!
You must make sure every x.php Doctrine_Record class has an associated xTable.php Doctrine_Table class or the record loading will break!