Laravel is one of, if not the most popular framework around for PHP today. Using GitHub as my source of truth, Laravel has 24,543 stars, 690 of which it has received this month alone. This is admittedly a simplistic, dare I say even rudimentary, measure of success or quality. But it does show both inertia and uptake.
Given this inertia and given that Laravel is a framework for the web’s most-used software development language, it’s important to consider how significant it may become over time, with an increasing number of applications based upon it or affected by it in some way. For that reason, and given that I’m so passionate about continuous deployment and integration, today I’m launching a series of articles demonstrating how to integrate Laravel with Codeship.
My intent isn’t to further popularize Laravel. Whether it’s Laracon US, Laracon EU, Laracasts, or one of the many dedicated podcasts or blogs, the community’s doing a fantastic job of promoting it without needing my help. My intent is to show you how to use Codeship as part of your CI workflow so that you can deploy your applications with a minimum of fuss and effort.
The Laravel Series Overview
If we lay a solid foundation for future success, we'll be able to gradually build in complexity without getting overwhelmed in the process. Let's start at the beginning, with a simple, uncomplicated application.
First, we'll install the application from a project repository, which you can find on GitHub. Then we’re going to link the GitHub project to our Codeship account and create a project to manage it. We’ll then work through the required pipeline configuration and related settings so that our application can be tested and deployed.
The application we’re going to build is a basic URL shortener. By the time we're done, the application will support the following functionality:
List all routes
Add new shortened routes
Update existing routes
Delete existing routes
As you can see, it’s essentially a simple CRUD application. I say simple because the core of the application, the shortening code, won’t be written by me. For that, I’ll be using an external library. Also, we'll just be managing records in a database, and not a very complicated one at that. However, this will give us enough to work with to emulate a normal CI workflow, which will include:
Making a code change
Pushing changes to version control system of choice
Triggering a build
Deploying if build succeeds
We’re also going to need to take environment variables into consideration, as we would in a normal deployment workflow. One aspect, which is very close to my heart, is that the application is going to be test-driven.
Given these elements, I’m confident that this series won’t fall into the "Hello World" category. There's more than enough meat on its bones to cover aspects that we’d encounter on a regular basis.
The Application Foundation
The application will be based on Laravel 5.2 and require only one external package, laravelcollective/html 5.2. This is so we can quickly build the forms for manipulating the route information. The rest will be stock Laravel. To keep things simple, the database will be SQLite 3.
Over the course of the series, I’ll also demonstrate how to integrate external vendors, such as ElephantSQL. This isn’t strictly necessary, but I feel it’s important to show how it's done.
And finally, to follow along with this series and to deploy the application yourself, you’re going to need to have an API key for Google’s URL Shortener API. If you don’t already have one, then take the time now to set one up.
The routes
As you can see in app/Http/routes.php
, the application has two routes:
/view-urls
. Links toUrlController@viewUrls
and renders all of the routes available in the database./manage-url/
. Links toUrlController@manageUrl
and will be responsible for adding new routes.
The controller
If we dig into app/Http/Controllers/UrlController.php
, we see that the two methods are very simplistic. viewUrls
makes a call to the Url
model’s all()
method, making a blanket request to retrieve all available routes. After retrieval, it sets them as a template variable called urls
.
manageUrl
is also a simplistic method. If the request is a POST request and data is present, it will retrieve the url provided by the user and created a shortened version of it by using the Laravel Shortener package. It then sets that information in a new Url
entity and saves the record in the database by making a call to the Url
model’s save()
method. It then redirects to /view-urls
, where the new URL and its shortened equivalent will be displayed in the URL list.
On purpose, it performs no validation or defensive coding at this stage. That may sound a bit insane, but we’re starting off light. Over the course of this series, proper validation and input data sanitation will be added.
The database configuration
Now let’s look at the database configuration. Given that we're using SQLite initially, it’s not going to be too involved. This extract, taken from config/database.php
, shows that I’ve set the default database as sqlite
and updated the configuration to point to the database file located under /database
.
'default' => env('DB_CONNECTION', 'sqlite'), 'sqlite' => [ 'driver' => 'sqlite', 'database' => env('DB_DATABASE', database_path('database.sqlite')), ],
The database migrations
Database migrations are one of the key aspects that I’ve come to like about Laravel. It bundles in migrations as part of the default install.
Now applications are diverse in scope and nature, and as a package maintainer, it’s hard to know exactly what kind of needs your end users will have. But if you’re prototyping an application, then using a database as your initial data source makes a lot of sense. Given that, the choice of bundling a database migration component as part of the core install makes a lot of sense as well. Here’s my initial migration.
Schema::create('urls', function (Blueprint $table) { $table->increments('id'); $table->string('shortened_url')->comment('Stores shortened url'); $table->string('original_url')->comment('Stores shortened url'); $table->timestamps(); });
You can see that it creates one table, called urls
. It has an auto-incrementing id
field, a field for the original URL, one for the shortened URL, and, using the timestamps method, auto-creates a field for when the record was created and updated.
The tests
Now to the tests. Here's a sample, available in tests/UrlControllerTests.php
. The first test checks the view-urls
route.
First off, we’ll load a record into the database, just one to keep things simple, then visit the page and run some checks on it. We go to the page, check the response code, the URL, and that the page contains the information that should have been rendered in the template, which was extracted from the database.
public function testViewUrlsPage() { // Load some sample data $url = factory(App\Url::class)->create([ 'shortened_url' => 'http://tSQ1r84', 'original_url' => 'http://www.google.com', ]); $this->visit('/view-urls') ->assertResponseOk() ->seePageIs('/view-urls') ->see('Manage URLs') ->see("This form let's you shorten a new url, or update an existing one.") ->see('http://tSQ1r84') ->see('http://www.google.com'); $this->visit('/view-urls') ->click('View URLs') ->seePageIs('/view-urls'); }
Following that, we perform a minor check to ensure that we can follow a link on the page. These aren’t exhaustive tests, but then, the application’s quite young! Over time, they’ll become more full-featured. If you are keen to know more about the testing functionality, definitely check out the Laravel testing documentation.
Adding The Application to Codeship
Now that we’ve looked at the application, let’s create the initial integration with Codeship. For this series, I’ll assume that you already have an account at your disposal.
After logging in, create a new project from your dashboard and choose Connect with GitHub repository. Then enter the URL of the repository, which you can find in Clone or download, in your fork of the project. After that’s done, you are ready to configure the project.
Codeship’s pretty handy in that it offers a default set of options based on some broad project types, such as PHP, Ruby on Rails, Python, Go, Dart, and Java. But it doesn’t offer a framework-specific set of configuration options. So, we’re starting off with the PHP default, which you can see below.
It’s setting the PHP binary to be version 5.6 (arguably it should be 7.0) and to install the code from the repository. If we were to try this, as I originally did, it would result in a series of failures. To set up the project properly, we’re going to need the following configuration:
phpenv local 7.0 mkdir ./bootstrap/cache composer install --prefer-source --no-interaction cp -v .env.example .env php artisan key:generate touch database/database.sqlite php artisan migrate
Let's step through that. We’re setting our environment to use PHP 7.0. We’re then creating the directory, bootstrap/cache
, which is required by Laravel’s command line tool, Artisan. We need to do this because it’s created as part of the package creation process, which won’t happen when we install from source.
After that’s done, we can then install from source, using Composer. We then need to set the application key and create the SQLite database file, called database.sqlite
and located in /database
. With these steps complete, we can now run the database migration, again using Artisan.
Configuring the Environment
There’s one task left to do, which is to configure the project environment. We need to tell Laravel what the default database connection is; otherwise, it’s going to assume that we’re using MySQL, and the build will fail.
To do that, navigate to the environment settings by clicking Environment under Project Settings. From there, we provide a KEY of DB_CONNECTION
and a value of sqlite
and save the configuration. We then add a second setting, called GOOGLE_SHORTENER_TOKEN_1
. For the value, we set the API key, which you can retrieve from your GoogleAPIs account.
Triggering a Build
With the project now configured, it’s time to put it to the test. To do that, I made a minor change to the tests and pushed the changes to GitHub. The build, I’m happy to say, was successful.
And that’s the first part of how to set up a continuous deployment workflow for a basic Laravel application. What we’ve not yet done is cover deploying the code to a test or production server, such as Heroku, Amazon, or DigitalOcean, or how to configure notifications.
That’s what we’ll be covering in part two of this series. We’ll also see how to work with an external service provider, such as ElephantSQL. Stay tuned, as this will start to flesh out the application and its accompanying complexity.
Wrapping Up Part I
As this is the first time that a deploy’s being made, I see no harm in the last three steps. However, we could have approached the deployment configuration a little differently.
We could have, and will in future, store the database file in the Git repository. We could also have created the cache directory previously and created an empty .gitkeep
file, so that the directory could be managed by Git. This way, we could remove both of these steps, simplifying the configuration. So long as the database file was there, there’d be no problem in running the migration.
However, as an initial configuration, this one works and, I hope, helps with understanding all of the working parts involved.