Silex: getting your project structure right
Matthias Noback
When I created my first Silex project, I almost felt encouraged to let go of my high standards in programming. So things were starting to look very much like my “legacy” PHP projects, in which everything was put in functions with lengthy parameter lists and those functions were called from within a single index.php
file. I ignored many of the things about high quality software development I had learned in previous years. The result of this, was a project much less maintainable than my other recent projects.
Don’t let go of your high standards!
My second project was a lot better, since I took the internal structure much more seriously. For this project I wanted a few things:
-
A bootstrap and application file which could be used by my frontend controller, as well as by PHPUnit
-
A web directory that is accessible for the world, containing only
index.php
and possibly some resources -
A directory structure which reflects my namespaces (
Acme\SomeClass
=>/src/Acme/SomeClass.php
) -
Autocomplete in my IDE (i.e. PhpStorm) for all Silex and Symfony classes that are packed in
silex.phar
-
A set of tests, which reflects the namespaces of my classes (
Acme\SomeClass
=>Acme\Tests\SomeClassTest
)
These are things that are by default available in any Symfony2 application, but have to be provided by hand for any Silex application.
Finally, I did not want to populate the $app
variable “inline”, but use service providers, which also makes for cleaner applications.
One by one I will show you how I met these requirements.
Directory structure
First, I defined a directory structure:
/app
/src
/tests
/vendor
/web
I downloaded silex.phar
and copied it to
/vendor`.
Requirement #1: A reusable bootstrap and application file
In /app/
I created bootstrap.php
containing only a require statement for the silex.phar
file:
// /app/bootstrap.php
require_once __DIR__.'/../vendor/silex.phar';
I also added /app/app.php
, containing the actual creation of the Silex application:
// /app/app.php
require_once __DIR__.'/bootstrap.php';
use Symfony\Component\HttpFoundation\Response;
$app = new Silex\Application();
$app->get('/', function() {
return new Response('Welcome to my new Silex app');
});
return $app;
I have now met my first requirement, although we will see in another post how we can make use of these files when unit testing with PHPUnit.
Requirement #2: An almost empty web directory, accessible to the world
The last line of /app/app.php
returns the application. The return value is caught in /web/index.php
, which looks like this:
// /web/index.php
$app = require_once __DIR__.'/../app/app.php';
$app->run();
Of course, we also need an .htaccess
file, which redirects all requests to index.php
:
# /web/.htaccess
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
Now, in whatever way you are used to, create a virtual host for your new application and you will have your very skinny Silex app up and running. Make sure your document root is /web
, so everything else is not accessible for the world.
In my Apache httpd-vhosts.conf
file, it looks like this:
<VirtualHost *:80>
DocumentRoot "/var/www/silexsandbox/web"
ServerName silexsandbox
<Directory "/var/www/silexsandbox/web">
AllowOverride All
</Directory>
</VirtualHost>
Requirement #3: A directory structure which reflects the namespacing of my classes
The next quest is for a directory structure which reflects the namespacing of my project’s classes. For instance: the class Acme\SomeNamespace\SomeClass
should be found in /src/Acme/SomeNamespace/SomeClass.php
. The solution to this problem is quite easy, since Symfony2’s universal autoloader is not only included in silex.phar
, it is also available in your application as $app['autoloader']
. So after creating $app
, we just add a line for autoloading our own classes:
// in /app/bootstrap.php
$app = new Silex\Application();
$app['autoloader']->registerNamespace('Acme', __DIR__.'/../src');
Autocomplete in my IDE
One of the first things I noticed was that my IDE couldn’t index silex.phar
, so no autocompletion for Silex related classes was available. This is very annoying, in fact, it made my work almost impossible, having to look things up in the Silex API documentation, as well as in the Symfony2 API documentation. This decreased my productivity very much, so it was time to find a solution for this problem. Use no .phar
file then? But I liked it very much… I looked through the solutions given in this issue opened by Fabien. It was suggested you could clone Silex’ repository, since it contains all files necessary for running a Silex application. It also contains many more files, which you don’t want to have in your own project. But I thought, if better autocompletion is the only thing you want, you can keep your silex.phar file inside your project, but load the complete Silex project as an external library (this works well, at least for PhpStorm and Eclipse).
Somewhere outside of your project directory run the following commands:
git clone https://github.com/fabpot/Silex.git
cd Silex
git submodule init
git submodule update
Now you have all the files needed by Silex on your computer. In PhpStorm I took these steps to add the Silex directory as an external library to my project:
-
In your “Project” panel rightclick on “External Libraries”
-
Select “Configure PHP Include Paths…”
-
Click on the plus icon to add an include path
-
Select the Silex directory containing all Silex files
-
Click “OK” and again “OK”
Now PhpStorm will start indexing the Silex and Symfony2 component files, and you will have autocompletion for all classes. In fact, you will have autocompletion for more classes than are actually available in the silex.phar
file, so be a bit careful about which classes you try to use in your Silex application.
Unit testing and service providers
Silex projects need just as much unit testing as any other project does, but these tests need to be organized as well, for better maintainability. A cleaner application and thus better maintainability can also be accomplished by writing services providers for your own services instead of adding them inline. In a next post I will write about these two things.
For now: enjoy Silex and the Symfony2 components and remember: you have no reason not to keep your application clean!