Three months ago I needed to write a small web app that was very UI-focused (no emails except for system alerts, very little database access, no connections to other systems, etc). I wanted to move on from Zend Framework 1 but my experiences with Zend Framework 2 up until that point had not been great, so I looked around for a more lightweight alternative.
After (re-)evaluating the usual suspects (CodeIgniter, CakePHP, Yii, Symfony, etc), I settled on Laravel 3. I knew Laravel 4 was on the horizon, but given that this app needed to be completed quickly and would be used by over a thousand people in April, I made the decision to stick with Laravel 3, which had many more blog entries and other resources on the web to help solve problems that I knew I would inevitably encounter along the way.
Well, I am happy to report that the app worked pretty well. With over a thousand users working on the full range of mobile and desktop platforms, I received only four email alerts from my application code and two incident reports from users saying they had trouble with the site. Based on that experience, I was pretty happy to commit to upgrading the app to Laravel 4.
My requirements when I set out to do this were to keep the framework code/project separate from the application code/project and to set things up in such a way that I could easily upgrade the framework code from Github whenever I desired. I work in Zend Studio 9, but I’ll refer to it as Eclipse, because for plain vanilla PHP development it is pretty much like working in Eclipse PDT.
To set up my local copy of the Laravel 4 framework I did the following:
Although the application folder structure in Laravel 4 is quite similar to the one in Laravel 3, after spending a couple of hours trying to refactor the Laravel 3 application I decided that I could achieve a much cleaner result by starting with a clean copy of the template application and then moving my code across one piece at a time. The steps I followed to do this were:
In order for the new application to work we need to:
Once those steps are completed the application should run and display the “You have arrived” screen.
UPDATE: artisan optimize will not work properly using the structure outlined above, because it expects to find the vendor folder in the root folder of the project (ie. at the same level as the app, bootstrap and public folders). This means that database migrations and other tasks that run the optimize function at the end will also not work. The simple fix on OS X and Linux is to create a soft link to the vendor folder in the Laravel framework project, in which case you could probably also skip item number two above. The other option, of course, is just to start your new project using composer create-project laravel/laravel project-name, as per the Quickstart Guide, which means you can avoid all this re-wiring in the first place.
† UPDATE 2: So now that I’ve researched what artisan optimize actually does, it has also occurred to me that there are some very important implications in trying to keep the application project separate from the framework project. My underlying motivation for this is to allow several projects to share the same copy of the framework, which has certain benefits in terms of version control and deployment. However, it turns out that this approach has some significant implications that may come back to bite me in the future. Most significantly, if you have multiple projects sharing one copy of the framework code, you should not run artisan optimize, because it will generate a classmap file in the vendor/composer folder that refers to classes in the project that you are optimizing. Consequently, it means that you cannot use Laravel’s Migrations either, because artisan migrate:make will run artisan optimize at the end of the process to re-generate the classmap. I’m guessing there are probably many other artisan commands that are going to invoke the optimize operation as well, so I think that naively trying to avoid artisan optimize is not the solution. Clearly, the solution is to ignore my advice above and do not configure two application projects to use the same vendor folder. Oh well, I tried! :-)
For starters, there is a very short list of things you’ll need to convert on the Laravel Wiki.
The following sections in this article are basically a log of all the things I did when converting my application, which was reasonably simple but did cover most of the basics.
This is the easy bit - just copy the contents of your current public folder over to the public folder in your new application project, taking care not to overwrite index.php, which is, of course, different in Laravel 4.
Laravel 4’s approach to configuration is basically the same as Laravel 3’s, with configuration options for database, session, auth, cache, etc., all split into seperate files. Environment-specific folders are also available, as before, to keep environment-specific configuration, which will always override the other configuration. Just remember that the test folder is for config related to unit testing, so you can’t use “test” to refer to your system testing environment.
The next thing I did was move my controllers across one at a time and just kept hitting them in my browser as I converted them to find the next issue that needed to be fixed. The major things that needed to change were:
Views were probably the easiest thing to upgrade and it is probably worth doing these before the controllers, because you probably won’t get too far into your controller code without them.
Environments were previously set in application/paths.php. They are now set in bootstrap/start.php. Note that “local” has been arbitrarily designated as the default “local” environment, which I would normally refer to as “development” environment.
There is a cool new way of defining environment-specific configuration stuff. Basically, if you have a file called some-environment-name.php in the app/start folder, that file will only be run when you’re in that environment. The skeleton application has a file called “local.php” in that folder by default, which corresponds with the “local” environment I referred to earlier.
Some things that were previously in application/start.php (like informing the class loader about additional directories) are now in app/start/global.php. Although I was pretty impressed with Laravel 3’s approach to configuration, the new and improved approach in Laravel 4 is even better.
You can no longer register the log listener using Event::listen( 'laravel.log', ...). You need to register the listener with the Log class instead, like this: Log::listen(function($level, $message, $context) { ... });.
Although session management seems much the same from a user perspective, there were two significant things that I learned during the upgrade:
I wasn’t using Eloquent very heavily in the Laravel 3 version of my application, so I didn’t have too many changes to make in my models.
Custom authentication was a very hard nut to crack. Although the first article that came up when I Googled provided a useful overview, it did not provide a working example. Based on that article and reading the code I ended up figuring it out but then later stumbled across another article that was much more detailed.
Here are some notes I made during the process of converting my custom authentication code:
Tasks are now called Commands and while you execute them using artisan, creating them is a bit trickier in Laravel 4. My advice is to work through the examples given in the documentation verbatim until you understand how things link up. And I wouldn’t recommend copying your old code across and refactoring it either. It will be easier to let artisan create the stubs and then paste your old code into the appropriate places in the new class.
If, for example, you want to convert a task called “dosomething” so that you can run it in your Laravel 4 project by typing artisan dosomething, I would recommend the following process:
Three important points I’d like to make about Commands are:
It took roughly three full days of effort (lets call that 24 hours) to convert the application from Laravel 3 to Laravel 4. Not including app/config, app/lang, app/tests or any other Laravel code that exists in the project structure, both projects consisted of a little over 2,500 lines of code. The exact line counts for the original project are as follows:
Folder | LOC |
---|---|
controllers | 540 |
helpers | 183 |
models | 927 |
tasks | 384 |
views | 476 |
Total | 2,510 |
I’m sure there will be some more articles written on converting applications from Laravel 3 to Laravel 4, so if you spot one, please feel free to link to it in the comments so that others will benefit.