Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Namespaces with Phalcon

Tags:

phalcon

All my controllers are under the namespace MyApp\Controllers so, as the documentation recommended, I've set my default namespace to it:

$dispatcher->setDefaultNamespace('MyApp\Controllers');

But now I need to not only organize my controllers in folders but also namespace them and have friendly URLs like: /features/featureX/ and /wizards/featureX/. So from that example I got MyApp\Controllers\Features\FeaturesX and MyApp\Controllers\Wizards\FeaturesX.

I believe that they shouldn't be considered modules right? They're just some custom routes, but from routing documentation I can't tell how to:

  • Declare a route that only defines the namespace (e.g $router->add("/:namespace", ["namespace" => 1]);)
  • Make the above routing strategy be used for only some controllers. For example, LoginController, should remain in the MyApp\Controllers namespace.

Maybe I can achieve this by using one router or dispatcher for each one. Any experienced Phalcon developer could please give me a light here?!

like image 802
kbtz Avatar asked Mar 23 '14 13:03

kbtz


1 Answers

Well, after a some time using Phalcon I can say that sometimes it is not so flexible when you decide to use a different approach from those we found in the project documentation. The use of namespaces is one of these cases.

Multiple namespace levels aren't handled very well by the framework, however it still well designed and extensible. You can override pretty much anything and, with a some customization, you can achieve any behavior you want.

The following is what I've done to get all my stuff organized in folders/namespaces and still make use of the whole framework.

Some of these information may be useful in your context, so here is a report of my legendary journey so far...

Phalcon vs Namespaces

OK, so if you decide to keep your PHP source code organized within folders and namespaces, get ready to tweak almost all the major functionalities and also to be much more explicit in your implementations (i.e use full class paths everywhere).

Basically you're going to throw away some of the coolest automation that the framework has to offer because they're are convention based and these conventions seems to be defined without namespaces in mind.

But, despite all this extra work ahead, I've decided to keep this design decision; After all, we'd chose a MVC framework for a reason, right?! We want to keep things organized. Specially in huge projects where is very important to have the source diluted in several namespaces/files to improve maintainability.


Directory Structure

First off, you should be aware of the directory structure I've picked to understand what I'm talking about down below. Believe me, I've spent some hours days tweaking this structure and reading about structures recommended by other MVC frameworks and after all my personal recommendation is very simple: pick the less problematic one! Generally this means the more decoupled/fragmented one.

This is the structure I'm currently using which served me well for almost two years now and fits whatever kind of code I need to throw at it:

Directory structure of the project root

A clear understanding of your directory/namespace structure removes a considerable amount guesswork while implementing new stuff and helps you to write decoupled code unwittingly. If you "waste" some time caring about this in the beginning this means less painful refactors for you in the future.


Class Loader

Did you noticed the source/ folder and it's contents organized more or less as the PSR Standards demands?! The reason for this is that the Phalcon's Class Loader is PSR-0 compliant;

So basically I just need to register only the source/ folder into the loader:

$phLoader = new PhLoader();
// Considering the public folder as your current __DIR__
$phLoader->registerDirs(['../source/']);
$phLoader->register();

...and voila! You simply refer to the class (using the full path when needed) and both PHP and Phalcon internal implementations will find it. For example, to register one of my components in my DI container:

// At the first time the service 'foo' is needed
// Phalcon will read the file at 'source/MyApp/Components/Foo.php'
// and then call Foo's constructor
$di->set('foo', 'MyApp\Components\Foo'); 

Router and Controllers

If all your controllers are on the same namespace/folder you can simply do this:

$router->setDefaultNamespace('MyApp\Controllers');

Nice!

But what if you have a controller under MyApp\Controllers\Foo\BarController?!

Just like the default loader behavior, there's no effort from the router to try to interpret the Foo portion as another namespace level.

ATTOW there's no workaround to make the router to find controllers in "deeper" namespaces. My solution so far is to turn off the default routing behavior and add manually the four common patterns for each namespace branch.

By "four common patterns" I mean these routes:

:namespace/index/index When only the namespace is defined

:namespace/:controller/index/ When only namespace and controller name is defined

:namespace/:controller/:action/ When namespace, controller and action name is defined

:namespace/:controller/:action/:params When everything is defined and includes parameters

To enable these routes for all namespaces I've replaced the :namespace regex placeholder with another regex that matches all possible namespaces and implemented a "converter" that translates the route to the correct namespace (i.e "/foo/bar/baz" --> ['namespace' => 'MyApp\Controllers\Foo', 'controller' => 'bar', 'action' => 'baz']).

Just to illustrate what I'm talking about here's a custom router that I've write some time ago and uses this technique: https://gist.github.com/snolflake/9797835


Models

You must follow what the docs say. That means, any time you refer to a model on PHQL (or relationship definitions) you need to always use the full class path.

Views

By views I mean basically the template files. Of course they aren't under namespaces, but since your controllers are, the automatic view picking doesn't work properly. So your views should be picked manually:

namespace MyApp\Controllers\Foo;

class BarController
{
    public function bazAction()
    {
        // The path is relative to the views dir you've set before
        $this->view->pick('configurations/subscriptions/index.volt')
        ...

Gladly you can make use of the event beforeExecuteRoute to automatically pick the view based on your own conventions.

Conclusion

It seems that Phalcon is aiming more in simple/trivial project, but that's perfectly reasonable and even with all these boilerplate code to get your application more fancy the rest of the framework itself make it all worth the job later on.

Meanwhile I just hope that next versions include more namespace-friendly features.

like image 192
kbtz Avatar answered Nov 09 '22 09:11

kbtz