February 16th, 2010

#sflive2010 Geoffrey Bachelet, Symfony Internals

Tom Boutell
Chief Software Architect
I'm attending the Symfony Live conference. Below are my notes from Geoffrey Bachelet's presentation on Symfony Internals. Geoffrey Bachelet wrote the "Symfony Internals" chapter of the Symfony book.

He wrote the Symfony Internals chapter

Customizable, flexible, extensible

Extensible through plugins, events, and overriding entire framework classes

The bootstrap retrieves application configuration, then instantiates sfContext and calls the frontend controller

sfContext implements the Singleton design pattern. Has pros and cons: mostly cons. Testability issues. Responsible for loading factories.

Application configuration: frontendConfiguration extends ProjectConfiguration, shares methods and constructors. Most customization happens in ProjectConfiguration.

ProjectConfiguration crewates an event dispatcher, then loads plugins.

sfEventDispatcher is available as a separate component. Implements the Observer design pattern. An object can register interest in events.

autoload.filter_config event allows you to customize the autoloader configuration.

There are more than 900 plugins in the repository today.

settings.yml holds all framework leve settings, there are a lot of them. Actions, security strategies, I18N, debug bar, etc.

app.yml is for your use. config/app.yml is a project wide app.yml, we rarely use that.

Environments allow you to specify different behavior for different environments. Different database, mailer strategy, etc.

Config Handler

Not a well known part of Symfony. Parses and translates configuration files. Each configuration file has a handler. sfDevineEnvrionmentConfigHandler, sfDatabaseConfigHandler, etc.

sfContext instantiates factories. These are components that drive your application: logger, I18N, mailer, request, response, etc. Configured through factories.yml. This allows you to to subclass the normal implementations and let Symfony know that you want it to instantiate yours instead of the parent class.

In your app's config/factory.yml.

sfFactoryConfigHandler converts config/factory.yml into executable PHP code to load the factories in question. It's all in your cache.

You can override almost every component used by Symfony via factories (see the book).

Events notified include request.filter_parameters, routing.load_configuration, and context.load_factories. context.load_factories is the earliest event at which point database access is possible.

The front controller implements the front controller pattern. Grabs module and action from the request and issues a simple forward().

sfError404Exception forces a redirect to configured 404 handler. (How about exceptions to force redirect to the "login required" and "credentials not good enough" handlers? That would save effort too.)

sfGeneratorConfigHandler: instantiates a generator. Runs it. There is no step 3.

Controllers dir: you can override getControllersDir(), which gives you control over the controllers' location. You can add any directory to the list or just replace it. (What class is this method in?)

The Action Stack

A FIFO stack. Holds every action that has been or will be executed. Access it through sfContext's getActionStack(). (When do you have multiple actions on the stack? That sounds like internal requests, which are coming in 2.0.)

You can enable and disable modules through sf_enabled_modules in settings.yml and through mod_MyModule_enabled in module.yml. Modules disabled through settings.yml cause an sfConfigurationException to be thrown, use them for permanent disabling of modules. Those disabled through module.yml redirect to the "disabled module" action (sf_module_disabled_module/sf_module_disabled/action). Better for temporary disabling of a module.

The Filter Chain

Implements the Chain of Responsibility pattern. Configurable through filters.yml. Has a config handler too.

You can write your own filters to be part of the chain of filter objects that can intercept a request. You add it between security and cache.
rendering: ~
functional_test:
  class: swFilterFunctionalTest
remember_me:
  class: sfGuardRememberMeFilter
The rendering filter does nothing before calling the next filter down (but it'll take action when that filter finally returns). A filter can execute code before the rest of the stack... but also after. So if you override the rendering filter you can add magic like autoloading of static stuff for the browser.

The secure filter implements credentials checks based on security.yml. Nice if the credentials are not dynamic and dependent on the object in question (aka "row ownership").

The cache filter has two bits of logic: it can prevent the rest of the chain from executing. It configures the HTTP cache headers. And it fills in the cache before rendering.

The execution filter actually runs your action. Checks for cache, if no cache is found, executes the action. (The cache filter doesn't do this?)

Execution workflow: preExecute(), execute via sfActions' execute() and therefore executeActionName(), then postExecute()

You can return whatever you like from an action to trigger a template by name. SUCCESS, NONE and ERROR are predefined. (You can also return $this->renderPartial or $this->srenderComponent now, which is very useful)

Handling the View

This is sfView's job. There are two ways of getting a view object, a custom sfView object for a specific action or a class name based on mod_module_view_class. (We have never overridden the view, we've always used templates)

sfPHPView loads core and standard helpers. Helper, Url, Assets, Tag and Escaping (sf_standard_helpers). Executes a view script (pure PHP) and decorates the result. Also handles some caching (partials caching?).

sfPartialView is responsible for rendering partials and components. Handles cache for partials and components.

Samples of custom views: sfTwigView integrates the Twig template engine by Fabien. sfTemplatingView integrates templating from the symfony components (the standard view?).

The rendering filter sends the response content through sfWebResponse's send method (aha, the rendering filter does do something).

Question: why should we not use sfContext? And can we configure multiple databases for one application?

Answer (sfContext): the singleton pattern means you can't easily change what singleton is being used, except with dependency injection which is not in Symfony 1. It tends to slow down unit tests to performance similar to functional tests because it is so heavy. (We at P'unk Avenue generally stick to functional tests especially since we care quite a bit what user is attempting to do what action at the model level.)

Q: can I avoid having five files called actions.class.php in my editor?

A: you could do one action per class.

Q: can you add listings for entirely new factories to factories.yml?

A: no, the list of factories to be instantiated is hardcoded. (But nothing is stopping you from loading a class name from app.yml and instantiating it, which is pretty much all factories.yml is doing. We do this to let you substitute alternative form classes etc. in sfDoctrineApplyPlugin.)

Q: What can you do by overloading the autoloader?

A (from audience): convince it to scan your own folders first. This allows you to override things that otherwise cannot be overridden, like core Propel classes. Poor man's dependency injection. (I hear this will be oh so much easier in 2.0)


Check out Apostrophe, our CMS plugin for Symfony!
Tom Boutell
Chief Software Architect

Check out another article
February 11th, 2010
Apostrophe 1.0!
February 10th, 2010
Oh So Close!