Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a URL in a Console Controller in ZF2?

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?

like image 781
Nick Avatar asked Dec 04 '14 13:12

Nick


3 Answers

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.

like image 96
Alexey Kosov Avatar answered Oct 18 '22 18:10

Alexey Kosov


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:

  1. AbstractActionController
  2. AbstractRestfulController
  3. 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

like image 29
Kwido Avatar answered Oct 18 '22 16:10

Kwido


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;
    }
}
like image 1
Erik van Velzen Avatar answered Oct 18 '22 16:10

Erik van Velzen