How to separate controller method in Laravel? - php

I start with Laravel, I write API. I have a method in TestController that checks if the student has correctly inserted data and has access to the exam solution. I do not think it's a good idea to have the whole method in the controller, but I have no idea how to separate it. I think about politics, but I have to have several models for one policy, maybe I can try to put part of the method on AuthorizeStudentRequest or try it in a different way? Of course, now I am returning 200 with the message, but I have to return 422 or another code with errors, but I have not done it because of my problem.
public function authorizeStudent(AuthorizeStudentRequest $request)
{
$hash = $request->input('hash');
$token = $request->input('token');
$exam = Exam::where([['hash', $hash], ['token', $token]])->first();
if($exam == null)
return ['message' => 'Exam does not exist.'];
$user = $exam->user_id;
$studentFirstname = $request->input('firstname');
$studentLastname = $request->input('lastname');
$student = Student::where([
['firstname', $studentFirstname],
['lastname', $studentLastname],
['user_id', $user]
])->first();
if($student == null)
return ['message' => 'Student does not exist.'];
$classroom = Classroom::where([
['name', $classroomName],
['user_id', $user]
])->first();
if($classroom == null)
return ['message' => 'Classroom does not exist.'];
if($student->classroom_id != $classroom->id)
return ['message' => 'Student is not in classroom.'];
if($exam->classrooms()->where(['classroom_id', $classroom->id], ['access', 1])->first() == null)
return ['message' => 'Class does not access to exam yet.'];
}

I would suggest you rather pass the primary keys of the selected $exam, $student and $classroom models to your controller from the form and validate whether they exist in the corresponding tables, rather than having to check their existence using a bunch of different columns.
If you pass the primary keys, you could use the 'exists' validation rule to check if they exist. For example, in your AuthorizeStudentRequest class you could have the following function:
public function rules()
{
return [
'exam_id' => 'required|exists:exams',
'student_id' => 'required|exists:students',
'classroom_id' => 'required|exists:classrooms',
];
}
Otherwise, if you really need to use the different columns to check the existence of the exam, student and classroom, you could create custom validation rules and use them in your AuthorizeStudentRequest class. For example, create a custom validation rule that checks whether the exam exists as follows:
$php artisan make:rule ExamExists
class ExamExists implements Rule
{
private $token;
private $hash;
public function __construct($token, $hash)
{
$this->token = $token;
$this->hash = $hash;
}
public function passes($attribute, $value)
{
return Exam::where([['hash', $hash], ['token', $token]])->count() > 0;
}
}
And then you can use the custom validation rule in your request as follows:
public function rules()
{
return [
'hash' => ['required', new ExamExists($this->hash, $this->token)],
... other validation rules ...
]
}
For checking whether a student has access to a classroom or a class has access to an exam, you could use policies.

API resources present a way to easily transform our models into JSON responses. It acts as a transformation layer that sits between our Eloquent models and the JSON responses that are actually returned by our API. API resources is made of two entities: a resource class and a resource collection. A resource class represents a single model that needs to be transformed into a JSON structure, while a resource collection is used for transforming collections of models into a JSON structure.
Both the resource class and the resource collection can be created using artisan commands:
// create a resource class
$ php artisan make:resource UserResource
// create a resource collection using either of the two commands
$ php artisan make:resource Users --collection
$ php artisan make:resource UserCollection
Before diving into all of the options available to you when writing resources, let's first take a high-level look at how resources are used within Laravel. A resource class represents a single model that needs to be transformed into a JSON structure. For example, here is a simple User resource class:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
Every resource class defines a toArray method which returns the array of attributes that should be converted to JSON when sending the response. Notice that we can access model properties directly from the $this variable. More information here
https://laravel.com/docs/5.7/eloquent-resources

Related

How to skip whole records/models when using Eloquent API Resources

I'm using the API resources & collections from here: https://laravel.com/docs/8.x/eloquent-resources
It works pretty well, except I can't find a clean way to filter returned models (records). So here's what I'm doing now:
class MyResource extends JsonResource
{
public function toArray($request)
{
if($someCondition) {
return [];
}
if(! $otherCondition) {
return null;
}
return [
'data' => [
'id' => $this->id,
'name' => $this->name,
'url' => $this->link,
],
];
}
Then from my api.php routes file, I call that like this:
Route::middleware('auth:api')->get('/myendpoint', function(Request $request) {
return MyResource::collection(Item::paginate()->withQueryString());
});
This mostly works, but my early returns still show up in the output as literal "null" values (the string "null").
I've searched and searched, from Google to Youtube to the Laravel docs and here on SO, but I can't find any mention of this. Is there really no way to exclude certain records/models from the API output?
Of course, I could manually code this up in my controller & model, but it'd be really nice to use Laravel's built-in API resources for this.

Factory relationship returning null laravel testing

I'm trying to unit testing a service that handles the registration of a user in Laravel.
This is the service:
public function completeRegistration(Collection $data)
{
$code = $data->get('code');
$registerToken = $this->fetchRegisterToken($code);
DB::beginTransaction();
$registerToken->update(['used_at' => Carbon::now()]);
$user = $this->userRepository->update($data, $registerToken->user);
$token = $user->createToken(self::DEFAULT_TOKEN_NAME);
DB::commit();
return [
'user' => $user,
'token' => $token->plainTextToken,
];
}
Where the update method has the following signature:
<?php
namespace App\Repositories\User;
use App\Models\User;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
interface UserRepositoryInterface
{
public function create(Collection $data): User;
public function update(Collection $data, User $user): User;
}
With my test being:
/**
* Test a user can register
*
* #return void
*/
public function test_user_can_complete_registration()
{
$userRepositoryMock = Mockery::mock(UserRepositoryInterface::class);
$registerTokenRepositoryMock = Mockery::mock(RegisterTokenRepository::class);
$userFactory = User::factory()->make();
$registerTokenFactory = RegisterToken::factory()
->for($userFactory)
->timestamped()
->make(['user_id' => $userFactory->id]);
dd($registerTokenFactory->user);
$userRepositoryMock
->expects($this->any())
->once()
->andReturn($userFactory);
....
}
When I run phpunit --filter=test_user_can_complete_registration I get the following error:
1) Tests\Unit\Services\Auth\AuthServiceTest::test_user_can_complete_registration
TypeError: Argument 2 passed to Mockery_0_App_Repositories_User_UserRepositoryInterface::update() must be an instance of App\Models\User, null given, called in /var/www/app/Services/Auth/AuthService.php on line 64
/var/www/app/Services/Auth/AuthService.php:64
/var/www/tests/Unit/Services/Auth/AuthServiceTest.php:88
This tells me that the user relationship on $registerTokenFactory is null. When I do:
public function test_user_can_complete_registration()
{
...
dd($registerTokenFactory->user);
}
I get the output null. I'm trying to test the service without hitting the database. How can I attach the user relationship to the $registerTokenFactory object? I have tried using for and trying to attach directly:
$registerTokenFactory = RegisterToken::factory()
->for($userFactory)
->timestamped()
->make(['user_id' => $userFactory->id, 'user' => $userFactory]);
In Laravel factories make() does only create the model and does not save it. For relationship to work, you will need your models to be saved.
$userFactory = User::factory()->create();
Since you do not want to use a Database, which is wrong in my opinion. People don't like writing tests, so when we have to do it make it simple, mocking everything to avoid databases is a pain. Instead an alternative is to you Sqlite to run in memory, fast and easy. A drawback is some functionality does not work there JSON fields and the version that are in most Ubuntu distributions does not respect foreign keys.
If you want to follow the path you are already on, assigned the user on the object would work, you have some left out bits of the code i assume.
$userRepositoryMock = Mockery::mock(UserRepositoryInterface::class);
$registerTokenRepositoryMock = Mockery::mock(RegisterTokenRepository::class);
$user = User::factory()->make();
$registerToken = RegisterToken::factory()
->for($userFactory)
->timestamped()
->make(['user_id' => $user->id]);
$registerToken->user = $user;
$registerTokenRepositoryMock
->expects('fetchRegisterToken')
->once()
->andReturn($registerToken);
$userRepositoryMock
->expects($this->any())
->once()
->andReturn($user);
// execute the call

Wrapping JSON response in an abstracted way in PHP / Laravel

I am making a REST API that will return different JSON responses depending on which type of User is making the call.
There is a single endpoint: example.com/api/v1/collect that uses Laravel's API authentication to get the User model with $user = auth()->guard('api')->user();.
Each User will belong to a Type.
If User 1 (type_id 1) makes the call, the response will look like:
{
"example": 1,
"success": true,
"data" : [
...
]
}
If User 2 (type_id 2) makes the call, the response can be different, depending on the user's type. It could look like:
{
"example": 2,
"response" : [
...
],
"custom": "string",
"success": 200
}
The ... is the data that we are sending back (for example a list of Post titles) and it will always be the same, but the "envelope" (or wrapper) around it would be specific to each user (or type of user).
So far, I've found two solutions to wrap that ... in an abstracted way:
Solution 1: Using Laravel Blade
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different blade filename
// There could be around a 100 types which will result in a 100 blade files
// The filename is stored in the database
$filename = $user->type->filename; // returns 'customBladeTemplate'
// Return a JSON response after passing the $data to the view
return response()->json([
view($filename, compact('data'))->render(),
]);
Using a blade file for each type of user allows me to wrap the data like this:
// resources/views/customBladeTemplate.blade.php
// This filename has to match the one in the database column
{
"example": 1,
"success": true,
"data" : [
{!! $data !!}
]
}
That will output a JSON response for the User 1 (example 1)
Solution 2: Using Laravel response macros
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different macro name
// There could be around a 100 types which will result in a 100 different macros
// The macro name is stored in the database
$macroName = $user->type->macro_name; // returns 'customMacroName'
return response()->{macroName}($data);
Creating a Macro for each type of user, using the macro name from the DB:
// App\Providers\AppServiceProvider.php
use Illuminate\Http\Response;
public function boot()
{
Response::macro('customMacroName', function ($data) {
return Response::json([
'example' => 2,
'response' => $data,
'custom' => 'string',
'success' => 200,
]);
});
}
That macro will output a JSON response for the User 2 (example 2)
Both options work fine but I am still wondering:
Is there another (possibly better) way to do it?
Are those two solutions valid or can they be enhanced?
Which of those two solutions seem to be better and why?
Edit: The $data is not actually coming from an eloquent model, it is rather from a serialized JSON column (JSON casting) - which means I can't use the Laravel API resources
If you are looking for the response formatting you should go with the Laravel API Resources
Based on your requirement(data formate different for two type of users), you can create two different Api Resource classes.
AdminResource & UserResource.
Here you have more flixibility on controlling fields or orgnizing data.
Here is how you can define the resource class:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UserResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
And you can use this as:
use App\User;
use App\Http\Resources\UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
If you would like to include with condition check in with user type, you can create a common function called renderJson($userType, $data) and place this in your parent class or can wrap with traits, all depends on your application architecture.
Here you can find laravel documentation for API Resource: https://laravel.com/docs/5.8/eloquent-resources
Edited:
With Laravel API Resource, you not only parse the modal object, you can parse any arrayble object.
Essentially they are just simple objects with one very important job
to do — transform your objects (interesting I said objects and not
models). To do this out of the box, all you have to do is instantiate
the Resource (collection or individual) with an Arrayable object. If
you did nothing else but generate a standard Resource and pass in an
Arrayable object the Resource would transform that object
automatically, and because Models are Arrayable this is where I got
caught out because if you create a resource collection and instantiate
it with a collection of models then the models get toArray'd and not
their corresponding resource.
Src: https://medium.com/#dinotedesco/laravel-api-resources-what-if-you-want-to-manipulate-your-models-before-transformation-8982846ad22c
So in your case if you can just collect() the json data and pass to api resource.
You could use middlewares to change what the response looks like.
With middleware you could change the response after the execution of your regular code, without having to take this into account in the controller itself. Using the below code you modify the response AFTER it has been executed.
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware
{
public function handle($request, Closure $next)
{
// Calls the controller and processes the request.
$response = $next($request);
// Here you can retrieve the user and modify the response however you want.
// Some example code:
$user = Auth::user();
if ($user->type == 1) {
... //Change response for user type 1
}
if ($user->type == 2) {
... //Change response for user type 2
}
// Etc...
return $response;
}
}
Reference: https://laravel.com/docs/5.8/middleware
Depends on how different the responses are from each other. I'm inclined to take inventory of each type's common features and build a response array as appropriate. This could be done in the controller or a helper function and then returned using Laravel's JSON response type.
$response = [];
// results common to all types
$response['example'] = $example;
$response['success'] = $success;
// customized results for specific types
if (in_array($type, [1, 3, 4, 5, ...])) {
$response['data'] = $dotdotdot;
}
if (in_array($type, [2, 6, 7, 8, ...])) {
$response['response'] = $dotdotdot;
$response['custom'] = $custom;
}
return response()->json($response);
I don't know if this is what you are looking for. I had something similiar a few months ago and fixed it with json files. As json is amazingly fast and you can create thousands of types.
Sorry for my bad english i will fix it after the weekend :-)
Let's get started.
First the user logs in using laravel passport or api routes.
Second the api calls a controller.(class). I will create a class based on your info.
let's say the api calls the ApiController and the method handle
use Illuminate\Http\Request;
class ApiController
{
public function __construct()
{
}
/**
* Handle the incoming request
*
* #param Request $request
*/
public function handle(Request $request)
{
//first let's find the correct format
$type = $requets->user()->type; //lets say type_1
$config = $this->getUserType($type);
//i don't know where you data comes from but let's say $data is your data.
$data = json_encode(['some' => "data", 'to' => "return"]);
//lets fill the data
$response = $this->fillDataInConfig($data, $config);
return response()->json($response);
}
/**
* Find the user type config by type name
*
* #param string $type
* #return object
*/
private function getUserType(string $type) : string
{
//let store them in the local storage
$config = \Storage::disk('local')->get("api_types/$type.json");
return json_decode($config);
}
/**
* Fill the data
*
* #param mixed $data
* #param object $config
* #return object
*/
private function fillDataInConfig($data, object $config) : object
{
//as for your example. The reusl//
// {
// "example": 2,
// "response" : *responseData*, <===== where the response should be
// "custom": "string",
// "success": 200
// }
foreach($config as $index => $value){
if($value === '*responseData*'){
$config->{$idnex} = $data;
}
}
//the data is filled in the response index
return $config;
}
}

Solution to non repetitive request validation in Laravel controller?

In my Laravel controllers I am always using the same validation to check if user submitted data is valid/invalid.
public function schedule(Request $request)
{
$request->validate([
'assessment_id' => 'required|integer',
'user_id' => 'required|integer',
'due_date' => 'required|string'
]);
$assessment_id = $request->input('assessment_id');
$user_id = $request->input('user_id');
$due_date = $request->input('due_date');
$staff = auth()->user();
$company = $staff->companies()->first();
$user = $this->staffAssessmentRepository->getUserById($user_id);
$assessment = $this->staffAssessmentRepository->getAssessmentById($assessment_id);
$date = Carbon::parse($due_date);
if(!$user || !$assessment){
return response()->json('Cannot find assessment and/or user!', 404);
}
if(!$company->hasUser($user)){
return response()->json('User does not belong to this company!', 401);
}
if(!$user->hasRole(Role::ROLE_CANDIDATE_NAME)){
return response()->json('User is not a candidate', 401);
}
if($user->hasAssessment($assessment, $company)){
return response()->json('Candidate already has this assessment!', 401);
}
$user_assessment = $this->staffAssessmentRepository->scheduleUserAssessment($user, $company, $assessment, $date, $staff);
if(!$user_assessment){
return response()->json('Failed to create user assessment!', 500);
}
return response()->json($user_assessment, 201);
}
Please look at this specific part from the PHP code above:
if(!$user || !$assessment){
return response()->json('Cannot find assessment and/or user!', 404);
}
if(!$company->hasUser($user)){
return response()->json('User does not belong to this company!', 401);
}
if(!$user->hasRole(Role::ROLE_CANDIDATE_NAME)){
return response()->json('User is not a candidate', 401);
}
if($user->hasAssessment($assessment, $company)){
return response()->json('Candidate already has this assessment!', 401);
}
In my controller methods I am always needing to validate the same user cases, and check if they fail (return response JSON) many times and it's become too repetitive. I'm trying to follow the DRY principle (Don't repeat yourself), and would like your solutions as to how I can prevent repetition in user validation. Solutions can be in PHP/Laravel but I am working in a Laravel project.
EDIT: please not that it isn't the fact that there are many if statements, the issue isn't that. The problem is that the SAME if statements are used amongst multiple different methods in my controllers and I need a architectural decision as to how I can decouple my code so my controllers can inherit the same validation (the if statements).
You should aim to clearly separate the various components of your application, meaning validation should be a single process rather than multiple fragmented processes. Your current approach -- validating some of the input using the Laravel Validator and then manually validating the rest -- is less than ideal.
The ideal approach is to use the Laravel Validator for validating all of the input, including state. There are additional features that can help with this, Form Requests which allow you to implement more advanced validation and re-use validation across multiple controllers, and there's Rule Objects which allow you to implement custom validation logic for attributes.
Your code does the following:
Checks if the User exists
Checks if the Assessment exists
Checks if the User belongs to the Company
Checks if the User is a candidate
Checks if the User already has an assessment
Each of these can be implemented as custom Rules, then you can create a single Form Request which would look something like this:
/**
* Get the validation rules that apply to scheduling an Assessment.
*
* #return array
*/
public function rules(): array
{
return [
'assessment_id' => 'required|exists:assessments',
'user_id' => ['required', 'exists:users', new BelongsToCompany, new IsCandidate],
'due_date' => 'required|date',
];
}
Then for more complex validation (such as validating that the user doesn't already have that assessment which requires 2 input values) you can either have a Rule that you explicitly pass in the additional values or you can use withValidator to extend the validator -- that's covered in the documentation.
Passing in the additional value:
/**
* Get the validation rules that apply to scheduling an Assessment.
*
* #return array
*/
public function rules(): array
{
return [
'assessment_id' => ['required', 'exists:assessments', new AssessmentAvailable(request()->input('user_id'))],
'user_id' => ['required', 'exists:users', new BelongsToCompany, new IsCandidate],
'due_date' => 'required|date',
];
}
Extending the validator:
public function rules(): array
{
// ...
}
/**
* Validates whether or not an assessment is available for the User.
*
* #param \Illuminate\Validation\Validator $validator
*
* #return void
*/
public function withValidator($validator)
{
$validator->after(function ($validator) {
$user = User::findOrFail($this->input('user_id'));
$assessment = Assessment::findOrFail($this->input('assessment_id'));
if ($user->hasAssessment($assessment)) {
$validator->errors()->add('assessment_id', 'The user already has this assessment.');
}
});
}
This approach gives you easy to re-use validation logic and the full power of Laravel's validation system, including input errors for users.

How to check for required fields in object?

i have example object with fields
name => John
surname => Dow
job => engineer
and output form with placeholders. some required, some not.
what is best practice for check if it requred and show error with null fields?
There are multiple ways actually you can do that inside of controller method or make use of Laravels Request Classes for me I prefer to use Request Classes
look below I will list the two examples
Validate inside the controller's method
public function test(Request $request){
if($request->filled('name){
/*filled will check that name is set on the current
request and not empty*/
//Do your logic here
}
}
Second way is by using the Validator Facade inside your controller
use Validator;
class TestController{
public function test(Request $request){
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
/*continue with your logic here if the request failed on
validator test Laravel will automatically redirect back
with errors*/
}
}
Third way my favorite one personally
you can generate a Request class using this command
php artisan make:request AddBookRequest
that will generate the request class under "app/Http/Requests/AddBookRequest" , inside of any generated request class you will find two methods authorize() and rules()
in the authorized method you have to return truthy or falsy value this will detect if the current user making the request has authorization to fire this request inside of the rules method you do pretty much as you did in the Validator in the second way check the example
public function authorize(){
return true;
}
public function rules(){
return [
'title' => 'required|string',
'author_id' => 'required|integer'
];
}
then simply in your controller you can use the generated request like this
use App\Http\Requests\AddBookRequest;
public function store(AddBookRequest $request){
/* do your logic here since we uses a request class if it fails
then redirect back with errors will be automatically returned*/
}
Hope this helps you can read more about validation at
https://laravel.com/docs/5.6/validation
I think "simple is the best", just through object and check if properties exists
Ref: property_exists
Example:
if (property_exists($object, 'name')) {
//...do something for exists property
} else {
//...else
}

Categories