Simplify API boilerplate in Laravel's controllers? - laravel

When I write APIs with Laravel, I often use the same method as GitHub API v3. I add URLs to ease the navigation during development and also for the users that will develop using that API.
In this example, I manually add the URLs on every field then add a count for the pagers on the frontend. I sometime have more complicated stuff if I want to add the necessary to filter the results (if used with Vuetify or Kendo Grid).
class UserController extends Controller
{
function index() {
$users = User::all()->each(function ($item, $key) {
$item['activities'] = url("/api/users/{$item['id']}/activities");
$item['classrooms'] = url("/api/users/{$item['id']}/classrooms");
});
return [
'count' => count($users),
'courses' => $users,
];
}
}
Is there a way to make this code less boilerplate? Is there a package that does everything out of the box?

I'm a big fan of fractal especially spaties fractal package. This enables you to transform objects into responses.
There are two concepts in fractal serializers and transformers. Serializers is about the whole request, meta information data etc. Transformer is how you transform each model or object. Normally you would not make custom serializers, but in your case in can solve most of your trouble.
use League\Fractal\Serializer\ArraySerializer;
class YourSerializer extends ArraySerializer {
public function collection($resourceKey, array $data)
{
return [
$resourceKey => $data,
'count' => count($data),
];
}
}
Create your transformer for your user. The other big thing you gain for using this, is you have one plays to be responsible for transformation. Instead of each controller having to have the logic, which will be spread out and you have to remember to include it.
use League\Fractal\TransformerAbstract;
class AccountBalanceTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'id' => $user->id,
'name' => $user->id,
// other user fields
'activities' => url("/api/users/{$user->id}/activities"),
'classrooms' => url("/api/users/{$user->id}/classrooms"),
];
}
}
You have to assign the serializer in your fractal.php config.
'default_serializer' => YourSerializer::class,
Now you should be able to transform you responses like so.
return fractal($users, new UserTransformer())
->withResourceName('courses')
->respond(Response::HTTP_OK);
For making it easier to use and avoid repeating your self, i usually do a method on a parent controller like so. While setting the transformer on the object.
public function respond($data, $resourceKey, $status = Response::HTTP_OK) {
return fractal($data, $this->transformer)
->withResourceName($resourceKey)
->respond($status);
}
This will produce a response similar to what was specified in the question.

Related

Laravel custom attributes loads relationships even when attribute is not asked

I have a custom attribute that calculates the squad name (to make our frontend team lives easier).
This requires a relation to be loaded and even if the attribute is not being called/asked (this happens with spatie query builder, an allowedAppends array on the model being passed to the query builder and a GET param with the required append(s)) it still loads the relationship.
// Model
public function getSquadNameAttribute()
{
$this->loadMissing('slots');
// Note: This model's slots is guaranteed to all have the same squad name (hence the first() on slots).
$firstSlot = $this->slots->first()->loadMissing('shift.squad');
return ($firstSlot) ? $firstSlot->shift->squad->name : null;
}
// Resource
public function toArray($request)
{
return [
'id' => $this->id,
'squad_name' => $this->when(array_key_exists('squad_name', $this->resource->toArray()), $this->squad_name),
'slots' => SlotResource::collection($this->whenLoaded('slots')),
];
}
Note: squad_name does not get returned if it's not being asked in the above example, the relationship is however still being loaded regardless
A possible solution I found was to edit the resource and includes if's but this heavily reduces the readability of the code and I'm personally not a fan.
public function toArray($request)
{
$collection = [
'id' => $this->id,
'slots' => SlotResource::collection($this->whenLoaded('slots')),
];
if (array_key_exists('squad_name', $this->resource->toArray())) {
$collection['squad_name'] = $this->squad_name;
}
return $collection;
}
Is there another way to avoid the relationship being loaded if the attribute is not asked without having spam my resource with multiple if's?
The easiest and most reliable way I have found was to make a function in a helper class that checks this for me.
This way you can also customize it to your needs.
-- RequestHelper class
public static function inAppends(string $value)
{
$appends = strpos(request()->append, ',') !== false ? preg_split('/, ?/', request()->append) : [request()->append];
return in_array($value, $appends);
}
-- Resource
'squad_name' => $this->when(RequestHelper::inAppends('squad_name'), function () {
return $this->squad_name;
}),

Map controller functions into database records in Laravel

I try to implement a chain of approval methodology in my Laravel app.
Lets say that I have a standard CRUD controller with standard REST routes.
[URL]/products
In the controller I have 3 functions (Index, Store, Update)
I want that for Store and Update a certain condition will happen before, something like that (pseudo):
if (fucntion requires chain of approval) {
if (auth()->user !== one of the approvers){
email all approvers;
return 201 "pending approval";
}
}
// the logged in user is allowed to execute the function
rest of the code...
I'm struggling with a few things here:
The only thing I can think of something that might be similar to this inside Laravel is the $this->authorize() function but I don't think that it was meant to be used like this, it is meant to authorize or not to authorize, not for 201 codes, and using it means aborting with 201 and it doesn't sit right
I want to allow the admins to control which functions need approval and because I don't want to maintain my controller functions together with a seeder that will contain a list of all the functions I'm thinking about creating an artisan command to be run before production and mapping all the functions into a database table that will be used as a model and all the process will use a proper many to many relations between the functions and the approvers, but I don't know how to map the functions with artisan command and I don't know if this is even the right way to go
I want to avoid from writing a certain code in all the functions that might require approval, and don't know how and if it is even possible
The return of the functions should return a JsonResource of the specific model for example ProductResource, What should I return when I need approval? Just to mock a proper response with status pending?
Thanks for everybody who is willing to give me some guidance.
You can do something similar to below... just have a single class that defines how each user type (or even permission) is handled.
public function index(IndexRequest $request) // verify that use is authorized to do this action
{
$response = (new MyJobDirector)->handle(Auth::user());
.. handle response
}
MyJobDirector Class
class MyJobDirector
{
const STRATEGY = [
'user' => 'userHandler',
'manager' => 'managerHandler',
];
public function handle(User $user): array
{
return $this->{static::STRATEGY[$user->role]}();
}
protected function userHandler(): array
{
event(EmailApprovers::class);
return [
'code' => 201,
'response' => 'pending approval',
];
}
}
You can make it even more complicated by defining a separate class for each handler and specifying constants for status and a response that each class should return
class User extends BaseJobHandle
{
const CODE = 201;
const RESPONSE = 'pending approval';
}
abstract class BaseJobHandler
{
const CODE;
const RESPONSE;
public function handle(): array
{
$this->additionalProcesses();
return [
'code' => static::CODE,
'response' => static::RESPONSE,
];
}
protected function additionalProcesses(): void {}
}
class MyJobDirector
{
const STRATEGY = [
'user' => User::class,
'manager' => Manager::class,
];
public function handle(User $user): array
{
$class = static::STRATEGY[$user->role];
return (new $class)->handle();
}
}
Regarding The return of the functions should return a JsonResource of the specific model for example ProductResource, What should I return when I need approval? Just to mock a proper response with status pending?
You can have a ProductResouce class, in which you decide which specific resource to return depending on a use case. It's all depends on what data you need to return.

What is the correct order of form validation and retrieving input values?

I wonder if I should do form validation before retrieving input values or vice versa.
I usually do validation first as I see no benefit in trying to access input values that might not be valid. However, a coworker looked at my code recently and found it strange. Is there any correct order for these steps?
public function createGroups(Request $request)
{
$this->validate($request, [
'courses' => 'required_without:sections',
'sections' => 'required_without:courses',
'group_set_name' => 'required',
'group_number' => 'required|integer|min:1'
]);
$courses = $request->input('courses');
$sections = $request->input('sections');
$group_set_name = $request->input('group_set_name');
$group_number = $request->input('group_number');
Positioning the validation for your controller logic at the beginning of a method is probably the way to go here, as you have required parameters defined. If you receive data that does not fully satisfy the requirements, you produce a validation error back to the user. This follows the productive "Fail Fast" line of thinking: https://en.wikipedia.org/wiki/Fail-fast
It's also important that you're not using any data that hasn't passed your stringent requirements from validation. Data that fails validation should no longer be trusted. Unless there's some other reason you need to be, say, logging any incoming data from the frontend, the order here looks good to me.
I totally agree with #1000Nettles response, to elaborate a little bit more on his/her answer (who should be the accepted one): There isn't any need to continue with your business logic when the data doens't comply with your specifications. Let's say you expected a string of a N characters long, because you defined your database with that limitation (in order to optimize the db desing), will you try to persist it even when it'll throw an exception? Not really.
Besides, Laravel has a particular way to extract validation classes: Form Request. This are injected in controllers. When a call reach the controller it means that already passed the validation, if not, an 422error be returned.
Create a custom request and keep the mess out of your controller, it doesn't even hit your controller function if validation failed and can just grab the data in your controller if validation passed.
php artisan make:request GroupRequest
In app/Http/Requests/GroupRequest.php:
public function authorize()
{
// return true;
return request()->user()-isAdmin; // <-- example, but true if anyone can use this form
}
public function rules()
{
return [
'courses' => ['required_without:sections'],
'sections' => ['required_without:courses'],
'group_set_name' => ['required'],
'group_number' => ['required', 'integer', 'min:1'],
];
}
The best part is you can even manipulate the data in here (GroupRequest.php) after it has been validated:
public function validated()
{
$validated = $this->getValidatorInstance()->validate();
// EXAMPLE: hash password here then just use new hashed password in controller
$validated['password'] = Hash::make($validated['password']);
return $validated;
}
In your controller:
public function createUser(UserRequest $request) // <- in your case 'GroupRequest'
{
$validated = $request->validated(); // <-- already passed validation
$new_user = User::create($validated); // <-- password already hashed in $validated
return view('dashboard.users.show')->with(compact('user'));
}
In your case, if you use my GroupRequest block above, you can return to view in 1 line of code:
public function createGroups(GroupRequest $request)
{
return view('example.groups.show')->with($request->validated()); // <-- already an array
}
In you blade view file, you can then use your variables like {{ $group_set_name }} and {{ $group_number }}

Laravel 5: How to create a router model binding on multiple parameters

So far I know how to create a router model binding on single parameters like so:
// RouteServiceProvider.php
$router->model('subject_slug', 'App\Subject', function($slug) {
return Subject::where('slug', $slug)->firstOrFail();
});
The above can then be used like this:
// routes.php
Route::get('/{subject_slug}', 'MenuController#showSubject');
And in the controller:
public function showSubject(Subject $subject) {
....
}
But sometimes I need to specify multiple parameters in order to get the right model.
For example consider the following route:
Route::get('/{subject_slug}/{topic_slug}/', 'MenuController#showTopic');
and the corresponding controller:
public function showTopic(Subject $subject, Topic $topic) {
....
}
However to get the correct model for Topic I need to know the Subject. For example:
// !!! Invalid laravel code !!!
$router->model('topic_slug', 'App\Topic, function($subject_slug, $topic_slug) {
// ERROR: $subject_slug is obviously not defined!
return Topic::where([
'subject_slug' => $subject_slug,
'slug' => $topic_slug,
])->firstOrFail();
});
How can I make a router model binding for Topic bearing in mind I need to know the Subject parameter before it in order to fetch the correct Topic.
Is there an alternative better way of doing this?
UPDATE
Currently my showTopic method in my controller is like this:
public function showTopic(Subject $subject, $topic_slug) {
$topic = Topic::where([
'subject_slug' => $subject_slug,
'slug' => $topic_slug,
])->firstOrFail();
// ...
}
and I have no router model binding for topic_slug.
This works as expected, but I would like to take advantage of router model bindings!
It turns out the way I was doing it was a bit flawed. I was unnessarily using model bindings when instead it would be better to have used a normal binding like so:
$router->bind('topic_slug', function($slug, Route $route) {
$subject = $route->parameter('subject_slug');
return Topic::where([
'subject_slug' => $subject->slug,
'slug' => $slug,
])->firstOrFail();
});
Also I was using model bindings completely wrong before as the 3rd function should be the "not found behaviour" (not for additional logic)!

CakePHP - Where to put reusable custom validation rules?

I want to add a custom validation rule that can be reused across the application when necessary. Where is the best place to put it?
I know I can put them in AppModel but it's messy and pollutes/bloats the AppModel with methods that are only used occasionally for validation, and I find I need to prefix them all with validateUsername, validateCustom, etc. to keep them clearly organised from the other methods in the AppModel. I feel like there is a better way of doing this in the OOP structure.
Is there a way to specify custom rules as static methods of a Lib class, for example?
e.g.
app/Lib/Validate.php
class Validate {
public function username($value) {
$value = array_shift($value);
return preg_match('/^[A-Z0-9_-]+$/i', $value);
}
}
And then use the rules only in the models when you need to:
app/Model/MyModel.php
App::uses('Validate', 'Lib');
class MyModel extends AppModel {
public $validate = array(
'username' => array(
'rule' => 'Validate::username',
'message' => 'Username contains invalid characters'
)
);
}
You could do this with a Behavior. http://book.cakephp.org/2.0/en/models/behaviors.html There is a setup callback you could use to attach validation rules or create your own custom functions. You can then attach the Behaviors as needed to models.

Resources