2011S
A First Silex Project
Although Symfony2 is not there yet, there is already a framework based on some of the Symfony core components, it’s the Silex microframework. It’s created by Fabien Potencier, the original developer of the Symfony framework and Igor Wiedler.
Silex is really easy for setting up a small new project and you can download the whole of it in one single file. It’s PHP 5.3 only as it makes use of namespaces and closures quite a lot. I wanted to give it a try and create a small project with it. It uses dependency injection in a very simple way which is also something I wanted to know better. Here I’ll guide you through the small project I created with Silex with as many explanations as I see fit for you to get up and running with using Silex.
This article is a quite complete project tutorial that you can follow and do yourself with Silex if you want to get up to the task with it. Silex is small, modular and really easy and fun to program with.
The Project
The project we’re going to build is a simple personal and “meaningful” url shortener. It’s called TSUSBOS (The Simple Url Shortener Based On Silex), clever huh?… It’s on a github repository at https://github.com/khepin/tsusbos and you can fork it use it fun it clone it or anything you want. So it is “simple” because it won’t do much fancy things, it is “personal” because mainly it’s meant to be installed on your personal server, it is “meaningful” because it will not force the short code of the url for you, you’ll have to choose it yourself and therefore can make links that bear something about the article / page they are referring to. As an example, my own version is located at http://u.khepin.com and if you use the link: http://u.khepin.com/crazyhdd you’ll be redirected to the much longer: http://thegadgetsite.com/2011/04/fake-samsung-hdd-appears-chinese-scammers-seem-to-have-found-the-trick-to-build-an-infinite-hard-drive/ so the tsusbos urls are not as short as http://bit.ly/uY7o, but are much more easy to understand and since it’s mostly personal I don’t think I’ll ever have billions of urls to share.
What does it do?
- It has a homepage that explains very briefly what it is.
- It can display a list of all the urls that it is managing.
- It can add a new url and short name if given the correct password (to avoid everyone adding thousands of urls to your shortener just for fun).
- It can redirect you from a short to a long url.
Getting started
Project layout
I decided to organize my code as follows:
- web/ # Server root repository (would contain css, images and all assets)
- src/ # To store our code
- tests/ # Because they're here for you!
- vendor/ # Will store the Silex files and maybe some others
- resources/ # Where we will save our urls / config etc...
The web directory only contains an index.php file that does not do much. It includes a file from src/ , gets the result which is a Silex\Application and runs it. It also contains a .htaccess file that contains the rewrite rules according to the silex documentation.
First tests
The core functionality of our app will be to redirect us from one link to another. So that’s what we’re going to try right now. To make it very simple, our urls and their short names will only be an array defined in code for now. This will still allow us to create a new application and try it. The code will be:
As our project will be very simple, we only separate the bootstrapping and the real application by comments in the file. If your application is a bit bigger, you might want to move all bootstrapping into a different file.
Bootstrapping only consists of requiring the silex.phar archive. We then create a new Silex application, define our array of urls and finally define a route and the corresponding action. The route here is defined by /{url_slug} which means we’ll be able to access a $url_slug variable from our action that will be directly extracted from the url.
The action associated to a url in Silex is always a closure. It can be passed parameters from the action like the $url_slug in our case. However all we have done here is say “whenever someone hits this url, you’ll use that function, but for now, just store this function as a variable”. Nothing has been called yet. Our action will also need access to the current Silex\Application and to our urls array, but those cannot be passed as parameters as Silex will only take the url place holders and send them as parameters to the action. The use of the “use” key word allows us to put $app and $urls in our closure’s scope so they can be used later.
The code of our closure itself is then very simple: get the url from the array and redirect the user to there.
Creating a service
I’ve said earlier that Silex makes use of dependency injection and it relies on the Pimple container for that. Now we’ve seen that our basic task is working fine, let’s try using this.
First we’ll add a new class that will be holding our service. And we’ll namespace everything to benefit from autoloading. In src/ I add a folder called Khepin that will hold my personal Khepin namespace in which I can define all the new classes I need. That’s where we’ll define our UrlService class like this:
The class is very simple. First it’s declared in the Khepin namespace. Then it has a private variable that is our earlier url array. And just one method to return the url for a given key.
There are a few ways to define a service in Silex. The first one which we’ll use here, will give us a new instance of UrlService each time we call it. The other one is created by using $app->share(//…) and would return the same instance every time.
This is the new look of our app.php file. First we tell that we’ll make use of the class Khepin\UrlService meaning that we can later refer to it without the complete namespaced name. We register the namespace to the autoloader so our classes will be loaded automatically.
Finally we define our service! You can see it is very simple: just add a closure to return a new instance of our service. In our action, we redefine the way we handle the redirect. We don’t need to use the $urls array anymore because we have a service to handle this for us. Then we use our service to get the url we need. Try it: it works!
Some Twig fun: Extensions!
Though our app does not have a lot of templates (we’ll have one for a homepage, one for the url list and one success page after adding a new url). We’ll use the Twig templating engine for this.
Silex allows the use of extensions. Extensions are basically auto-registered services. First we’ll use the Twig extension, then we’ll finish our url shortener and put it in an extension too. The Twig extension is bundled in Twig, but not Twig itself so first download twig and put it under a /vendor/Twig/ folder.
We’re going to register our extension in the bootstrap part, then we’ll add the homepage action, and finally define the corresponding templates.
Registering an extension takes an instance of a Twig extension and an array of parameters:
- twig.path = where we are going to store our templates
- twig.class_path = where the twig library is located
- twig.options = defines options to pass to the Twig environment constructor. We setup a path for cache so that Twig stores cached files instead of parsing the templates for each request
Our homepage action makes use of the ‘twig’ service that has been registered by the extension and renders a template.
In templates we defined 2 of them: layout.html.twig that contains the basic structure of an html file. And index.html.twig. Index will extend layout and redefine the content block. Actually all our templates will extend layout and only change the content part. Those are not the most interesting code blocks I guess, it’s basically plain html but here they are:
And … it works! Except that the list url included here will bring you nowhere for now!
A new action
Let’s create this list. On our service class we’ll add a method to return the complete array.
Now we passed some parameters to our template, let’s see how it works:
Refining the service, adding urls
The service
We change our service class from UrlService to UrlShortener (you can keep the same name though!). We will need it to have the same possibilities as the one we just had but we need to get rid off the url array. We need to persist our urls somewhere to be able to add one. Since I only want something simple, we use a .ini file that will be stored in resources, it makes things very simple, we’ll only have:
url_short_name = http://url.com
goog = http://www.google.com
fb = http://www.facebook.com
Our UrlShortener is constructed with a link to that file, fetches the urls from there. If we add a new url, it verifies that it does not erase a previous key. It also checks that the url is valid according to a regex. Our new class looks like this:
We added the ability to parse a file and load the urls, the ability to dump the file and save all current urls and finally the ability to add one url after it’s been verified. You can see if something goes wrong in url checking we throw an exception. We’ll see a bit later that Silex can catch all those exceptions and do something with them just like any other action. In my case whenever something goes wrong, the user is redirected to the homepage.
We can now check the action to add a new shortened url. We define a url like /add/{short_name}?url=http:… or actually /add/{password}/{short_name}?url=http…
We add the key to be sure that only people who have the key can add urls to our shortener. The real url cannot be given in a url place holder because it contains “/” which would make Silex routing think that we have many more place holders that were not correctly defined. Therefore we need to pass it as a query string parameter. So what does the add function look like?
We added a secret key (to keep things simple I have no issue having it defined right in the code instead of in a config file for example) and a url to be matched that starts with add, and has a place holder for our url’s short name and one for the key. First thing we do is check the key and throw an exception if it goes wrong. Add the url to the shortener service (which again would throw an exception if anything goes wrong). And finally render a template.
All of the pieces of our project are now working fine together! But let’s push things and make them a little bit better!
Our own extension
Let’s define UrlShortener as our own extension. Once we do so, we can have other people use it inside of other projects very easily for example. If we wanted to do so though, we’d need to check that the twig service is there since we’re using it, if you check the Silex repository and the included extensions, you can find examples of how services can rely on each other by checking from the service container what other services are already present. Also if you really expect to distribute your extension, it would be better to namespace all your services with a unique name so there is no conflicts in the service container. For example, use tsusbos.url_file_name to store your file name instead of just url_file_name.
Defining an extension is pretty easy. We add a class in the Khepin folder / namespace called ShortenerExtension. It needs to implement the silex Extension interface which only has one method: register. And the register method takes a Silex\Application service container as a parameter. All we do in the register method is define UrlShortener as a service, exactly like we did the first time when we created UrlService except this time we’ll make it a shared service.
And then instead of registering a service in the bootstrap, we register our extension just like we did for twig:
Catching errors
As said before, Silex has a special way to catch all errors so that you can deal with them in a separate place. Just like we defined $app->get(//….) we use $app->error(//…) and do what we want with our errors here:
If I’m connected from the localhost, I want to see all errors. Otherwise, I redirect everyone to the hompage no matter what the error was.
Testing
Before we can say our project works and before getting further, we want to add some tests to ensure that those basic functions are working and keep working over time when we add some stuff to our application.
Testing works exactly like for symfony2 (symfony2 testing) and is based on phpunit in the same way. Since Silex is built on top of the core symfony2 components, we can use the WebTestCase, create a client and a crawler in the same way it’s done in symfony2. Let’s try!
In the test folder add a TsusbosTest.php class that extends the Silex\WebTestCase. The test case in Silex needs to implement one more method: createApp. That’s why you need to use require_once when including silex.phar, otherwise there will be some conflicts between app.php and the testing.
Functional testing mainly consist of creating an http client, making a request and test that the result contains what you expect, that the response has the correct status code, that an exception was correctly raised etc…
Then by running the phpunit command in the test folder, you’ll see all your tests going and, hopefully, succeeding!
Conclusion
Silex is a very easy framework to start with and fits very well for small applications like this one. You can do a lot more with Silex and it could work pretty well with projects a bit bigger. However when things get more complicated, when you need to have many different environments for your application, when you need to separate you controllers more and have them to more complicated things etc… Then Silex should probably not be your first choice.
For everything else, just like I said, Silex is fun, easy, and very clear and understandable for me. Doing this short project for me was just a small test run for Silex and it was enjoyable to use.
I hope all of this is useful for some of you!