Aug 12, 2013
Table of contents:
Over the past couple of weeks, I’ve looked at setting up a Controller, making your Controllers flexible, and testable and we’ve looked at using Mockery in order to write tests.
The time has finally come to actually start building out the methods of a RESTful Controller.
In this post I’m going to go through building a RESTful Controller step by step.
So if you remember back to the previous tutorials, I’m using a User Repository and injecting it through the Constructor. So far the UsersController.php
file should look like this:
use Cribbb\Storage\User\UserRepository as User;
class UsersController extends BaseController
{
/**
* User Repository
*/
protected $user;
/**
* Inject the User Repository
*/
public function __construct(User $user)
{
$this->user = $user;
}
I’ve also already created the index()
method:
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
return $this->user->all();
}
Currently, this simply returns JSON. I’m not too worried about creating a view and displaying this data property for the time being, so this method is effectively finished so far.
So the first method I’m going to look at is create();
. The Create method is used for displaying a form to create a new resource. This is a GET request and so we are simply getting a View, we don’t want to actually create a new user using this method.
Creating a new user should probably be tied with registration, but we will deal with that at some point in the future. For now, just think of a User being a typical resource.
So the only thing to do in the create();
method is to specify which View should be rendered by Laravel:
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
return View::make('users.create');
}
Remember when we created a new resource in routes.php
?
Route::resource("users", "UsersController");
This means Laravel will automatically be expecting /users/create as a route so we don’t have to define it.
Now fire up the server and hit the /users/create URL in your browser:
$ php artisan serve
You should be greeted with an error, View [users.create] not found..
In order to display a View, first we have to actually create it. Create a new directory under app/views
called users
. Next create a new file called create.blade.php
and type some stuff so you will know that it is working when you see it in the browser.
When I wrote:
return View::make("users.create");
This means Laravel will be looking for a file called create
under the users
directory. You can think of users.create
as simply users/create
. Also, I have given the create
View a blade extension. This is because I’m going to be using Laravel’s templeting engine. I haven’t really covered blade yet, but I will in a future tutorial. Blade simply allows you to make better HTML templates for your views.
So now that you have created app/views/users/create.blade.php
reload the page in your browser and everything should work.
To test this, we can simply create a new test like this:
public function testCreate()
{
$this->call('GET', 'users/create');
$this->assertResponseOk();
}
Here I’m just making a request to the page and asserting that the response is 200.
The next method to look at is store();
. To create a new resource, we POST to the store method.
The store method of the Controller should only be concerned with accepting the data from the POST request. Remember, Controllers should only be concerned with the flow of traffic.
If you are using my Magniloquent package, all your validation should be abstracted away into the model. This means we can simply test to see if the data is saved and then act accordingly:
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store()
{
$s = $this->user->create(Input::all());
if ($s->isSaved())
{
return Redirect::route('users.index')
->with('flash', 'The new user has been created');
}
return Redirect::route('users.create')
->withInput()
->withErrors($s->errors());
}
In this method I’m simply attempting to store the data. If the record passes the validation rules and is saved correctly, we can redirect back to the Controller’s index method and pass a flash message as a confirmation.
if ($s->isSaved()) {
return Redirect::route("users.index")->with(
"flash",
"The new user has been created"
);
}
If the record fails to save, we can just redirect back to the create method and send the input and the errors so that the user can fix their mistake and try again.
To test this method, we only need to assert that the user is redirected to the correct place depending on success or failure. Remember, we should only be testing one thing at a time and so we need to mock the other dependancies:
public function testStoreFails()
{
$this->mock->shouldReceive('create')
->once()
->andReturn(Mockery::mock(array(
'isSaved' => false,
'errors' => array()
)));
$this->call('POST', 'users');
$this->assertRedirectedToRoute('users.create');
$this->assertSessionHasErrors();
}
In this test I’m mocking that the save failed and that the user was redirected back to the create method with errors.
public function testStoreSuccess()
{
$this->mock->shouldReceive('create')
->once()
->andReturn(Mockery::mock(array(
'isSaved' => true
)));
$this->call('POST', 'users');
$this->assertRedirectedToRoute('users.index');
$this->assertSessionHas('flash');
}
And in this method I’m asserting that the user is redirected to the index method with a flash message.
The show method simply returns a single record based on an id that is passed through the URL.
/**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show($id)
{
return $this->user->find($id);
}
And this is the test to ensure things are working correctly:
public function testShow()
{
$this->mock->shouldReceive('find')
->once()
->with(1);
$this->call('GET', 'users/1');
$this->assertResponseOk();
}
The edit method needs to return a view that contains the form to allow a user to edit the resource. Much like the create method, all we need to do is return the view.
First create a new view under app/views
called edit.blade.php
.
Next, update the edit method to return the view:
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function edit()
{
return View::make('users.edit');
}
And finally, the test to ensure the route responses correctly:
public function testEdit()
{
$this->call('GET', 'users/1/edit');
$this->assertResponseOk();
}
To update a user, all we have to do is find the user by the id and then attempt to update the model. Again much like the store method, the Controller is only really concerned with directing traffic:
/**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$s = $this->user->update($id);
if ($s->isSaved())
{
return Redirect::route('users.show', $id)
->with('flash', 'The user was updated');
}
return Redirect::route('users.edit', $id)
->withInput()
->withErrors($s->errors());
}
I added the following method to the UserRepository
:
public function update($input);
And the following method to EloquentUserRepository
:
public function update($id)
{
$user = $this->find($id);
$user->save(\Input::all());
return $user;
}
And finally, the two tests are as follows:
public function testUpdateFails()
{
$this->mock->shouldReceive('update')
->once()
->with(1)
->andReturn(Mockery::mock(array(
'isSaved' => false,
'errors' => array()
)));
$this->call('PUT', 'users/1');
$this->assertRedirectedToRoute('users.edit', 1);
$this->assertSessionHasErrors();
}
public function testUpdateSuccess()
{
$this->mock->shouldReceive('update')
->once()
->with(1)
->andReturn(Mockery::mock(array(
'isSaved' => true
)));
$this->call('PUT', 'users/1');
$this->assertRedirectedToRoute('users.show', 1);
$this->assertSessionHas('flash');
}
Finally, the delete method simply searches for a record based on the id and deletes it:
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return Response
*/
public function destroy($id)
{
return $this->user->delete($id);
}
In my EloquentUserRepository.php
file, the delete method is simply:
public function delete($id)
{
$user = $this->find($id);
return $user->delete();
}
And there you have it, your first basic RESTful controller. As you can see, by following the conventions of REST, we can very quickly build Controllers that meet all the basic requirements of a simple web application.
Whilst this is most certainly a typical example of a RESTful controller, it should provide you with a good basis for building your own RESTful controllers in your projects. You will likely come up with some strange scenarios that you need to solve, but by following the conventions of REST, you will have a solid foundation to begin with.
This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.