If there is one single core feature that demonstrates Laravel’s superiority, it would be the ability to quickly and easily create a RESTful API. With the arrival of Laravel 5, several new features have been added; however, the ability to create application models and controllers via the Artisan command-line tool remains the most useful feature.
This feature is what initially encouraged me and so many others to abandon frameworks such as CodeIgniter, which at the time that Laravel 4 was in beta, did not natively have the same integrated functionality. Laravel provides the basic CRUD methods: create, read, update, delete, and also lists all.
Requests that arrive via HTTP to a Laravel URL are managed through their verbs and subsequently, the
routes.php
file, which is located at app/Http/routes.php
. There are two ways in which the requests are handled. One way is that the request is handled directly via a closure, and the code is entirely inside the routes
file. Another way is that it routes the request to a controller, where a method will be executed.
Also, the basic paradigm used is convention-over-configuration, where the method names are ready to handle the various requests, without too much extra effort.
RESTful APIs in Laravel
The list of RESTful API requests handled by the RESTful API are as follows:
HTTP VERB | Function | UR | |
---|---|---|---|
1 | GET | This lists all accommodations | /accommodations |
2 | GET | This shows (reads) a single accommodation | /accommodations/{id} |
3 | POST | This creates a new accommodation | /accommodations |
4 | PUT | This entirely modifies (updates) an accommodation | /accommodations/{id} |
5 | PATCH | This partially modifies (updates) an accommodation | /accommodations/{id} |
6 | DELETE | This deletes an accommodation | /accommodations/{id} |
Most RESTful API best practices suggest using the plural form of the model name. Laravel’s documentation uses the singular format. Most practices agree that consistent plural naming, that is,
/accommodations/{id}
refers to a single accommodation and /accommodations
refers to more than one accommodation, both using the plural form are preferred over a mixed, but grammatically correct/accommodation/{id}
(singular form) and /accommodations
(plural form).Essential CRUD
For simplicity, I have numbered each of the rows. The first and second items represent the read part of CRUD.
The first item, which is a
GET
call to the plural form of the model name, is rather simple; it displays all of the items. Sometimes, this is called a list to differentiate it from the read of a single record. Adding alist would thus expand the acronym to CRUDL. They could be paginated or require authorization.
The second item, also a
GET
call, adds the ID of the model to the end of the URL, displaying a single model with that corresponding ID. This could also require authentication but not paging.
The third item represents the create part of CRUD. It uses the
POST
verb to create a new model. Note that the URL format is the same as the first item; this demonstrates the importance of the verb to distinguish between the actions.
The fourth, fifth, and sixth items use the new
HTTP
verbs that were not supported by all browsers. Whether or not the verbs are supported, JavaScript libraries and frameworks, such as jQuery, will send the verb in a way that Laravel can properly handle.
The fourth item is the
update
part of CRUD and updates the model using the PUT
verb. Note that it has the same URL format as the second, as it needs to know which model to update. It is also idempotent, which means that the entire model must be updated.
The fifth item is similar to the fourth item; it updates the model, but uses the
PATCH
verb. This is used to indicate that the model will be partially modified, which means that one or more of the model’s attributes have to be changed.
The sixth item deletes a single model and thus requires the model’s ID, using the self-explanatory
DELETE
verb.Bonus features
Laravel adds two additional methods that are not usually part of a standard RESTful API. A
GET
method on the model URL, adding create
is used to display a form to create the model. A GET
method on the model URL with its ID, addingedit
is used to display a form to create the model. These two functions are useful for providing a URL that will load a form, even though this type of usage is not a standard RESTful:HTTP VERB | Function | URL | |
---|---|---|---|
GET | This displays an accommodation creation form | /accommodations/create | |
GET | This displays an accommodation modification/update form | /accommodations/{id}/edit |
Controller creation
To create a controller for the accommodations, the following Artisan command is used:
$ php artisan make:controller AccommodationsController php namespace MyCompany\Http\Controllers; use MyCompany\Http\Requests; use MyCompany\Http\Controllers\Controller; use Illuminate\Http\Request; class AccommodationController extends Controller { /** * Display a listing of the resource. * @return Response */ public function index() { } /** * Show the form for creating a new resource. * @return Response */ public function create() { } /** * Store a newly created resource in storage. * @return Response */ public function store() { } /** * Display the specified resource. * @param int $id * @return Response */ public function show($id) { } /** * Show the form for editing the specified resource. * @param int $id * @return Response */ public function edit($id) { } /** * Update the specified resource in storage. * * @param int $id * @return Response */ public function update($id) { } /** * Remove the specified resource from storage. * @param int $id * @return Response */ public function destroy($id) { } }
CRUD(L) by example
We have seen this controller before, but here are a few examples. The single most simple example of a RESTful call would be as shown in the following sections.
cRudl – read
Create a
GET
call to http://www.dunebookdemo.com/accommmodations/1
, where 1
would be the ID of the room:/** * Display the specified resource. * * @param int $id * @return Response */ public function show($id) { return \MyCompany\Accommodation::findOrFail($id); }
This would return a single model as a JSON-encoded object:
{ "id": 1, "name": "Hotel On The Hill","description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "location_id": 1, "created_at": "2015-02-08 20:13:10", "updated_at": "2015-02-08 20:13:10", "deleted_at": null }
crudL – list
Create a GET call to
http://www.dunebookdemo.com/accommmodations
.
This is similar to the preceding code, yet slightly different:
/** Display a listing of the resource. * @return Response */ public function index() { return Accommodation::all(); }
This would return all of the models, automatically encoded as JSON objects; there is nothing else that is required. Formatting has been added so that the JSON results are more easily readable, but basically, the entire model is returned:
[{ "id": 1, "name": "Hotel On The Hill","description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "location_id": 1, "created_at": "2015-02-08 20:13:10", "updated_at": "2015-02-08 20:13:10", "deleted_at": null } { "id": 2, "name": "Patterson Place", "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "location_id": 2, "created_at": "2015-02-08 20:15:02", "updated_at": "2015-02-08 20:15:02", "deleted_at": null }, { "id": 3, "name": "Neat and Tidy Hotel", "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "location_id": 3, "created_at": "2015-02-08 20:17:34", "updated_at": "2015-02-08 20:17:34", "deleted_at": null } ]
Tip
The
deleted_at
field is a soft delete or the recycle bin mechanism. It is eithernull
for not deleted or a date
/time
stamp for deleted.Pagination
To add pagination, simply substitute
all()
with paginate()
:public function index() { return Accommodation::paginate(); }
The results will now look like this. The eloquent collection array is now moved inside a
date
attribute:{"total":15, "per_page":15, "current_page":1, "last_page":1, "next_page_url":null, "prev_page_url":null, "from":1, "to":15, "data":[{"id":9, "name":"Lovely Hotel", "description":"Lovely Hotel Greater Pittsburgh", ….
Crudl – create
Create a
POST
call to
.
To create a new model, a
POST
call will be sent to /accommodations
. A JSON would be sent from the frontend as follows:{ "name": "Lovely Hotel", "description": "Lovely Hotel Greater Pittsburgh", "location_id":1 }
The
store
function might look something like this:public function store() { $input = \Input::json(); $accommodation = new Accommodation; $accommodation->name = $input->get('name'); $accommodation->description = $input->get('description'); $accommodation->location_id = $input->get('location_id'); $accommodation->save(); return response($accommodation, 201) ; }
Tip
201
is the HTTP status code (HTTP/1.1 201 created
) for created
.
In this example, we returned the model as a JSON-encoded object. The object will include the ID that was inserted:
{ "name":"Lovely Hotel", "description":"Lovely Hotel Greater Pittsburgh", "location_id":1, "updated_at":"2015-03-13 20:48:19", "created_at":"2015-03-13 20:48:19", "id":26 }
crUdl – update
Create a
PUT
call to
, where 1
is the ID to be updated:/** * Update the specified resource in storage. * * @param int $id * @return Response */ public function update($id) { $input = \Input::json(); $accommodation = \MyCompany\Accommodation::findOrFail($id); $accommodation->name = $input->get('name'); $accommodation->description = $input->get('description'); $accommodation->location_id = $input->get('location_id'); $accommodation->save(); return response($accommodation, 200) ->header('Content-Type', 'application/json'); }
To update an existing model, the code is exactly the same as we used earlier, except that the following line is used to find the existing model:
$accommodation = Accommodation::find($id);
The
PUT
verb would be sent to /accommodations/{id}
, where id
would be the numeric ID of the accommodations table.cruDl – delete
To delete a model, create a
DELETE
call tohttp://www.hotelwebsite.com/accommmodation/1
, where 1
is the ID to be deleted:/** * Remove the specified resource from storage. * * @param int $id * @return Response */ public function destroy($id) { $accommodation = Accommodation::find($id); $accommodation->delete(); return response('Deleted.', 200) ; }
Tip
There seems to be some disagreement about what the proper status code should be for a deleted model.
Moving beyond CRUD
If one of the requirements of the software application to be built is able to search for an accommodation, then we can easily add a search function. The search function will find accommodations by using a
name
string. One way to do this is to add a route to the routes.php
file. This will map a GET
call to search for a newsearch()
function contained withinAccommodationsController
:Route::get('search', 'AccommodationsController@search'); Route::resource('accommodations', 'AccommodationsController');
Tip
In this case, the
GET
method would be preferred instead of the POST
method, as it can be bookmarked and recalled later.
Now, we will write our search function:
public function search(Request $request, Accommodation $accommodation) { return $accommodation ->where('name', 'like', '%'.$request->get('name').'%') ->get(); }
There are several mechanisms here:
- The
Request
object that contains the variables from theGET
request is type-hinted and then injected into the search function - The
Accommodation
model is type-hinted and then injected into thesearch
function - The
where()
method from the fluent query builder is called on the eloquent model$accommodation
- The
name
parameter is used from therequest
object - The
get()
method is used to actually perform the SQL queryTip
Note that some of the query builder and eloquent methods return an instance of the query builder, while the others execute the query and return the result. Thewhere()
method returns an instance of the query builder, while theget()
method executes the query. - The resulting eloquent collection is returned and automatically encoded into JSON
The
GET
request, therefore, is as follows:http://www.hotelwebsite.com/search-accommodation?name=Lovely
The resultant JSON would look something like this:
[{"id":3, "name":"Lovely Hotel", "description":"Lovely Hotel Greater Pittsburgh", "location_id":1, "created_at":"2015-03-13 22:00:23", "updated_at":"2015-03-13 22:00:23", "deleted_at":null}, {"id":4, "name":"Lovely Hotel", "description":"Lovely Hotel Greater Philadelphia", "location_id":2, "created_at":"2015-03-11 21:43:31", "updated_at":"2015-03-11 21:43:31", "deleted_at":null}]
Nested controllers
Nested controllers is a new feature in Laravel 5 and is used to handle all of the RESTful actions that deal with relationships. For example, we can take advantage of this feature for the relationship between accommodations and rooms.
The relationship between accommodation and room is as follows:
- An accommodation may have one or more rooms (one-to-many)
- A room belongs to one and only one accommodation (one-to-one)
In our models, we will now write the code to enable the one-to-one and one-to-many relationships to be skillfully handled by Laravel.
Accommodation hasMany rooms
First, we will add the code that is needed by the
Accomodation.php
file that represents theaccommodation
model as follows:class Accommodation extends Model { public function rooms(){ return $this->hasMany('\MyCompany\Accommodation\Room'); } }
The
rooms()
method creates an easy way to access the relationship from inside the accommodation model. The relation states that “the accommodationhasMany rooms”. The hasMany
function, when residing inside the Accommodation
class, without any additional parameters, expects a column namedaccommodation_id
to exist in the Room
model’s table, which in this case is rooms
.Room belongsTo accommodation
Now, we will add the code that is needed by the
Room.php
file that represents theRoom
model:class Room extends Model { public function accommodation(){ return $this->belongsTo('\MyCompany\Accommodation'); } }
This code states that “a room belongsTo an accommodation”. The belongsTomethod inside the
Room
class, without any additional parameters, expects a field in the room
model’s table; in this case,rooms
, named accommodation_id
.Tip
If the tables in the application database have followed the active record conventions, then most of the eloquent relation functionalities will automatically function. All of the parameters can be easily configured.
The command to create a nested controller is as follows:
$php artisan make:controller AccommodationsRoomsController
Then, the following line would be added to the
app/Http/routes.php
file:Route::resource('accommodations.rooms', 'AccommodationsRoomsController');
To display the routes created, the following command should be executed:
$php artisan route:list
The following table lists the HTTP verbs and their functions:
HTTP verb | Function | URL | |
---|---|---|---|
1 | GET | This shows the accommodation and room relations | /accommodations/{accommodations}/rooms |
2 | GET | This shows an accommodation and room relation | /accommodations/{accommodations}/rooms/{rooms} |
3 | POST | This creates a new accommodation and room relation | /accommodations/{accommodations}/rooms |
4 | PUT | This entirely modifies (updates) an accommodation and room relation | /accommodations/{accommodations}/rooms/{rooms} |
5 | PATCH | This partially modifies (updates) an accommodation and room relation | /accommodations/{accommodations}/rooms/{rooms} |
6 | DELETE | This deletes an accommodation and room relation | /accommodations/{accommodations}/rooms/{rooms} |
Eloquent relations
A nice mechanism used to illustrate an Eloquent relation directly inside the controller is performed through the use of a nested relation, where two models are connected firstly through the route and secondly through their controller method’s parameters via model dependency injection.
Nested update
Let’s investigate the
update
/modify PUT
nested controller command. The URL looks like this:http://www.hotelwebsite.com/accommodations/21/rooms/13
.
Here,
21
would be the ID of the accommodation and 13
would be ID of the room. The parameters are the type-hinted models. This allows us to easily update the relationship as follows:public function update(Accommodation $accommodation, Room $room) { $room->accommodation()->associate($accommodation); $room->save(); }
Nested create
Similarly, it is easy to perform the nested
create
operation with a POST
body tohttp://www.hotelwebsite.com/accommodations/21/rooms
. The POST
body is a JSON formatted object:{"roomNumber":"123"}
Note that there is no ID needed for the room since we are creating it:
public function store(Accommodation $accommodation) { $input = \Input::json(); $room = new Room(); $room->room_number = $input->get('roomNumber'); $room->save(); $accommodation->rooms()->save($room); }
Route caching
Laravel 5 has a new mechanism for caching the routes as the
routes.php
file can easily grow very large and will quickly slow down the request process. To enable the caching mechanism, type the following artisan
command:$ php artisan route:cache
This creates another
routes.php
file in /storage/framework/routes.php
. If this file exists, then it is used instead of the routes.php
file, which is located inapp/Http/routes.php
. The structure of the file is as follows:php
/*
|--------------------------------------------------------------------------
| Load The Cached Routes
|
…
*/
app('router')->setRoutes(
unserialize(base64_decode('TzozNDoiSWxsdW1pbmF0ZVxSb3V0aW5nXFJvdXRlQ29sbGVjdGlvbiI6NDp7czo5OiIAKgByb3V0ZXMiO2E6Njp7czozOiJHRVQiO2E6M
…
... VyQGluZGV4IjtzOjk6Im5hbWVzcGFjZSI7czoyNjoiTXlDb21wYWbXBhbnlcSHR0cFxDb250cm9sbGVyc1xIb3RlbENvbnRyb2xsZXJAZGVzdHJveSI7cjo4Mzg7fX0='))
);
Notice that an interesting technique is used here. The routes are serialized, then base64 is encoded. Obviously, to read the routes, the reverse is used,
base64_decode()
, and then unserialize()
.
If the
routes.php
cached file exists, then every time a change is made to theroutes.php
file, the route cache artisan
command must be executed. This will clear the file and then recreate it. If you later decide to no longer use this mechanism, then the following artisan
command can be used to eliminate the file:$ php artisan route:clear
Laravel is useful for building several distinctly different types of applications. When building traditional web applications, there is often a tight integration between the controllers and the views. It is also useful when building an app that can be used on a smartphone. In this case, the frontend will be created for the smartphone’s operating system using another programming language and/or framework. In this case, only the controllers and model will most likely be used. In either case, however, having a well-documented RESTful API is an essential part of a well-designed modern software.
Nested controllers helps developers right away to read the code—it is an easy way to understand that the particular controller deals with the “nesting” or the concept that one class is related another.
Type-hinting the models and objects into the controller also improves the readability and, at the same time, reduces the amount of code necessary to perform the basic operations on the objects.
Also, eloquent model casting creates an easy way to transform the attributes of a model, without having to rely on external packages or tedious accessor functions, as was the case in Laravel 4.
Now it is rather clear to us why Laravel is becoming the choice of many developers. Learning and repeating some of the steps illustrated in this Turorial will allow a RESTful API to get created in under an hour for a small-to-medium size program.
Wrapping up
A RESTful API provides an easy way to expand the program in the future and also integrates with third-party programs and software that exist within a company that might need to communicate with the application. The RESTful API is the front-most shell of the inner part of the program and provides the bridge between the outside world and the application itself. The inner part of the program will be where all of the business logic and database connections will reside, so fundamentally, the controllers simply have the job of connecting the routes to the application.
Laravel follows the RESTful best practices, thus documenting the API should be easy enough for other developers and third-party integrators to understand. Laravel 5 has brought a few features in to the framework to enable the code to be more readable.
In future articles, middleware will be discussed. Middleware adds various “middle” layers between the route and the controller. Middleware can provide features such as authentication. Middleware will enrich, protect, and help organize the routes into logical and functional groups.
We will also discuss DocBlock annotations. Annotations, while not natively supported in PHP, can be enabled via a Laravel community package. Then, inside the DocBlock of the controller and controller functions, the routing for each controller is automatically created, without having to actually modify the
app/Http/routes.php
file. This is another great community concept that Laravel easily adapts to, in the same manner as phpspec and Behat.from : https://www.dunebook.com/complete-guide-for-building-restful-apis-in-laravel-5-2/
沒有留言:
張貼留言