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.
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.
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 have a Console Controller and an action to send emails (defined below in module.config.php)
'console' => array(
'router' => array(
'routes' => array(
'cronroute' => array(
'options' => array(
'route' => 'sendEmails',
'defaults' => array(
'controller' => 'Application\Controller\Console',
'action' => 'send-emails'
)
)
),
)
)
),
In the action I want to send an email that contains a link to another action on the site. This would normally be done with a URL View Helper, but since the Request is of type Console and not HTTP, that doesn't work. I've tried to create an HTTP request, but I do not know how to give it the site domain or the Controller/Action link.
My Controller code:
$vhm = $this->getServiceLocator()->get('viewhelpermanager');
$url = $vhm->get('url');
$urlString = $url('communication', array('action' => 'respond', 'id' => $id,
array('force_canonical' => true));
This throws an error:
======================================================================
The application has thrown an exception!
======================================================================
Zend\Mvc\Router\Exception\RuntimeException
Request URI has not been set
How do I create an HTTP Request in a Console Controller that has the site scheme, domain and path/to/action? And how do I pass it along to the URL View Helper?
Here's how this issue can be solved:
<?php
// Module.php
use Zend\View\Helper\ServerUrl;
use Zend\View\Helper\Url as UrlHelper;
use Zend\Uri\Http as HttpUri;
use Zend\Console\Console;
use Zend\ModuleManager\Feature\ViewHelperProviderInterface;
class Module implements ViewHelperProviderInterface
{
public function getViewHelperConfig()
{
return array(
'factories' => array(
'url' => function ($helperPluginManager) {
$serviceLocator = $helperPluginManager->getServiceLocator();
$config = $serviceLocator->get('Config');
$viewHelper = new UrlHelper();
$routerName = Console::isConsole() ? 'HttpRouter' : 'Router';
/** #var \Zend\Mvc\Router\Http\TreeRouteStack $router */
$router = $serviceLocator->get($routerName);
if (Console::isConsole()) {
$requestUri = new HttpUri();
$requestUri->setHost($config['website']['host'])
->setScheme($config['website']['scheme']);
$router->setRequestUri($requestUri);
}
$viewHelper->setRouter($router);
$match = $serviceLocator->get('application')
->getMvcEvent()
->getRouteMatch();
if ($match instanceof RouteMatch) {
$viewHelper->setRouteMatch($match);
}
return $viewHelper;
},
'serverUrl' => function ($helperPluginManager) {
$serviceLocator = $helperPluginManager->getServiceLocator();
$config = $serviceLocator->get('Config');
$serverUrlHelper = new ServerUrl();
if (Console::isConsole()) {
$serverUrlHelper->setHost($config['website']['host'])
->setScheme($config['website']['scheme']);
}
return $serverUrlHelper;
},
),
);
}
}
Of course you have to define default host and scheme values in config since there's no way to detect them automatically in console mode.
Update:
The correct answer for this post can be found here:
Stackoverflow: Using HTTP routes within ZF2 console application
Well you're very close to this but you are not using the Url plugin. If you dived a little bit further into the ZF2 Documentation of the Controller Plugins you could have found the solution.
See for reference: ZF2 Documentation - Controller plugins
Your ConsoleController has to implement one of the following, to be able to retrieve the Controller plugins:
AbstractActionController
AbstractRestfulController
setPluginManager
Well I recommend to extend your controller with the AbstractActionController if you haven't done it yet.
In case you do use the AbstractActionController you can simply call $urlPlugin = $this->url() since the AbstractActionController has an __call() implementation retrieving the plugin for you. But you can also use: $urlPlugin = $this->plugin('url');
So in order to generate the URL for your mail you can do the following in your controller:
$urlPlugin = $this->url();
$urlString = $urlPlugin->fromRoute(
'route/myroute',
array(
'param1' => $param1,
'param2' => $param2
),
array(
'option1' => $option1,
'option2' => $option2
)
);
You can now pass this URL to your viewModel or use the URL viewHelper within your viewModel, but that is up to you.
Try to avoid viewHelpers within your controller as we've got plugins available for this case.
In case you wonder what methods the AbstractActionController has, here is ZF2 ApiDoc - AbstractActionController
In order to make this work you have to setup your route config with a proper structure:
// This can sit inside of modules/Application/config/module.config.php or any other module's config.
array(
'router' => array(
'routes' => array(
// HTTP routes are here
)
),
'console' => array(
'router' => array(
'routes' => array(
// Console routes go here
)
)
),
)
If you've got a Console module, just stick with the console route paths. Don't forget the console key with all the routes beneath it! Take a look at the documentation for a reference: ZF2 - Documentation: Console routes and routing
I think the best solution is using a DelegatorFactory.
config/autoload/server-url.local.php:
return [
'server_url' => 'http://example.com',
];
module/Application/config/module.config.php:
'service_manager' => [
'delegators' => [
TreeRouteStack::class => [
TreeRouteStackConsoleDelegatorFactory::class,
],
]
],
module/Application/src/TreeRouteStackConsoleDelegatorFactory.php:
namespace Application;
use Interop\Container\ContainerInterface;
use Zend\Router\Http\TreeRouteStack;
use Zend\ServiceManager\Factory\DelegatorFactoryInterface;
use Zend\Uri\Http;
class TreeRouteStackConsoleDelegatorFactory implements DelegatorFactoryInterface
{
public function __invoke(ContainerInterface $container, $name, callable $callback, array $options = null)
{
/** #var TreeRouteStack $treeRouteStack */
$treeRouteStack = $callback();
if (!$treeRouteStack->getRequestUri()) {
$treeRouteStack->setRequestUri(
new Http($container->get('config')['server_url'])
);
}
return $treeRouteStack;
}
}
I can't believe it but I did it :)
Hope it will work for all of You.
In controller where the fromRoute() function is used I added those lines below:
$event = $this->getEvent();
$http = $this->getServiceLocator()->get('HttpRouter');
$router = $event->setRouter($http);
$request = new \Zend\Http\Request();
$request->setUri('');
$router = $event->getRouter();
$routeMatch = $router->match($request);
var_dump($this->url()->fromRoute(
'route_parent/route_child',
[
'param1' => 1,
'param2' => 2,
)
);
Output:
//mydomain.local/route-url/1/2
Of course route_parent/route_child is not a console route but HTTP route :)
Thank #Alexey Kosov for response. You will probably have an issue when your application is working under subdirectory rather than root directory after domain '/'.
You have to add:
$router->setBaseUrl($config['website']['path']);
Whole code:
<?php
// Module.php
use Zend\View\Helper\ServerUrl;
use Zend\View\Helper\Url as UrlHelper;
use Zend\Uri\Http as HttpUri;
use Zend\Console\Console;
use Zend\ModuleManager\Feature\ViewHelperProviderInterface;
class Module implements ViewHelperProviderInterface
{
public function getViewHelperConfig()
{
return array(
'factories' => array(
'url' => function ($helperPluginManager) {
$serviceLocator = $helperPluginManager->getServiceLocator();
$config = $serviceLocator->get('Config');
$viewHelper = new UrlHelper();
$routerName = Console::isConsole() ? 'HttpRouter' : 'Router';
/** #var \Zend\Mvc\Router\Http\TreeRouteStack $router */
$router = $serviceLocator->get($routerName);
if (Console::isConsole()) {
$requestUri = new HttpUri();
$requestUri->setHost($config['website']['host'])
->setScheme($config['website']['scheme']);
$router->setRequestUri($requestUri);
$router->setBaseUrl($config['website']['path']);
}
$viewHelper->setRouter($router);
$match = $serviceLocator->get('application')
->getMvcEvent()
->getRouteMatch();
if ($match instanceof RouteMatch) {
$viewHelper->setRouteMatch($match);
}
return $viewHelper;
},
'serverUrl' => function ($helperPluginManager) {
$serviceLocator = $helperPluginManager->getServiceLocator();
$config = $serviceLocator->get('Config');
$serverUrlHelper = new ServerUrl();
if (Console::isConsole()) {
$serverUrlHelper->setHost($config['website']['host'])
->setScheme($config['website']['scheme']);
}
return $serverUrlHelper;
},
),
);
}
}
I had an similar problem with zend console - the serverUrl view helper also not worked properly by default.
My case:
/module/Application/src/Application/Controller/ConsoleController.php
...
public function someAction()
{
...
$view = new ViewModel([
'data' => $data,
]);
$view->setTemplate('Application/view/application/emails/some_email_template');
$this->mailerZF2()->send(array(
'to' => $data['customer_email'],
'subject' => 'Some email subject',
), $view);
...
}
/module/Application/view/application/emails/some_email_template.phtml
<?php
/** #var \Zend\View\Renderer\PhpRenderer $this */
/** #var array $data */
?><!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link ... rel="stylesheet" />
<title>...</title>
</head>
<body>
<div style="...">
... <img src="<?= $this->serverUrl() ?>/images/logo-maillist.png" width="228" height="65"> ...
<p>Hello, <?= $this->escapeHtml($data['customer_name']) ?>!</p>
<p>... email body ...</p>
<div style="...">
some action ...
</div>
...
</div>
</body>
</html>
The serverUrl view helper returns just only "http://" under Console Controller (runned by cron). But the same template renders properly under web http requests handled by other controllers.
I fixed it by this way:
/config/autoload/global.php
return array(
...
'website' => [
'host' => isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'my.production.domain',
'scheme' => 'https',
'path' => '',
],
);
/config/autoload/local.php
return array(
...
'website' => [
'host' => isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'my.local.domain',
],
);
/public/index.php (the ZF2 engine starting script)
chdir(dirname(__DIR__));
// --- Here is my additional code ---------------------------
if (empty($_SERVER['HTTP_HOST'])) {
function array_merge_recursive_distinct(array &$array1, array &$array2)
{
$merged = $array1;
foreach ($array2 as $key => &$value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = array_merge_recursive_distinct($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
$basicConfig = require 'config/autoload/global.php';
$localConfig = #include 'config/autoload/local.php';
if (!empty($localConfig)) {
$basicConfig = array_merge_recursive_distinct($basicConfig, $localConfig);
}
unset($localConfig);
$_SERVER['HTTP_HOST'] = $basicConfig['website']['host'];
$_SERVER['HTTP_SCHEME'] = $basicConfig['website']['scheme'];
$_SERVER['HTTPS'] = $_SERVER['HTTP_SCHEME'] === 'https' ? 'on' : '';
$_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST'];
unset($basicConfig);
}
// ---/End of my additional code ---------------------------
// Setup autoloading
require 'init_autoloader.php';
...
And that's all that I changed.
Magic! It works! :-)
Hope this helps to someone too.
I have application created in Zend Framework 2. I would like to run controller action as cron job. I read a lot about it but I don't know how to run it.
I did something like that. I added console route to module's module.config.php
'console' => array(
'router' => array(
'routes' => array(
'cronroute' => array(
'options' => array(
'route' => 'updateproducts',
'defaults' => array(
'controller' => 'Application\Controller\Console',
'action' => 'update'
)
)
)
),
),
),
I created Controller named ConsoleController
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Application\Model;
use Application\Model\Auth;
class ConsoleController extends AbstractActionController
{
protected $usersTable = null;
public function updateAction()
{
$this->getUsersTable()->cronTest();
return false;
}
public function getUsersTable()
{
if (!$this->usersTable) {
$sm = $this->getServiceLocator();
$this->usersTable = $sm->get('Application\Model\UsersTable');
}
return $this->usersTable;
}
Inside Auth.php
public function cronTest(){
$data = array(
'login' => 'cronZend'
);
$this->tableGateway->insert($data);
}
I think that everything here is OK and I tried to write something inside command line on shared server:
/usr/local/bin/php /home/****/domains/****/public_html/public/index.php updateproducts
and it doesn't work. When I test it writing something like that in cron.php
$dbc = mysqli_connect(****);
$query = "INSERT INTO vm_user (login) VALUES ('cron')";
mysqli_query($dbc, $query);
and in command line
/usr/local/bin/php /home/****/domains/****/public_html/cron.php
it works fine. Could anyone tell me what is wrong or how to do it?