Chadwick Meyer
Chadwick Meyer

Reputation: 7331

Customize the Location and Order that Symfony auto loads classes, controllers, templates, files, etc

I am building a hosted CMS that uses core files for the platform, but allows us to create custom files for clients if necessary, that are used instead of the core. So my core Symfony compatible code lies in directories like this:

// root directory
/var/www/core/cms/ 
// symfony app directory
/var/www/core/cms/app
// my cms bundle
/var/www/core/cms/src/Gutensite/CmsBundle
// web root
/var/www/core/web
etc.

These follow the standard naming conventions and file locations so symfony auto loads classes whenever I need them. The Symfony structure allows me to override vendor bundles as needed, by putting vendor bundle folders and files in either my /app or /src/ directory. See Docs.

"Bundle inheritance allows you to override any existing bundle in order to customize its 
 controllers, templates, or any of its files." 

But my client folders will exist in a different vhost id directory, e.g.:

// the root of the client vhost directory
/var/www/vhosts/999/
// will reflect the same structure as the core app
/var/www/vhosts/999/src/

So if I have a core Controller called:

/var/www/core/cms/src/Gutensite/MenuBundle/Controller/MenuPrimaryController.php

And I want to make a custom version of that for a client, I would put it here:

/var/www/vhosts/999/src/Gutensite/MenuBundle/Controller/MenuPrimaryController.php

My main app has an InitController that normally would load the controller like this:

$namespaceController = 'Gutensite\MenuBundle\Controller\MenuPrimaryController';
$contentController = new $namespaceController();

So I would like to just change the path of that controller to a different namespace and let Symfony load it, e.g.

// I think namespace can't have parts that start with numbers
$namespaceController = 'vhost999\src\Gutensite\MenuBundle\Controller\MenuPrimaryController';

Right now, my code does this manually by checking if the file exists, and then requiring the file. Then the new command loads the right controller. But I would like to use symfony's auto loading if I possible (and get expert advice on the best way to accomplish this).

And I would like to better understand how to customize the ORDER and LOCATION that symfony looks in to override bundles, etc. So that I can have it look in the following places in the following order:

1. Custom Directory (/var/www/vhosts/999/src/Gutensite/MenuBundle)
2. Template Directory (/var/www/core/cms/src/Gutensite/TemplateBundle/Controller/MyDesign/Gutensite/MenuBundle
3. Core Vendor Directory (/var/www/core/cms/src/Gutensite/MenuBundle)

Update--Mostly Solved:

Cerad's suggestion answers the majority of this question, i.e. how to prioritize where Symfony looks when it tries to load controllers. Following his example, I register an alternative location of files for all namespaces that start with Gutensite by adding this to the app/autoload.php

$loader->add('Gutensite',   '/var/www/vhosts/1/src', true);

Then in my controller I confirm that it's registered by printing out:

print_r(ClassMapGenerator::createMap('/var/www/vhosts/1'));

Which returns:

Array
(
    [Gutensite\MenuBundle\Controller\MenuPrimaryController] => /var/www/vhosts/1/src/Gutensite/MenuBundle/Controller/MenuPrimaryController.php
)

That is the correct path and indeed my custom controller loads, instead of the default controller. That is exactly what I needed.

Alternative Namespace for Specific View Record

I also had a slightly different use case that needed to be addressed. Sometimes I will want to create a custom controller for all instances of a content type (like above). Other times I will need to create a specific instance that is for a specific page (e.g. view ID 35160).

I tried a lot of different ways to organize this so that I can include the ID either in the namespace or the object and the file name, but evidently there are lot of limitations on how you can use numbers, underscores and dashes (see notes at the end). So this is the naming convention that ended up working:

Put the custom file in the same folder as the generic controller, and put the specific view ID number in the file name, without underscore or dash: /var/www/vhosts/1/src/Gutensite/MenuBundle/Controller/MenuPrimaryController35160.php

namespace Gutensite\MenuBundle\Controller;

class MenuPrimaryController35160
{
    public function indexAction()
    {
        echo "\n custom MenuPrimaryController for View ID 35160 in /var/www/vhosts/1/src";
    }
}

IMPORTANT NAMING CONVENTION NOTES:

My attempts were failing repeatedly because I tried to use a number, dash, and underscore as part of the namespace path, object name, or file name and they all fail in different ways.

  1. According to comments on php.net Namespaces cannot have parts that are just numbers, e.g. foo/1/bar/ will fail but foo/bar1 will work.

  2. Underscores used in Auto Loading PSR-0 specs will be converted to a directory separator, e.g. foo/bar/class_1 would map to foo/bar/class/1 not to a file called class_1.

  3. Object names should not include special characters like "-".

Remaining Question: Modify Auto Loading in Controller

Can I modify the auto loading locations, later from within the controller (rather than in the app/autoload.php method used above). I need to register another path for the Gutensite namespace for alternative file location, and it needs to point to the right template folder, but I don't know which template they are using until after I load the site information in my primary controller.

I've created another question for this issue.

Upvotes: 0

Views: 565

Answers (1)

Cerad
Cerad

Reputation: 48865

You will perhaps notice that web/app.php loads app/bootstrap.php.cache which in turn loads app/autoload.php.

A typical app/autoload.php might be:

$loader = require __DIR__.'/../vendor/autoload.php';

$loader->add('Cerad',   __DIR__  . '/../../cerad2/src', true);

AnnotationRegistry::registerLoader(array($loader, 'loadClass'));

return $loader;

So Symfony 2 actually uses the autoloaded generated by composer. You probably don't want to mess with the composer stuff though it worth looking at vendor/composer/ClassLoader.php to see the available functionality.

However, the loader has an add method which allows you to tweak things. Notice the true being passed as the third argument in my example. This means to prepend the new path onto the list of existing paths. So classes with namespace of Cerad will first look under the cerad2/src directory and then whatever composer says.

I have not tried using a number for a directory but I don't really see any reason /var/www/vhosts/999 won't work as a namespace path. So just tweak app/autoload.php and you should be good to go.

Upvotes: 1

Related Questions