Solved thread

This post is marked as solved. If you think the information contained on this thread must be part of the official documentation, please contribute submitting a pull request to its repository.

Define dynamic actions in the root

I have a website made in PHP Phalcon, and it has several controllers.

Now I need this website to receive words in the URL (example.com/WORD) and check if they exist in the database, but I must continue to support other controllers like example.com/aboutme.

After few hours of trying different methods and searching online I cannot find a way to accomplish this task. The closer intent was creating a Route to redirect non-existing actions to a new controller, but I cannot make this solution work.

Can you think on a solution that may work and share the code/idea? I am not adding any code because I could not get to do anything useful.

edited Nov '17

I would suggest you define a notFound route at the very bottom of your router:

$router->notFound(['controller'=>'Routing','action'=>'notFound']);

Then you'd define a controller whose sole purpose is to assist with routing within your application.

Define a controller: class RoutingController extends \Phalcon\Mvc\Controller
Then you'd define a notFoundAction method inside it.

Preform any database queries you need within this controller. Then decide after these queries where you need to route to.

public function notFoundAction()
{
    $url = filter_var(trim(urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)),'/'), FILTER_SANITIZE_URL);
    $page = Pages::find($url); // Or whatever you decide to name the model for this
    if(!$page){return notFoundPage();} // Display a 404. Avoid usage of exit(), so tests can test this
    $namespace = __NAMESPACE__ . '\\';
    $controller = $page->controller.'Controller';
    $action = $page->action. 'Action';
    $parameters= $page->parameters;
    return call_user_func_array([$namespace.$controller,$action], $parameters);
}

The next step is defining a Pages model and executing whatever queries you need.
In this manner you'd achieve about as perfect of an architecture that's possible with Phalcon's router.
While it's possible to extend Phalcon's router to execute queries before routing is done, I feel queries should be executed at the controller level, rather than the routing level, then simply hand control over to another controller.

See also: https://docs.phalconphp.com/en/3.2/dispatcher
$this->dispatcher->forward( ... ) is probably a cleaner solution.

edited Nov '17

Hi @Ultimater,

Thanks for your answer.

That was the first thing I tried, but I could not make the Route notFound() to work. When I use the code below, the action "open" is capturing all the requests from both existing or non-existing controllers.

Basically if I call "example.com/WORD" or "example.com/aboutme" both go to "example.com/index/open". Can you help me with a set of rules to fix this problem?

$di->set('router', function() {
    $router = new \Phalcon\Mvc\Router(false);
    $router->add("/:controller/:action", ['controller'=>1, 'action'=>2]);
    $router->notFound(['controller'=>'index', 'action'=>'open']);
    $router->handle();
    return $router;
});

Thank you in advance!



3.5k
Accepted
answer
edited Nov '17

The problem with your /:controller/:action route is that it requires two parts. You're testing this on a URL with only one part.
If you're looking to make "index" your default controller, you would add something like this:

$router->add("/:action", ['controller'=>'index', 'action'=>1]); 

Or if each page is its own controller, and you want to use a default action instead, you'd do something like this:

$router->add("/:controller", ['controller'=>1, 'action'=>'index']);

You can also play with:

$router->setDefaultController("index");
$router->setDefaultAction("index");

but I'd recommend against using these "default" methods as their behavior is not obvious without doing some testing.

It's also wise to write:

$router->add("/", ['controller'=>'index', 'action'=>'index']); 
edited Nov '17

After all, rules did not work, so I came out with a workaround. I am catching the the exception raised when a controller don't exist and running my code there. So far the only issue is the bitter taste of contributing to spaghetti code :-(

try{
    $application = new Application($di);
    echo $application->handle()->getContent();
}
catch(\Phalcon\Mvc\Dispatcher\Exception $e)
{
    $word = substr($e->getMessage(), 0, strpos($e->getMessage(), "Controller"));
    // RUN ESPECIFIC CODE HERE
}
edited Nov '17

Not sure why you'd need to resort to a try-catch, probably related to your usage of $router->handle(); when you're also doing $application->handle() and thus handling routing twice.

Also I'd suggest $application->handle()->send(); rather than echo $application->handle()->getContent();
The difference is send() also checks for headers set through Phalcon and outputs them at this point in the application as well. With getContent() you only get the HTML, and any headers you try to set through Phalcon won't be set. Setting headers this way is useful for tests so you can check for a specific header without actually sending a header response.