We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Micro app router map->via with Collection?

I'm setting up a Phalcon app using the Micro class. I'm setting up my router to use the Collection class so that I can use a controller to split up the functionality and keep it fairly contained.

Now, what I'd like to do is have a route that handles both GET and POST. With the normal micro app setup it looks pretty easy with $app->add(…)->via(['GET', 'POST']);. However, the collections class offers a map method but nothing like a via.

Does anyone know the best way to achieve what I'm trying to do while still using Collection? Code in the router looks simply like:

<?php

use Phalcon\Mvc\Micro\Collection;

$login = new Collection();
$login->setHandler('Service\Controllers\LoginController', true);
$login->setPrefix('/login');
$login->post('/basic', 'usernameAction');
$login->map('/social/{oauthProvider}', 'socialAction')->via(['GET', 'POST']);

$app->mount($login);

(Note: this is just an included file, so auto-loading, defining $app and so on is fine, just in another file. It also includes the map/via combination that doesn't work just to highlight what I'm trying to do.)

Thanks!

Well it's actually new feature request, we can't add it as via method beacause there is no operation on objects, everything is done on plain array elements. Calling this via method would change just last added element, in some cases it would change what we don't want. What i propose is to add new method mapVia, which will accept methods parameter. In phalcon 4.0.0 just add new argument to map with methods.

edited Dec '17

I have done this for a project.

namespace Myns;
use \Phalcon\Mvc\Micro\Collection;

//Overrides collection methods in order to inject namespace and baseUri
class MicroCollection extends Collection
{
    private $_config;

    /**
     * Inject current namespace and normally proceed further treatment of request to a parent method
     * @param null $handler
     * @param bool $lazy
     * @return mixed
     * Default zephir signature:
     * public function setHandler(var handler, boolean lazy = false) -> <Collection>
     */
    function setHandler($handler = null, $lazy = true)
    {
        return parent::{__FUNCTION__}(__NAMESPACE__ . '\\' . $handler, $lazy);
    }

    /**
     * @param null $prefix
     * @return mixed
     * Default zephir signature:
     * public function setPrefix(string! prefix) -> <Collection>
     */
    function setPrefix($prefix = null){
        //inject baseUri to every route prefix
        return parent::{__FUNCTION__}($this->_config->application->baseUri . $prefix);
    }

    /**
     * @param null $routePattern
     * @param null $handler
     * @param null $name
     * @return mixed
     *
     * Default zephir signature:
     * public function get(string! routePattern, handler, var name = null) -> <Collection>
     */
    function get($routePattern = null, $handler = null, $name = null){
        //auto prepend baseUri if route prefix is not set
        !self::getPrefix() && $routePattern = $this->_config->application->baseUri . $routePattern;
        return parent::{__FUNCTION__}($routePattern, $handler, $name);
    }

    /**
     * @param null $routePattern
     * @param null $handler
     * @param null $name
     * @return mixed
     *
     * Default zephir signature:
     * public function post(string! routePattern, handler, var name = null) -> <Collection>
     */
    function post($routePattern = null, $handler = null, $name = null){
        //auto prepend baseUri if route prefix is not set
        !self::getPrefix() && $routePattern = $this->_config->application->baseUri . $routePattern;
        return parent::{__FUNCTION__}($routePattern, $handler, $name);
    }

    /**
     * MicroCollection constructor.
     *
     * @param \Phalcon\DiInterface|null $di
     */
    final function __construct(\Phalcon\DiInterface $di = null){
        //resolve config object into local scope
        $this->_config = $di->getShared('config');
    }

    /**
     * @param string|null $routePattern
     * @param string|null $handler
     * @param string|null $name
     * @return bool
     * This method creates both GET and POST routes automatically
     */
    final function via(string $routePattern = null, string $handler = null, string $name = null){
        self::get($routePattern, $handler, $name);
        self::post($routePattern, $handler, $name);
        return true;
    }

You basically only need the very last method (via).

Only change self to parent:

 parent::get($routePattern, $handler, $name);
 parent::post($routePattern, $handler, $name);

Usage:

        $authModule = new MicroCollection(self::getDI()); //I need DI for config object etc, you might not need it...
        $authModule->setHandler('AuthModule');
        $authModule->via('/login', 'customerLogin', 'customer_login_module'); //This method creates both GET and POST routes automatically

Micro currently doesn't support "via". It carries on treating the method as NULL. Internally Phalcon will use NULL as a signal meaning this applies to ALL request methods. If it sees the URL, it won't even check the request method used. In otherwords if you're using map, there's no reason to write via(['GET', 'POST']) because it will apply to all request methods. From the callback there ,you can check the request method used if you want to deny anything that wasn't GET or POST.

I added method mapVia anyway. I guess in phalcon 4 we cant add argument to map method so it will accept method. Sometimes Kevin you want to map specifcally to GET and POST, without DELETE, PUT and other methods.

edited Dec '17

True, although we could add this as early as Phalcon 3 since it wouldn't break the current Micro functionality.

In the past I've used:

    class MicroCollection extends \Phalcon\Mvc\Micro\Collection
    {
        public function setName($name)
        {
            $this->_handlers[count($this->_handlers)-1][3] = $name;
            return $this;
        }
        public function via($methods)
        {
            $this->_handlers[count($this->_handlers)-1][0] = $methods;
            return $this;
        }
    }

The idea being it would just grab the last handler referenced and update its request method array. Same idea with adding setName so we can name routes. This allows two new methods to be added without changing anything else in the zephir.

I see you added a mapVia to 3.3.x. While that would probably be faster speed wise than my approach, I think we should add a via anyways as people would expect one to exist.

As you're already working on Micro, you mind adding these two for me?

Not sure if this is good idea this thing operating on array, but i guess it won't break anything or don't do anything unexpected, yea i guess i could add those two, though you can set already a name of added route by setting third parameter so it's not needed really, better to avoid unncessary method calls.

Thanks for the feedback to the problem, adding mapVia into 3.3.3, and giving me possibly ways I could do what I want to now. Much appreciated!