CRUD operations

Tidy up your Form Requests in Laravel

Introduction

Malik Sharfo
7 min readNov 29, 2021

--

This is part four. If you have not read the previous parts, we recommend that you go back and them here.

This will be a short article showing how we can reduce the number of form requests in our application by using HTTP verbs, and also creating an update route, which we are missing from our UserController.

A link to the github repository used, will be provided at the end of this article.

Understanding Laravels Route::apiResource

On default, using Laravels Route::apiResource will create route parameters for your resource routes based on the “singularized” version of the name of the resource. With Route::apiResource you get the help of setting up some default routes for you, and it even names them.

Route::apiResource creates resource URIs by using English verbs. With having a Route::apiResource('users', ‘UserController'); the following default routes gets created for you by the help of the route resource. Since this is a series on APIs we will use the ::apiResource rather than the ::resource.

Verb          Path                        Action  Route Name
--------------------------------------------------------------
GET /users index users.index
POST /users store users.store
GET /users/{user} show users.show
PUT|PATCH /users/{user} update users.update
DELETE /users/{user} destroy users.destroy

If you feel the need to modify the naming of some the these routes, then you should re-consider as Laravel creates a standard RESTful API with ::apiResource that should be familiar to your consumers.

Using HTTP verbs in the form requests

The most commonly used HTTP verbs, or methods as some might call them, are GET, POST, PATCH, PUT and DELETE. These correspond to the typical CRUD operations, to create, read, update and delete. There are numerous of other verbs too, but which are utilized less frequently than the mentioned ones, though OPTIONS and HEAD are used more often than other.

We make use of the HTTP verb in form requests by following $this->isMethod('post').

How the update functionality was implemented

In this part four, we have taken a step further and is now trying to test, whether we can update the fields for an already existing user in the database.

The steps taken towards this, have been the following, that in api.php we have refactored our single routes, to instead use Route::apiResource, as this itself, as explained above, will generate the different routes for us, which would only need us, connecting to those routes, or pointing to them.

api.php would now contain the following line, Route::apiResource('/users’, UserController::class); instead. And by using the php artisan route:list command in your terminal, you can see the different routes that have been generated, just as below.

The routes generated by Route::apiResource

This would make that we “only” need in our UserController to point to the path, the Route::apiResource have created for us. Inside UserController we create a new method with the name of update, as given by our route resource, and in the OpenAPI specs we specify that this request is of type Patch.

...class UserController extends Controller
{
...
/**
* @OA\Patch(path="/api/users/{userId}", description="Update user based on user id", operationId="",
* @OA\RequestBody(@OA\JsonContent(ref="#/components/schemas/User"), required=true,description="The updating of a user"),
* @OA\Response(response=200, description="OK",
* @OA\JsonContent(ref="#/components/schemas/User")
* ),
* @OA\Response(response=422, description="Unprocessable Entity / Validation Failed")
* )
*/
public function update(Form $request, int $id)
{
return $request->saveOrUpdate($id);
}
}

Although that PUT and PATCH might do the same thing of updating a resource at a location, they do it differently.

PUT is a method that modifies a resource where the client sends data that which updates the entire resource. PUT is used to set an entity’s information completely. This makes PUT similar to POST in that essence that it can create resources, but it does so when an URI is defined. PUT overwrites the entire entity if it already exists, and creates a new resource if it doesn’t exist.

PATCH on the other hand works slightly different, PATCH applies a partial update to the resource. What this means is that you are only required to send the data that you actually only want to update, and it will not have an effect or change anything else. Therefore if you only want to, for example, change the name on an existing database entry field, then you will only be required to send the first parameter, which is name.

In our case, we want to show that we can update a database name and/or the email field and nothing else, therefore the use of PATCH is more convenient than the use of PUT.

As it can be seen, we have given the update function one more parameter of type int and named it $id. This is due to when working with Route::apiResource the specified route to the put/patch endpoint of our userEndpoint will require an id specified parameter. And in the OpenAPI spec we are pointing to that parameter, named userId, as follows @OA\Patch(path="/api/users/{userId}".

With this explained, we move on to our UserForm class, in which we also have created a new function, or rather we have modified our previously named save function to now be able to save and update depending on the request method we are trying to perform.

...class UserForm extends FormRequest {
...
public function saveOrUpdate(?int $id = null)
{
if($this->isMethod('post'))
{
$user = new User();
$user->password = bcrypt("asd");
}
else
{
$user = User::find($id);
}
$user->name = $this->name;
$user->email = $this->email;
$user->save();
return [
'name' => $user->name,
'email' => $user->email,
];
}
}

In order to update a user field, that user needs to already be existing. We use Laravels eloquent method of interacting with the database and use User::find() and specify that the database field it needs to match up with, is the id field, and retrieve the data it matches up with. We use id since this is unique and does not have multiple entries.

We use $this->isMethod() to see what type of request we are trying to perform, as mentioned in previous articles $this is the keyword when working with FormRequest and the isMethod() checks the request type.

With the introduction of PHP v.7.1 you could now provide hints to a function to only accept the given data type. In PHP you can use type hinting for objects, arrays and callable datatypes.

Since we in our function is prepending ? to our type name int this means that we are type hinting that this can be a nullable type. Nullable type simply means that when you prepend ? to the type name while declaring types in the function parameters or for return values, it will be marked as nullable.

If our request method is of type post then that must mean we have no user and that we would like to create one. Else we assume the request method is either of type patch or put and we would like to update the user fields in the users database table.

Testing

With all set and “good-to-go”, we need to actually test whether the responses we get matches up with our OpenAPI specs, and if an update actually occurs in our users database table.

The first test we conduct is on the serverside, we need to successfully assert that an update has taken place. The test was build like the following:

...class UserTest extends TestCase
{
...
public function test_that_serverside_updates_user()
{
$user = User::find(1);
$updatedMockPayloadUser = [
'name' => 'changed_name',
'email' => 'changing@email.com',
];
$response = $this->patchJson("{$this->userEndpoint}/{$user- >id}", $updatedMockPayloadUser);

$response->assertStatus(200);
$this->assertDatabaseHas('users', [
'name' => $updatedMockPayloadUser['name'],
'email' => $updatedMockPayloadUser['email']
]);
}
}

We start by looking for if a given user already exists in the database, and we use the id for this purpose.

The user we get in return gets stored inside the $user variable, and then we create an updatedMockPayloadUser where we set the name field to be a different one from the current name, and we change the email too for the sake of changing, though either would work though for email, same goes for name.

When testing Json APIs, Laravel provides several helpers for testing Json APIs and their following responses. We make use of Laravels patchJson since we specified that our update request is of type patch.

We patchJson to our endpoint that got created for us by using Route::apiResource and append $user->id to our userEndpoint. Again this was specified as a result of Route::apiResource, so we needed to match the endpoints to be the exact same.

Then we assertStatus for HTTP status code 200 (OK), which indicates that the patch request was successful. HTTP status code 200 (OK) was specified in our OpenAPI specs for our UserController update() function. But not only that, we also assertDatabaseHas the updated name and email fields by comparing it with $updatedMockPayloadUser name and email property.

The next test conducted was to check whether or not our OpenAPI specs matches with Swagger, if our OpenAPI spec contains such operation. The test looks as following:

class UserTest extends TestCase
{
...
public function test_that_swagger_reaches_correct_endpoint_method()
{
$updatedMockPayloadUser = [
'name' => 'changed_name',
'email' => 'changing@email.com',
];
$this->address = new OperationAddress("{$this->userEndpoint}/1", 'patch');
$request = (new ServerRequest('patch', "{$this->userEndpoint}/1"))
->withHeader('Content-Type', 'application/json')
->withBody(Utils::streamFor(json_encode($updatedMockPayloadUser)));
try {
$this->validator->validate($this->address, $request);
$this->addToAssertionCount(1);
} catch (InvalidBody $e) {
$latestException = $e->getMessage();
$previousException = $e->getPrevious()->getMessage();
$exceptionLocation = implode(".", $e->getPrevious()->dataBreadCrumb()->buildChain());
$this->fail("$latestException $previousException $exceptionLocation");
}
}
}

This test is very similar to our test_that_swagger_validates_good_payload in the way that we making a server request and trying to validate if we reach the correct endpoint. The endpoint we are trying to reach is /api/users/{userId} where userId is the given users id, that we are trying to update. We are mock sending the $updatedMockPayloadUser.

Changes made to other functionalities along the way

Along the way a few refactoring, or changes, have been made, to accommodate this new update functionality.

Changes made is listed below:

  1. Inside UserForm.php the function save() have been refactored to saveOrUpdate.
  2. Inside UserController the save function now calls the UserForm saveOrUpdate function now.
  3. Inside api.php the single routes have been replaced with Route::apiResource('/users', UserController::class).
  4. Inside UserTest inside the setUp method the following line have been removed: $this->address = new OperationAddress($this->userEndpoint, $requestMethod); and placed inside wrapServerRequest which now takes an extra parameter of type string named requestMethod in which this will be the type of HTTP request we are trying to make, ie. post, patch and so on.

Github repository for this article can be found here plus the one extra commit change made, here.

--

--