I am currently learning/experimenting with the stable version of ZF2. The last couple of days has been used trying to find a solution to my problem, which is: I want to be able to write some setup logic general to a set of controllers. In ZF! I would then just write a general controller and derive from it, using the init() method for my setup logic. After a bit of searching I found that the init() method was removed in ZF2 and that there were alternative approaches to get the same functionality.
I tried to follow the guide by M. W. O'Phinney: http://mwop.net/blog/2012-07-30-the-new-init.html
In my case I have to be able to retrieve and check route params for my setup logic, so the method overriding alternatives didn't work due to one not having access to the MvcEvent at that point. So, I tried the Update: serviceManager solution, and this is where I got stuck. First I just tried to copy the code from the guide into my Module class and echo some text to see if the callback was at all called. Which it isn't.
After more searching on the web i found a possible solution; attaching the callback in the constructor of the general controller. The same problem appeared to be here as well. The constructor gets called of course, but the callback is either not attached or triggered properly (or at all).
I'll attach some of my code from the two different solutions:
In Module.php:
public function getControllerConfig() {
return array(
'factories' => array(
'Game\Controller\Mapsquare' => function($controllers) {
$serviceManager = $controllers->getServiceLocator();
$eventManager = $serviceManager->get('EventManager');
$controller = new Controller\MapsquareController();
echo "this text is echoed";
$eventManager->attach('dispatch', function ($e) use ($controller) {
echo "this text is NOT echoed";
$request = $e->getRequest();
$method = $request->getMethod();
if (!in_array($method, array('PUT', 'DELETE', 'PATCH'))) {
// nothing to do
return;
}
if ($controller->params()->fromRoute('id', false)) {
// nothing to do
return;
}
// Missing identifier! Redirect.
return $controller->redirect()->toRoute(/* ... */);
}, 100); // execute before executing action logic
$controller->setEventManager($eventManager);
return $controller;
}
)
);
}
In MapsquareController.php (the general controller):
public function __construct() {
$this->getEventManager()->attach('dispatch', array($this, 'preDispatch'), 1000);
echo "construct";
}
public function preDispatch() {
echo "This is preDispatch()!";
}
Is there someone out there that can help me with this problem, eventually tell what I'm missunderstanding here? Any help is appreciated :)
You cannot attach to dispatch from a factory because factory is called a long time after the dispatch has been processed.
To make it work, open your Module.php and edit onBootstrap(), so that you attach there.
For example:
public function onBootstrap($e)
{
...
$eventManager = $e->getApplication()->getEventManager();
$eventManager->attach("dispatch", function($e) {
echo "Dispatch!";
});
}
Alternatively, you can also do this from a specific controller. Not in the constructor, but by overriding setEventManager:
public function setEventManager(EventManagerInterface $events) {
parent::setEventManager($events);
$controller = $this;
$events->attach("dispatch", function($e) use ($controller) {
echo "Dispatch!";
});
}
Hope this helps!
Pls note that the accepted is not correct. The code posted by OP is simply not functional. Never polute your onBootstrap with cross-cutting concerns. The reason this:
public function __construct() {
$this->getEventManager()->attach('dispatch', array($this, 'preDispatch'), 1000);
echo "construct";
}
didn't work is because the event manager gets attached AFTER construction, and fires on dispatch.
The comment about "factory is called a long time after the dispatch" makes no sense also. Do you know what a factory does?
The suggestion by "Mr. O'phinney" is the correct way, maybe read the manual better next time before you decide someone like Matthew disappointed you..
Solution is here: http://mwop.net/blog/2012-07-30-the-new-init.html
Evan Coury's post on Module-specific layouts in Zend Framework 2 seems to give a solution, it works for me.
http://web.archive.org/web/20140623024023/http://blog.evan.pro/module-specific-layouts-in-zend-framework-2
Related
Lets say I have a factory returning different classes via methods.
class CarFactory
{
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function createCarOne() : CarInterface
{
return $this->container->make(CarOneClass::class);
}
// Vs
public function createCarTwo() : CarInterface
{
return new CarTwoClass({Inject Dependencies Here});
}
}
When would this be considered a service locator or anti-pattern and why? I am considering the first method solely for the dependency resolution provided by the container. All car's have the same typed interface dependencies the main difference of the entities come from how they transform the data provided.
Whenever one of these methods are called I need a new instance of the specified car so the data set can be transformed based on the choice.
This is not the implementation but the easiest example I can provide.
$output = [];
foreach ($car as $key => $data) {
$newCar = $this->factory->createCar{$key}();
// Pass Some Data To The New Car Methods So It Can Be Transformed
$output[] = $newCar;
}
return $output;
If this is the wrong approach what would be the alternative option?
Edit
After further digging I see some IoC containers pass factory callables as dependencies. I was going to bind each Car to a callable but thanks to the ability to type hint data from method returns (php7) I can configure factories using a provider then call the 'callable factory' from within the CarFactory. Requires additional binding but prevents the need to reference/dependency inject the IoC container within every factory.
Still researching I would love to hear feedback from those with more experience.
Ex:
// Within Some Registered Provider
// I Will Have To Wire Each Car
$one = function() use ($app) {
return $app->make(CarOne::class);
};
$two = function() use ($app) {
return $app->make(CarTwo::class);
};
$app->bind(ICarFactory::class, function($app) use ($one, $two) {
return $app->make($concrete, [$one, $two]);
});
// Car Factory Constructor
public function __construct(callable $carOne, callable $carTwo) {
$this->one = $carOne;
$this->two = $carTwo;
}
Since get methods are type hinted ( view original car factory ) an error is thrown when the returned item does not implement CarInterface, each factory method would just have to call the 'callable factory' ( something like this return ($this->one)();).
I believe i solve my problem of outsourcing creation of dependencies ( avoiding creating within factory was bothering the hell out of me ) while still following 'best practices'. Still looking for advice if anyone has any to offer.
At present I set a couple of variables to be used by the app's overall layout.phtml, using the onDispatch method of a BaseController, which all my other controllers extend:
public function onDispatch(MvcEvent $e)
{
$config = $this->getServiceLocator()->get('config');
$this->layout()->setVariable('platformName', $config['platform']['name']);
$this->layout()->setVariable('platformYear', $config['platform']['year']);
}
This works fine, until I test some error pages and find that these pages do not get provided with the variables, as it's not using the base controller.
How can I get around this problem and provide the error pages with the same variables?
Change the event you're listening for.
In this case, I'd move this logic to the application bootstrap event or the application render event (I haven't tested this, but it would probably work fine).
One example, in your Module.php
public function onBootstrap($e)
{
$config = $e->getApplication()->getServiceManager()->get('config');
//$e->getViewModel()->setVariable();
}
Haven't tested that commented out line, but it should get you headed in the right direction.
EDIT: Found an example of using the render event
public function onBootstrap($e)
{
$event = $e->getApplication()->getEventManager();
$event->attach('render', function($e) {
$config = $e->getApplication()->getServiceManager()->get('config');
$e->getViewModel()->setVariable('test', 'test');
});
}
(Necro)
When using onDispatch in a Controller, remember to return the parent with the event and all:
public function onDispatch(MvcEvent $e)
{
// Your code
return parent::onDispatch($e);
}
Otherwise, the logic on your Actions in that Controller will be ignored.
i am following a getting started tutorial on zend framework 2, in one of the topics it suggests using tests, the code it suggests is:
namespace ApplicationTest\Controller;
use ApplicationTest\Bootstrap;
use Zend\Mvc\Router\Http\TreeRouteStack as HttpRouter;
use Application\Controller\IndexController;
use Zend\Http\Request;
use Zend\Http\Response;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Router\RouteMatch;
use PHPUnit_Framework_TestCase;
class IndexControllerTest extends PHPUnit_Framework_TestCase
{
protected $controller;
protected $request;
protected $response;
protected $routeMatch;
protected $event;
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new IndexController();
$this->request = new Request();
$this->routeMatch = new RouteMatch(array('controller' => 'index'));
$this->event = new MvcEvent();
$config = $serviceManager->get('Config');
$routerConfig = isset($config['router']) ? $config['router'] : array();
$router = HttpRouter::factory($routerConfig);
$this->event->setRouter($router);
$this->event->setRouteMatch($this->routeMatch);
$this->controller->setEvent($this->event);
$this->controller->setServiceLocator($serviceManager);
}
public function testIndexActionCanBeAccessed()
{
$this->routeMatch->setParam('action', 'index');
$result = $this->controller->dispatch($this->request);
$response = $this->controller->getResponse();
$this->assertEquals(200, $response->getStatusCode());
}
}
as you can see there is no __autoload($class).
to manually make it work i added include "../../Bootstrap.php"; it did solve the problem but i remember once i could get this code to work, and the tutorial doesn't seem to forget something conceptually obvious and there is no feedback about it in the Topic comments , there may be something I am missing, how would the code above probably work?
I managed to get it working but noticed you couldn't use phpUnit's extended Request and Response objects. These are the instructions for early 2.0 release. At least after 2.0.7, the instructions are much different and the code is cleaner:
http://zf2.readthedocs.org/en/latest/user-guide/unit-testing.html
<?php
namespace ApplicationTest\Controller;
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
class IndexControllerTest extends AbstractHttpControllerTestCase
{
public function setUp()
{
$this->setApplicationConfig(
include '/path/to/application/config/test/application.config.php'
);
parent::setUp();
}
public function testIndexActionCanBeAccessed()
{
$this->dispatch('/');
$this->assertResponseStatusCode(200);
$this->assertModuleName('application');
$this->assertControllerName('application_index');
$this->assertControllerClass('IndexController');
$this->assertMatchedRouteName('home');
}
}
In this example testing is carried out by extending Zend's controller test case, which was the way controller tests were carried out with zf1.
What is the best/standard way to put common variables and functions in Zend framework 2 (with doctrine), to be used across all the modules, specifically their controllers.
I read somewhere that our controllers should extend another controller (like AppCommonController) which, in turn, extends AbstractActionController. The AppCommonController will then define the common variables and functions that we can access in any controller that extends it.
Is there a better/standard way to do this?
---Updated---
Say for e.g., I want to check the current mode of my site (test or live) in most of my controllers (across different modules), and accordingly want to do the necessary in the actions.
I write following in some controller:
private $__currentMode = '';
public function __construct()
{
//following will be set to Live or Test depending on a session value
$this->setCurrentMode('Live');
}
public function setCurrentMode($mode)
{
$this->__currentMode = $mode;
}
public function getCurrentMode()
{
return $this->__currentMode;
}
I believe it is a bad idea to put above code in all the controllers where I need to check the current mode.
So I want to put it (both the currentMode property and getter/setter functions) at some place from where I can access them in all the controllers wherever needed.
Seems like this is what controller plugins are there for
First create a controller plugin...
namespace Application\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
class MyModeHelper extends AbstractPlugin
{
protected $mode;
public function __construct($mode)
{
$this->mode = $mode;
}
public function getMode()
{
return $this->mode;
}
}
Then tell the controller manager about it in Module.php using the getControllerPluginConfig() method
// in Application/Module.php
public function getControllerPluginConfig()
{
return array(
'factories' => array(
'myModeHelper' => function($sm) {
// get mode from environment
$mode = 'live';
return new Controller\Plugin\MyModeHelper($mode);
}
)
); //fixed syntax error
}
}
Plugin should now be available any time you call it in a controller
// in your controllers
public function indexAction()
{
if ($this->myModeHelper()->getMode() == 'live') {
// do live stuff
} else {
// do test stuff
}
return new ViewModel();
}
Well, heavily depends on the functions.
First of: variables would probably best placed inside configuration. From there on they are accessible anywhere a ServiceLocator is present.
As far as functions are concerned, it heavily depends on what the functions do. Are they some sort of ControllerLogic? Then your approach Mymodule\Stdlib\Controller\Mycontroller might be a good idea.
Looking at the current "Community-Standards" having general-purpose-code under the Stdlib-Namespace is commonly accepted.
Outside of the above i don't know what to tell you, as your question is pretty vague.
I have the following code blocks:
class MerchantStoreForm extends sfForm
{
public function configure()
{
$this->disableCSRFProtection();
$this->setWidgets(array(
'brand_id' => new sfWidgetFormDoctrineChoice(array('label'=> 'Store Brand','model'=>'Brand','add_empty'=>'-Select Brand-','method'=>'getName','key_method'=>'getId','order_by'=>array('name','asc'))),
'newbrand' => new sfWidgetFormInputCheckbox(array('label' => 'New'),array('value'=>'Y'))
));
$this->setValidators(array(
'newbrand' => new sfValidatorString(array('required'=>false)),
'brand_id' => new sfValidatorDoctrineChoice(array('model'=>'Brand'))
));
$brand = new Brand();
$brand_form = new BrandForm();
$brand_form->widgetSchema['name']->setAttribute('style','display:none');
$this->embedForm('brand', $brand_form);
$this->getWidgetSchema()->setNameFormat('store[%s]');
}
public function execute()
{
$form_values = $this->getValues();
if($form_values['newbrand'])
{
$brand_form = $this->getEmbeddedForm('brand');
$brand_form->save();
$brand = $brand_form->getObject();
}
else
{
$brand = doctrine::getTable('Brand')->findOneById($form_values['brand_id']);
}
return $brand->getId();
}
}
Two questions:
1) The magic of $brand_form->save() doesn't work for me. I get a 500 Internal Server Error sfValidatorErrorSchema error pointing to the following piece of code in my symfony generated BaseBrandForm.class.php:
...
$this->widgetSchema->setNameFormat('brand[%s]');
$this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
...
This works instead in replacement though:
$brand_form->updateObject($form_values['brand']);
$brand_form->getObject()->save();
Why is this?
2) Why do I get an undefined method error while calling getter method on the object of the BaseFormDoctrine embedded form:
return $brand->getId();
Thanks in advance for your help.
Sharmil
1) BrandForm throws an exception because it doesn't have any values. Classes that extend sfFormObject don't play nicely when embedded directly into non object forms (like sfForm).
What is MerchantStoreForm doing? Depending on the situation, it should probably be extending sfFormObject or BrandForm should be the top level form. If this isn't possible, you'll have to write add a save method to MerchantStoreForm that calls updateObject and save. To better understand what's happening, go through the logic that takes place in sfFormObject - it's worth knowing especially if you're using embedded forms.
2) No clue here. I would see what $brand is actually an instance of. If it's a record and that record has an id field, there's no reason that shouldn't work.