Upgrading from Laravel 3 to Laravel 4

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.

Step 1 - Framework Project

To set up my local copy of the Laravel 4 framework I did the following:

  1. Created a new project in Eclipse by selecting New -> PHP Project from Git and entering the path to the laravel framework repo: https://github.com/laravel/framework.git
  2. Ran composer update to pull down all the dependencies

Step 2 - Template Application Project

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:

  1. Created a new project in Eclipse by selecting New -> Local PHP Project
  2. Fired up Terminal and changed directories to a temporary folder
  3. Downloaded the Laravel 4 skeleton app into that temporary folder using git clone https://github.com/laravel/laravel.git
  4. Copied the following items from the laravel folder to the root folder of my new project:
  • app
  • artisan
  • bootstrap
  • public

Step 3 - Wiring It All Up†

In order for the new application to work we need to:

  1. Change bootstrap/start.php (at or around line 62) to point to the correct location of the src folder in your Laravel framework project
  2. Change bootstrap/autoload.php (at or around line 17) to point to the correct location for the vendor/autoload.php file in your framework project
  3. Give write permission to all the sub-folders within the app/storage folder in your application project

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! :-)

Step 4 - Code Conversion

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.

Public folder

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.

Configuration

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.

Controllers

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:

  • Class names: from This_Style to StudlyCase
  • Method names: from snake_case to camelCase
  • All controllers are restful by default, so you won’t need to include $this->restful = true any more
  • return Response::error(‘404’) becomes return App::abort(404)
  • $this->filter(‘before’, ‘auth’) becomes $this->beforeFilter(‘auth’)
  • $this->filter(‘before’, ‘csrf’)->on(‘post’) becomes $this->beforeFilter(‘csrf’, array(‘on’ => ‘post’))

Views

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.

  • @layout becomes @extends
  • URL::base() becomes URL::to(‘/’)
  • The parameters of the Form::open() method have changed, so to use the login URL as as example,
    {{ Form::open(‘login’, ‘post’) }} becomes {{ Form::open(array(‘url’ => ‘login’ )) }}
    (Laravel will set the form’s action attribute to POST by default)
  • You can remove calls to Form::token(), as the CSRF token (which is now called "_token") is added to forms automatically when they are opened.
  • The Form::button() now adds the type=“button” attribute to button elements, which means the button will no longer submit the form. If you want your button to submit the form you’ll need to use Form::submit() instead.
  • $validation->errors->first() becomes $validation->messages()->first()

Environment Stuff

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.

(Update) Logging

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) { ... });.

Session Management

Although session management seems much the same from a user perspective, there were two significant things that I learned during the upgrade:

  1. In Laravel 3 I found it helpful to be able to peek into the Session instance by accessing Session::$instance during debugging. The closest thing I could find in Laravel 4 was the Session::all() method. Although this doesn’t provide quite as much information, it provides enough and it is probably a much safer (and the much more technically correct :-) approach.
  2. The other big thing I learned is that if you want to store an object in the Session you will need to serialize() it first, otherwise you will end up with a class of type __PHP_Incomplete_Class rather than your actual class. Don’t forget to unserialize() it on the return journey either.

Database Access

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.

  • public static $table = ‘table_name’ becomes protected $table = ‘table_name’
  • public static $key = ‘key_name’ becomes protected $primaryKey = ‘key_name’
  • public static $timestamps = false becomes public $timestamps = false
  • DB::query() becomes DB::select(), DB::insert(), DB::update() or DB::delete() depending on the nature of your query

Custom Authentication

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:

  • Firstly, the driver “name” appears in only two places: app/config/auth.php and app/start/global.php. I felt like the driver name should have been in my custom auth code, but no, it is really just a way for Laravel to reference the custom driver (which, in my case, is actually Laravel’s Auth class and my CustomUserProvider class).
  • Although you can write a new Auth class if you wish (eg. CustomGuard), you probably won’t need to. If you are using an existing application database that already has a table for storing users and has its own hashing algorithm, you will probably just need to write your own CustomUserProvider class, which will implement the UserProviderInterface.
  • Although writing a CustomUserProvider class was fairly straightforward, the one thing that caught me out was that the retrieveByID() method was being called using the username field, not the id field. This only because obvious to me when stepping through the code, but should have occurred to me when I noticed a data item in the session that was obviously the authentication token and contained the username, not the id, as was the case in my Laravel 3 version.
  • I included my own hashing code in my CustomUserProvider class, which may not be the right way to wire things up but it works fine for me.

Tasks

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:

  1. Use artisan command:make dosomethingCommand to create the template for your command - this will create a file called dosomethingCommand.php in app/commands
  2. Open up that file and change $name to either ‘dosomething’ or ‘blah:dosomething’ - if you prefix all your commands with something and then a colon, they will be all listed together under that heading when you type artisan list
  3. Once you’ve done that, and before you do anything else, locate the artisan.php file in the app/start folder and add: Artisan::add( new adduserCommand );
  4. You should then be able to run your (currently empty) command. You will probably get an error about having not provided the “example” argument, but that’s OK. At this point you’re on the home stretch - all you need to do is manually move across the code from your old Task to the new Command and you’re done.
  5. If you don’t need options you can simply remove the getOptions() method, or, if you want to keep it there just in case, make sure the method returns an empty array(). One mistake I made was returning null during one of my earlier attempts, which did not end well for artisan. :-)
  6. Oh, and you access arguments using $this->argument(‘argname’) rather than by accessing the $arguments[] array. Same with options.

Three important points I’d like to make about Commands are:

  1. Do NOT forget to register your command in app/start/artisan.php. I would almost go so far as to say that this should be mentioned at the start of the docs for Commands, because the inclination as you are writing the code is to run it to see what it does, but nothing ain’t gonna run until you’ve registered the command!
  2. Although I was tempted to give my classes names such as dosomething instead of dosomethingCommand, in the end I decided it was better to go with the slightly more verbose approach because (A) you’ll only have to type it once anyway and (B) there is less likelihood of name clashes in the future if you follow that convention.
  3. One thing that is missing in the new approach is the ability to have a default method that runs if you type artisan dosomething and another method within the same class that runs if you type artisan dosomething:tricker. My workaround was to simply add an argument and then implement a switch based on that argument, which yields a very similar result.

How Long Did It Take?

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:

FolderLOC
controllers540
helpers183
models927
tasks384
views476
Total2,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.

Yet Another Programming Blog

Where James Gordon rambles about PHP, Laravel and web development in general.

Find me on Google Plus Find me on Twitter Find me on Stack Exchange Find me on Github Subscribe