Silex: getting your project structure right

Posted on by 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:

  1. A bootstrap and application file which could be used by my frontend controller, as well as by PHPUnit

  2. A web directory that is accessible for the world, containing only index.php and possibly some resources

  3. A directory structure which reflects my namespaces (Acme\SomeClass => /src/Acme/SomeClass.php)

  4. Autocomplete in my IDE (i.e. PhpStorm) for all Silex and Symfony classes that are packed in silex.phar

  5. 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.pharand 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:

  1. In your "Project" panel rightclick on "External Libraries"

  2. Select "Configure PHP Include Paths..."

  3. Click on the plus icon to add an include path

  4. Select the Silex directory containing all Silex files

  5. 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!

PHP Silex
Comments
This website uses MailComments: you can send your comments to this post by email. Read more about MailComments, including suggestions for writing your comments (in HTML or Markdown).
Solid

Hi! i would like have some advice for a project structure a little bigger than this. I would like have some advice ...

Clops

I have prepared a minimal silex template which should fit most simiple web-sites right out of the box.

I just finished a symfony tutorial and i was researching best practices for structuring a silex application when i ran into your post. Sorry for posting on your old post!

It seems using silex to write a full-fledged web application (and using high standards) brings you to a symfony framework clone.In this scenario (a full-fledged app with admin) Is there still an advantage with using silex over symfony framework? (Other than probably gaining a deeper understanding on how things work and why certain decisions were made)

quazardous

How do you make your API doc in silex ? phpDoc ?

Matthias Noback

I haven't, but phpDocumentor would work fine I assume.

emmanuel Nyachoke

The method you used to autoload your class seems to been removed from silex. Please update as necessary for the sake of others. Use mpmedia's method. Thanks.

Martijn

Oh, correction you only need app.php inclusion but not bootstrap.php again ;-)

Martijn

Hi Matthias,

First of all thanks for the tuts you share on your website ;-)
However I found a double app.php inclusion which I believe isn't
necessary.

Keep up the goodwork!

Requirement #1:
// /app/app.php
require_once __DIR__.'/bootstrap.php';
[...]
return $app;

Requirement #2:
// /web/index.php
require_once __DIR__.'/../app/bootstrap.php';

$app = require_once __DIR__.'/../app/app.php';

$app->run();

mpmedia

Hi , you can recycle the autoload file and use it as an autoloader :


$loader = require ROOT.'/vendor/autoload.php';
$loader->add("MyNamespace",DIRECTORY_OF_THE_NAMESPACE);

Matthias Noback

That's right, thanks for your suggestion.

Jatin

I am getting the below error:

Fatal error: Uncaught exception 'PharException' with message 'phar "C:\xampp\htdocs\xampp\silex\vendor\silex.phar" SHA1 signature could not be verified: broken signature' in C:\xampp\htdocs\xampp\silex\vendor\silex.phar:11 Stack trace: #0 C:\xampp\htdocs\xampp\silex\vendor\silex.phar(11): Phar::mapPhar('silex.phar') #1 C:\xampp\htdocs\xampp\silex\app\bootstrap.php(15): require_once('C:\xampp\htdocs...') #2 C:\xampp\htdocs\xampp\silex\web\index.php(5): require_once('C:\xampp\htdocs...') #3 {main} thrown in C:\xampp\htdocs\xampp\silex\vendor\silex.phar on line 11

How to fix it ?

Matthias Noback

It might be a good idea to install Silex using Composer or the archive, not with a .phar file, which is sometimes problematic, see also http://silex.sensiolabs.org....

Jatin

I did as per your comment, even the .htaccess also, but I get the error:

Server error!

The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there was an error in a CGI script.

If you think this is a server error, please contact the webmaster.
Error 500
localhost
7/20/2012 6:33:03 PM
Apache/2.2.17 (Win32) mod_ssl/2.2.17 OpenSSL/0.9.8o PHP/5.3.4 mod_perl/2.0.4 Perl/v5.10.1

Sebastian Szostek

Hello,

I'm just starting with Silex and this post, especially the part about autocompletion, helped a lot (and for the record, in NetBeans it's configured almost exactly the same: Projects panel > Properties > PHP Include Path ).

Now off to read the post about setting up for unit testing!

dennis

Do you have any idea how to auto-complete the services. I am new to both symfony and silex. E.g. how to make auto-complete on monolog used as a service in silex, e.g this:

$app['monolog']->addDebug('Testing the Monolog logging.');

Matthias Noback

Thanks, nice to hear! Good luck.

Denis

Hi Matthias,

I am new to Silex and can seem to understand a problem. I followed your project folder structure but when I try to load a new provider it doesn't seem to find it. I am registering the new namespace but no luck. When I directly include the file it seems to be working perfectly. Any ideas?

Thanks in advance,
Denis R.

Denis

Hi Matthias,
I found the problem and it was all my fault. I added another directory level so the path was wrong.
Sorry for that,
Denis R.

Matthias Noback

Ah, that's good to hear - have fun with Silex!

Chris

Quick question...

Where you set up your autoloader in "Requirement #3" did you mean to label the file as app.php rather than bootstrap.php? In bootstrap.php we're not creating the application...

Cheers,
Chris

Matthias Noback

Hi Chris,

bootstrap.php is meant for common "bootstrapping", to provide both the tests and the application with anything they might need (for example, if you need some "old school" PHP library, you may require it's main file here, or define some constants).

app.php contains the definition of the Silex application. A fresh app should created for each functional test, therefore it is not in bootstrap.php

I hope this clarifies things.

Good luck,

Matthias

Chris S

Hi Matthias,

That's exactly what I meant!

However, in your example you label the file where the application is set up as "bootstrap.php" where I'm guessing it should be "app.php"!

Cheers,
Chris.

Matthias Noback

Ah, of course, you are right; I corrected the comment line which said "in /app/bootstrap.php". Thanks!

Greg

Hello.

Nice tuts.

Did you try : https://github.com/lyrixx/S... ?

Matthias Noback

Thanks, I browsed through the repository of the Silex Kitchen Edition, but I think it contains too many files out-of-the-box. Starting as light-weight as you can should be recommended. Anyway, I have used Silex only for non-UI web applications, but when you mainly develop Silex applications with a visual front-end, the Kitchen Edition could be very useful indeed.