In this unit we are going to focus on the Slim Framework to develop REST applications. Last year, in DFTI, you were introduced to Slim but it might be new to you if you come from another university.
Representational State Transfer, REST, is not a technology, but an architecture for web services. The idea centres around using clear, highly-descriptive URLs to represent each real-world entity that our web application needs to deal with (e.g. a song, a list of all songs by a given artist, a flight, a biography of an actor, etc). For example we could have these URLs:
http://www.hittastic.com/artist/Oasis http://www.hittastic.com/biography/Madonna http://www.solentairways.com/flight/SA101 http://www.solentairways.com/flights/June/1/Southampton/New_York
In REST, these URLs are called resources. REST has the following key principles:
A key principle of REST, illustrated by the examples above, is that of clean, unchanging URLs. Why is this useful? URLs which show the real location on the server, or the server-side technology used (e.g. the fact that it's a PHP script) are prone to continuous change, for example, if the script is moved to a different directory or we switch server-side scripting technology. This causes problems in bookmarking and linking to such pages, and also, if the URLs represent web services, means that developers of client applications have to update their client code to point to the new URL.
With REST, we hide the implementation details with a simple, clean and easily-remembered URL, and define how this URL is mapped to the real, underlying location of the script on the server. For example, rather than
http://www.hittastic.com/track.php?title=Wonderwall&artist=Oasiswe could use:
http://www.hittastic.com/Oasis/WonderwallThe web server software would convert this REST-style URL to the real, underlying script.
If we changed the underlying URL, i.e. the location of the actual server side script on the server, all we'd need to do is change the mapping of our clean, easily remembered, publicly-visible, "REST-style" URL to the real underlying URL, and clients of the web service could continue to use our web service unchanged with the same publicly-visible URL as before; they wouldn't have to alter their code to reflect the new underlying URL. We could even change the server-side implementation technology (e.g. PHP to ASP) without having to change the publicly-visible URL: once again we would only have to change the mapping from the publicly-visible URL to the underlying URL.
Furthermore, this allows us to easily swap between dynamically and statically generated data. Imagine the URL below points at a static (i.e. not dynamically generated) JSON file on the server representing all Oasis hits.
http://www.hittastic.com/artist/OasisBy changing the server configuration, we could easily change this URL to point to a server side script which dynamically generates the data from a database. So in summary, REST style URLs provide a clean and unchanging interface to data supplied by our server and there is no need to change the URL depending on how the data associated with that URL is generated.
The practical details of how to actually set up REST-style URLs to point to given scripts will be discussed towards the end of this week's notes.
With REST, we send different types of messages to the same URL to make it do different things, e.g. retrieve data or change the state of the item represented by the URL. For example if we had the URL:
http://www.solentairways.com/flight/SA101we could send one type of message (let's call it a "get" message) to to the URL to retrieve the details about flight SA101, and another type of message (let's call it a "put" message) to update the details (e.g. departure time) of flight SA101, and a third type (let's call it a "delete" message) to delete flight SA101.
But what form do these messages take? We could use query string parameters to inform the script of the message type. However, like custom error codes, this could be seen as reinventing the wheel. Last week we looked at HTTP methods and encountered the recommendation to send GET requests when retrieving data or POST requests when adding or modifying data. REST combines this concept in conjunction with the "one URL per real world entity" concept already introduced. So you could send a GET request to the SA101 URL above to retrieve data about flight SA101, or a POST request to modify data about SA101.
However, in addition to GET and POST, REST uses a couple of additional and less-commonly-encountered HTTP methods: PUT and DELETE. In REST, PUT differs from POST in that PUT is used to modify data while POST is used to add new data. DELETE requests, unsurprisingly, are used to delete data.
REST takes the view that HTTP methods and status codes are under-used and can be exploited in web services, as described below.
As mentioned above, the idea is that one single web resource (URL) can be used for retrieving, adding, and deleting data associated with a particular item, e.g. a particular song in the HitTastic! database. What we can do is to do different things with the song depending on the type of HTTP method we use to communicate with the URL. In general we:
As well as using HTTP methods, REST applications use HTTP status codes to indicate errors to clients. Recall that the first line of any HTTP response is a status code which indicates whether the request was successful or not. There are a large number of HTTP status codes including:
Imagine we had a REST web service to look up a given song:
http://hittastic.com/song/id/1009This script could return "404 Not Found" to the client if the song with that ID was not on the HitTastic! database, or, if an invalid ID (0 or less) was supplied, the script could return "400 Bad Request", another standard HTTP error code. Note that this use of error codes differs from the normal usage:
We looked at the Slim framework last year briefly in DFTI, but here we will look at it in the context of REST web services. However as not everyone was here last year, I will introduce Slim from scratch. Slim is a relatively easy approach to create REST web services in PHP.
http://www.hittastic.com/allSongs http://www.hittastic.com/allArtiststhe corresponding URL patterns in a Slim application, running on hittastic.com, would be:
/allSongs /allArtistsand the routes for these URL patterns would be a combination of the pattern and the code to handle the pattern - you will see this below.
hittastic.com/song/123to the "real" underlying URL:
hittastic.com/song.php?id=123
Here is an example of a .htaccess file to be used with Slim:
RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ index.php [QSA,L]What does this mean?
Slim is a third-party library; it is not part of the core PHP distribution. However it is quite easy to install thanks to Composer. Composer (see here) is a dependency management system for PHP projects, allowing you to import specified third-party libraries (dependencies - so called because your project depends on them). You create a composer.json file specifying which libraries are needed, and then run an install command, and all necessary dependencies will be installed. Composer itself can be downloaded as an application from the Composer site: the Composer application is a file named composer.phar which is typically saved to your home directory (~ on Linux systems) and run with PHP, e.g:
php ~/composer.phar (Composer command)
However on Neptune, Composer is installed globally so you can just use composer to run it.
Here is an example of a composer.json file for Slim:
{ "require": { "slim/slim" : "^3.0" } }This means, as you might expect, the Slim package is a dependency of your project, and it needs to be at least version 3.0. (Version 4 was released over the summer, but is more complex for beginners to use, so we will stick with version 3. However, feel free to research and implement v.4 for your assignment!) You can add multiple libraries to the "require" block. Once you have setup a composer.json, you can then install the dependencies with:
composer installSee here for full documentation on Composer.
However you do not have to write the composer.json file yourself. You can require a package by running Composer's require command, e.g. by running this in your project directory:
composer require slim/slimThis will generate a composer.json automatically, and will also load all the dependencies.
The dependencies will be downloaded to a vendor directory within your project directory. Also, an autoload.php will be generated in this project directory, allowing you to automatically include all the necessary PHP files for all dependencies. So in your PHP code, you can simply add:
<?php require("vendor/autoload.php"); ...
The example below is the Hello World of Slim. Note that it's annotated with comments.
// Include all the Slim dependencies. Composer creates an 'autoload.php' inside // the 'vendor' directory which will, in turn, include all required dependencies. require 'vendor/autoload.php'; // Create a new Slim App object. (v3 method) $app = new \Slim\App; // Setup a route (see below) $app->get('/hello', function ($req, $res, array $args) { $res->getBody()->write('Hello World from Slim!'); return $res; }); // Run the application $app->run();
Note how we we handle a routes. A route is, as we saw above, a combination of a URL pattern and a function to handle requests matching that URL pattern. Slim allows us to set up routes very easily, for both 'GET' and 'POST' methods. Here we are creating a route called /hello. For example for HitTastic!, it might be:
http://hittastic.com/hello
The handler for the route is a function which takes three parameters, $req representing the HTTP request, $res representing the HTTP response, and $args for any arguments sent to the application (see below). In this example we call the send() method of the response object to send back Hello World from Slim! to the client.
// Include all the Slim dependencies. Composer creates an 'autoload.php' inside // the 'vendor' directory which will, in turn, include all required dependencies. require 'vendor/autoload.php'; // Create a new Slim App object. (v3 method) $app = new \Slim\App; $app->get('/hello', function($req, $res, array $args) { $res->getBody()->write('Hello World!'); return $res; }); $app->get('/time', function($req, $res, array $args) { $res->getBody()->write("There have been ". time() ." milliseconds since 1/1/70."); return $res; }); $app->run();
This example sets up two routes matching URL patterns '/hello' and '/time'. The first will send back 'Hello World!' and the second will send back the number of milliseconds since January 1st 1970. You could set up POST routes with $app->post(); we will see an example of this later.
Slim allows us to easily specify placeholders for variable URLs, e.g we could define a route such as:
/artist/{artistname}Here, artistname is a placeholder which can be replaced by any artist, so that the following would match on the hittastic.com server:
http://hittastic.com/artist/Oasis http://hittastic.com/artist/MadonnaBoth of these would be handled by the artist route, with the {artistname} placeholder representing the specified artist. We can then use the $args array passed into the handler to retrieve the actual value of the placeholder.
Here is a full example which displays a student ID passed into the URL:
; // Include all the Slim dependencies. Composer creates an 'autoload.php' inside // the 'vendor' directory which will, in turn, include all required dependencies. require 'vendor/autoload.php'; // Create a new Slim App object. (v3 method) $app = new \Slim\App; $app->get('/student/{id}', function($req, $res, array $args) { $res->getBody()->write("You selected the student with ID ". $args["id"]. "<br />"); return $res; }); $app->run();Note how this will setup the route matching the URL /student followed by a placeholder {id} representing the student ID. So URLs such as
http://server/student/1144332 http://server/student/1239723 http://server/student/goatwill be matched. (There is no check that ntimes is numeric, so the final URL will be matched. However you can use regular expressions in Slim routes to ensure that only numbers are passed in the placeholder; see here).
To access the named placeholder from the PHP code, we use the $args parameter to our handler function, so, in this example, we would use $args['id'] to access the specified ID.
Note we can specify more than one parameter with multiple placeholders in the URL.
The examples so far have simply displayed data, to illustrate route handling. However a real app would obviously need to actually perform database queries. How do we do this? For maximum flexibility, Slim does not have its own database connection functionality - instead, you as the developer can use whichever database connection library (such as PDO) you like.
Slim introduces the concept of a dependency injection container. Dependency injection is a software development pattern which is designed to avoid dependencies of one library on another by placing ("injecting") these dependencies in a container object which is independent of the objects making up our chosen framework (such as Slim).
In this way, Slim is not dependent on a particular database library, for example, but can work with whichever database library has been placed in this container object. So, if we are using PDO, we can place our PDO connection inside the container and refer to it in our route handlers. The example below shows this:
// Include all the Slim dependencies. Composer creates an 'autoload.php' inside // the 'vendor' directory which will, in turn, include all required dependencies. require 'vendor/autoload.php'; // Create a new Slim App object. (v3 method) $app = new \Slim\App; $container = $app->getContainer(); $container['db'] = function() { $conn = new PDO("mysql:host=localhost;dbname=dftitutorials", "dftitutorials", "dftitutorials"); return $conn; }; $app->get('/student/{id}', function ($req, $res, array $args) { $stmt = $this->db->prepare("SELECT * FROM students WHERE studentid=?"); $stmt->bindParam (1, $args['id']); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); $res->getBody()->write("Student name ". $row['name']. " Course: " . $row['course']); return $res; }); $app->run();Note in the example how we obtain the Slim app's container with:
$container = $app->getContainer();and then add a db field to the container, which is set equal to a function which returns the PDO database connection.
Note also how we refer to the database connection in the /student/{id} route handler.We're using $this->db to refer to the PDO database connection; note how this corresponds to the db field of the container object.
The examples so far have covered very basic use of Slim to generate HTML. However, we are interested in web service development, so we are more interested in outputting JSON from our Slim app. The good news is that it's very easy to generate JSON from a Slim web service once you know the basics of Slim above: you simply call the withJson() method on your Response object and return its result from your route handler. This will generate the response as JSON. For example:
// Include all the Slim dependencies. Composer creates an 'autoload.php' inside // the 'vendor' directory which will, in turn, include all required dependencies. require 'vendor/autoload.php'; // Create a new Slim App object. (v3 method) $app = new \Slim\App; // Setup the dependency injection container - as in the last example $container = $app->getContainer(); $container['db'] = function() { $conn = new PDO("mysql:host=localhost;dbname=dftitutorials", "dftitutorials", "dftitutorials") return $conn; }; $app->get('/primeMinisters/{country}', function($req, $res, array $args) { $stmt = $this->db->prepare("SELECT * FROM primeMinisters WHERE country=?") $stmt->bindParam (1, $args["country"]); $stmt->execute(); $results = $stmt->fetchAll(PDO::FETCH_ASSOC); // or fetch() to fetch one row return $res->withJson($results); });
As we saw above, in the REST discussion, HTTP status codes are used to communicate errors to clients when writing web services. How can we generate HTTP error codes? Again this is easy, the withStatus() method is used. For example, this would generate a 400 Bad Request:
return $res->withStatus(400);
The examples above were deliberately simplified to make things easier for beginners. Here is a version of the "Hello World" example done the "correct" way. It introduces the PHP features of namespaces and type declarations. Note the comments which explain each feature.
// Import classes from the Psr library (standardised HTTP requests and responses) // This illustrates the PHP feature of NAMESPACES. When developing PHP code there is the possibility that // two libraries contain classes of the same name, e.g. two Request classes. To prevent clashes, we // declare a namespace for each library and put the class inside the namespace. To use the class from // outside the library, we need to import it with the "use" statement. // Here, we are importing the classes "ServerRequestInterface" and "ResponseInterface" from // the namespace Psr\Http\Message, and then aliasing them as Request and // Response respectively. use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; // Include all the Slim dependencies. Composer creates an 'autoload.php' inside // the 'vendor' directory which will, in turn, include all required dependencies. require 'vendor/autoload.php'; // Create a new Slim App object. (v3 method) $app = new \Slim\App; // Setup a route. // Note the type declarations here. The parameters $req and $res are // declared as data type Request and Response respectively, // which will enforce that these parameters are of these types, similar to // other more strongly-typed languages such as Java. $app->get('/hello', function (Request $req, Response $res, array $args) { $res->getBody()->write('Hello World from Slim!'); return $res; }); // Run the application $app->run();
You are going to develop a REST web service using Slim called HT-Tracks, which will query the HitTastic! database and return JSON of songs to clients.
cd public_html mkdir wad cd wadUse Composer to install Slim and its dependencies:
php ~vmadmin/composer.phar require slim/slim:^3.0The ^3.0 ensures that Slim v3 is installed.
database wad username wad password wad
$rows = $stmt->fetchAll(); if(count($rows)==0) { ... }
require '/var/www/html/share/vendor/autoload.php';
If you followed my instructions, you have hopefully done this already.
However, if not... do the following. Create a one-page "fan" website for your favourite artist. The site must have some CSS (enough to make it distinctive; it must, as a minimum, the CSS must set the background and text colour and font) and should include a short biography of the artist (get this from Wikipedia if necessary; if short of time, just state their date and town of birth). Next session, you will connect this site to your web service, retrieving all songs by that artist from the web service and displaying them on the site.
You need to make sure you use an artist in the database. See here for a list of all the songs and artists present.