33
loading...
This website collects cookies to deliver better user experience
UserController
:class UserController extends Controller
{
public function store(Request $request): RedirectResponse
{
$this->authorize('create', User::class);
$request->validate([
'name' => 'string|required|max:50',
'email' => 'email|required|unique:users',
'password' => 'string|required|confirmed',
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => $request->password,
]);
$user->generateAvatar();
$this->dispatch(RegisterUserToNewsletter::class);
return redirect(route('users.index'));
}
public function unsubscribe(User $user): RedirectResponse
{
$user->unsubscribeFromNewsletter();
return redirect(route('users.index'));
}
}
index()
, create()
, edit()
, update()
and delete()
methods in the controller. But we'll make the assumption that they are there and that we're also using the below techniques to clean up those methods too. For the majority of the article, we'll be focusing on optimizing the store()
method.store()
method.php artisan make:request StoreUserRequest
app/Http/Requests/StoreUserRequest.php
class that looks like this:class StoreUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}
authorize()
method to determine if the user should be allowed to carry out the request. The method should return true
if they can and false
if they cannot. We can also use the rules()
method to specify any validation rules that should be run on the request body. Both of these methods will run automatically before we manage to run any code inside our controller methods without us needing to manually call either of them.store()
method and into the authorize()
method. After we've done this, we can move the validation rules from the controller and into the rules()
method. We should now have a form request that looks like this:class StoreUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return Gate::allows('create', User::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'string|required|max:50',
'email' => 'email|required|unique:users',
'password' => 'string|required|confirmed',
];
}
}
class UserController extends Controller
{
public function store(StoreUserRequest $request): RedirectResponse
{
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => $request->password,
]);
$user->generateAvatar();
$this->dispatch(RegisterUserToNewsletter::class);
return redirect(route('users.index'));
}
public function unsubscribe(User $user): RedirectResponse
{
$user->unsubscribeFromNewsletter();
return redirect(route('users.index'));
}
}
store()
method from a \Illuminate\Http\Request
to our new \App\Http\Requests\StoreUserRequest
. We've also managed to reduce some of the bloat for the controller method by extracting it out into the request class.\Illuminate\Foundation\Auth\Access\AuthorizesRequests
and \Illuminate\Foundation\Validation\ValidatesRequests
traits. These come automatically included in the controller that Laravel provides you in a fresh install. So, if you're extending that controller, you're all set to go. If not, make sure to include these traits into your controller.store()
method could be to move out our "business logic" into a separate action or service class.store()
method is to create a user, generate their avatar and then dispatch a queued job that registers the user to the newsletter. In my personal opinion, an action would be more suitable for this example rather than a service. I prefer to use actions for small tasks that do only particular thing. Whereas for larger amounts of code that could potentially be hundreds of lines long and do multiple things, it would be more suited to a service.Actions
folder inside our app
folder and then creating a new class inside this folder called StoreUserAction.php
. We can then move the code into the action like this:class StoreUserAction
{
public function execute(Request $request): void
{
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => $request->password,
]);
$user->generateAvatar();
$this->dispatch(RegisterUserToNewsletter::class);
}
}
class UserController extends Controller
{
public function store(StoreUserRequest $request, StoreUserAction $storeUserAction): RedirectResponse
{
$storeUserAction->execute($request);
return redirect(route('users.index'));
}
public function unsubscribe(User $user): RedirectResponse
{
$user->unsubscribeFromNewsletter();
return redirect(route('users.index'));
}
}
UserController
that handles traditional web requests and an Api\UserController
that handles API requests. For the sake of argument, we can make the assumption that the general structure of the store()
methods for those controllers will be the same. But, what would we do if our API request we doesn't use an email
field, but instead uses an email_address
field? We wouldn't be able to pass our request object to the StoreUserAction
class because it would be expecting a request object that has an email
field.spatie/data-transfer-object
package and install it using the following Artisan command:composer require spatie/data-transfer-object
DataTransferObjects
folder inside our App
folder and create a new StoreUserDTO.php
class. We'll then need to make sure that our DTO extends Spatie\DataTransferObject\DataTransferObject
. We can then define our three fields like so:class StoreUserDTO extends DataTransferObject
{
public string $name;
public string $email;
public string $password;
}
StoreUserRequest
from before to create and return a StoreUserDTO
class like so:class StoreUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return Gate::allows('create', User::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'string|required|max:50',
'email' => 'email|required|unique:users',
'password' => 'string|required|confirmed',
];
}
/**
* Build and return a DTO.
*
* @return StoreUserDTO
*/
public function toDTO(): StoreUserDTO
{
return new StoreUserDTO(
name: $this->name,
email: $this->email,
password: $this->password,
);
}
}
class UserController extends Controller
{
public function store(StoreUserRequest $request, StoreUserAction $storeUserAction): RedirectResponse
{
$storeUserAction->execute($request->toDTO());
return redirect(route('users.index'));
}
public function unsubscribe(User $user): RedirectResponse
{
$user->unsubscribeFromNewsletter();
return redirect(route('users.index'));
}
}
class StoreUserAction
{
public function execute(StoreUserDTO $storeUserDTO): void
{
$user = User::create([
'name' => $storeUserDTO->name,
'email' => $storeUserDTO->email,
'password' => $storeUserDTO->password,
]);
$user->generateAvatar();
$this->dispatch(RegisterUserToNewsletter::class);
}
}
email_address
rather than email
, we would now be able to solve it by simply building the DTO and assigning the DTO's email field the request's email_address
field. Let's imagine that the API request had it's own separate form request class. It could look like this as an example:class StoreUserAPIRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return Gate::allows('create', User::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'string|required|max:50',
'email_address' => 'email|required|unique:users',
'password' => 'string|required|confirmed',
];
}
/**
* Build and return a DTO.
*
* @return StoreUserDTO
*/
public function toDTO(): StoreUserDTO
{
return new StoreUserDTO(
name: $this->name,
email: $this->email_address,
password: $this->password,
);
}
}
User
model and we want to be able to perform all CRUD (create, update, update, delete) operations on this model. A resource controller typically contains index()
, create()
, store()
, show()
, edit()
, update()
and destroy()
methods. It doesn't necessarily have to include all of these methods, but it wouldn't have any methods that weren't in this list. By using these types of controllers, we can make our routing RESTful. For more information about REST and RESTful routing, check out this article here.__invoke()
method. These are really useful if you have a controller that doesn't really fit into one of the RESTful methods that we have in our resource controllers.UserController
could probably be improved by moving the unsubscribe
method to its own single-use controller. By doing this, we'd be able to make the UserController
a resource controller that only includes resource methods.php artisan make:controller UnsubscribeUserController -i
-i
to the command so that the new controller will be an invokable, single-use controller. We should now have a controller that looks like this:class UnsubscribeUserController extends Controller
{
public function __invoke(Request $request)
{
//
}
}
unsubscribe
method from our old controller:class UnsubscribeUserController extends Controller
{
public function __invoke(Request $request): RedirectResponse
{
$user->unsubscribeFromNewsletter();
return redirect(route('users.index'));
}
}
routes/web.php
file to call the use the UnsubscribeController
controller rather than the UserController
for this method.