Why my test is not running the model observer? - laravel

I'm working in a laravel project where I make some logic while updating data. For this purpose I'm taking advantage of the observer pattern in updating() method.
Here are my simplified snippets:
ModuleObserver
public function updating($module)
{
dd('This must be shown');
}
ModuleController
public function update(ModuleUpdateRequest $request, string $moduleId): ModuleResource
{
try {
$module = Module::findOrFail($moduleId);
} catch (ModelNotFoundException $e) {
throw new NotFoundHttpException('Module not found.');
}
$module->fill($request->all());
$module->save();
}
ModuleControllerTest
public function testModuleUpdate()
{
$module = factory(Module::class)->create(['name' => 'My Module']);
$this->actingAs($this->user)->putJson('/modules/' . $module->id, [ 'name' => 'My Updated Module' ])->assertStatus(200);
}
The test passes ok instead of showing the debugger line.
(My observer is correctly loaded in AppServiceProvider file, BTW it works fine out of the test context)

According to Laravel documentation:
When issuing a mass update via Eloquent, the saving, saved, updating, and updated model events will not be fired for the updated models. This is because the models are never actually retrieved when issuing a mass update.
To make sure of the model event to be fired you can use model properties ...
Something like:
$module->name = $request->input('name');
$module->date = $request->input('date');
// ...........
$module->save();

Related

Why this Implicit Binding is not working? - Laravel

I'm using Laravel 9. I have a problem, i wil appreciate any help.
I Have a Model named Entity, a controller EntityControler, and a FormRequest UpdateEntityRequest.
My API Routes looks like:
Route::apiResource('entities', EntityController::class);
so i have show, create, store, update, delete... routes.
This is muy update method in EntityController (without the code/not important now)
public function update(UpdateEntityRequest $request, Entity $entity)
{
return $entity;
}
the update method works perfect. But I want another update method for only a section and here starts the problem.
This is my new API Routes:
Route::apiResource('entities', EntityController::class);
Route::patch('/entities/{id}/{section}',[EntityController::class, 'updateSection' ]);
And this is the new method in the controller(without code yet):
public function updateSection( UpdateEntityRequest $request,Entity $entity, $section)
{
return $entity;
}
But this last method return [] insted of the Entity and the update method works. WHY?
I change de uri in Postman for update PUT {{baseUrl}}/entities/1 and for updateSection {{baseUrl}}/entities/1/1 .
Why does work in update and not in updateSection?
PD:
This method work, and give the id, and I can create a Entity from this:
public function updateSection( UpdateEntityRequest $request, $entity, $section)
{
return $entity;
}
But this is not what I want. Any idea why this happen?
please make sure your uri segment is same as the variable name in the controller, in your case replace id with entity
Route::patch('/entities/{entity}/{section}',[EntityController::class, 'updateSection' ]);
for more please see documentation
Make the route param name consistent in your route api.php and your function updateSection in EntityController
Route::patch('/entities/{entity}/{section}',[EntityController::class, 'updateSection' ]);
and
public function updateSection( UpdateEntityRequest $request,Entity $entity, $section)
{
return $entity;
}

Laravel request remove fields before validation

I am trying to remove some fields before they are validated.
Trying to attempt this inside prepareForValidation()
use Illuminate\Foundation\Http\FormRequest;
class VideoRequest extends ApiRequest
{
// ..code..
protected function prepareForValidation()
{
$this->merge([
'public' => $this->toBoolean($this->public),
'notify' => $this->toBoolean($this->notify),
]);
$video_id = $this->route('video_id');
if($this->isMethod('put') && Video::salesStarted($video_id)){
Log::info('removing sales');
// attempt 1
$this->except(['sales_start', 'tickets', 'price']);
// attempt 2
$this->request->remove('sales_start');
// attempt 3
$this->offsetUnset('sales_start');
}
if($this->isMethod('put') && Video::streamStarted($video_id)){
Log::info('removing streams');
// attempt 1
$this->except(['stream_start', 'stream_count', 'archive']);
// attempt 2
$this->request->remove('sales_start');
// attempt 3
$this->offsetUnset('sales_start');
}
$thumb = $this->uploadThumbnail($video_id);
if($thumb !== null){
$this->merge($thumb);
}
}
// ..code..
}
I made sure the code was entering inside the if statement, however the fields are not being removed.
Running $this->request->remove() and $this->except() have no effect.
If I add safe() it throws Call to a member function safe() on null.
I also tried to use unset() but nothing seems to work.
The rules for the dates are like so:
'sales_start' => 'sometimes|required|date|after:now|before:stream_start',
'stream_start' => 'sometimes|required|date|after:now',
but the $request->validated() returns the errors although it shouldn't be validating the deleted fields.
"sales_start": [
"The sales start must be a date after now."
],
"stream_start": [
"The stream start must be a date after now."
]
Why are the fields not being deleted?
Edit
As requested I added some code.
This is what ApiRequest looks like:
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
abstract class ApiRequest extends FormRequest
{
protected function failedValidation(Validator $validator): void
{
$response['data'] = [];
$response['api_status'] = 'ng';
$response['status_message'] = 'failed validation';
$response['errors'] = $validator->errors()->toArray();
throw new HttpResponseException(
response()->json( $response, 422 )
);
}
protected function toBoolean($booleable)
{
return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
}
And the request is called from the controller like so:
public function update(VideoRequest $request, $video_id)
{
... some code ...
$validated = $request->validated();
... some code ...
}
so $this refers to the VideoRequest that extends FormRequest.
Can't find anything about deleting. But acording to Laravel docs you pick what keys you need from a request as follows:
$request->only(['username', 'password']);
// plug everything you need into the array.
$request->except(['username', 'password']);
//plug everything you don't need into the array
The latter is probably most useful to you.
Example:
Say I have the following keys: ['username', 'password', 'randomVal'];
$request->only(['username', 'password']);
// Output:
['username', 'password']
$request->except(['username', 'password']);
// Output:
['randomVal']
To remove (unset) a key from a Request before it goes to the Controller you can use offsetUnset()
inside your request:
protected function prepareForValidation()
{
$this->offsetUnset('sales_start');//same goes for the other key to remove...
}
This is a bit of an ugly answer.
Instead of modifying the request before the validation, I tried adding exclude when getting rules().
So something along these lines:
public function rules() {
$ex = $this->isMethod('put') && Video::salesStarted($video_id) ? 'exclude|' : '';
return [
'sales_start' => $ex.'sometimes|required|other_stuff',
];
}
Note that the validation 'exclude' only works if added first.
So this won't work:
'sometimes|required|other_stuff|exclude' //exclude is called last
I am still unable to find out why remove(), exclude(), offsetUnset() were not working, so this is not the right answer, but I hope it helps if someone is having the same issue.
Edit
Setting this as correct answer as I was unable to find an alternative solution/fix.

Data that has been edited is not updated into database

i had some problem for updating my data, i was able to catch the file that was going to be updated by using dd and they are there, but when i was submitting the form the data remains the same, nothing change and no error whatsoever, here are my store(update) controller :
public function store(Request $request)
{
//dd($request);
$request->validate([
'attachment_name' => 'required|file|image|mimes:jpeg,png,jpg,gif,svg|max:10048',
]);
$storedImage = $request->attachment_name->store('public/image/');
MediaOrder::updateOrCreate(['id' => $request->id],
[
'nomor'=> $request->nomor,
'nomor_reference'=> $request->nomor_reference,
'periode_start'=> $request->periode_start,
'periode_end'=> $request->periode_end,
'category_id'=> $request->category_id,
'type_id'=> $request->type_id,
'agency_code'=> $request->agency_code,
'agency_name'=> $request->agency_name,
'advertiser_code'=> $request->advertiser_code,
'advertiser_name'=> $request->advertiser_name,
'brand_code'=> $request->brand_code,
'brand_name'=> $request->brand_name,
'nett_budget'=> $request->nett_budget,
'gross_value'=> $request->gross_value,
'nett_cashback'=> $request->nett_cashback,
'nett_bundling'=> $request->nett_bundling,
'version_code'=> $request->version_code,
'spot'=> $request->spot,
'accountexecutive_name'=> $request->accountexecutive_name,
'group_id'=> $request->group_id,
'userto_name'=> $request->userto_name,
'notes'=> $request->notes,
'attachment_name'=> $storedImage,
]);
flash_success_store('Media Order successfully updated.');
if ($request->ajax()) {
return redirect_ajax_notification('media-order.index');
} else {
return redirect_ajax('media-order.index');
}
}
i already find and change it myself but to no avail, maybe someone can have a solution?, thank you for your time.
The most common reason that the model is not updated or entered is that you do not make the attributes fillable in the model itself. Go to your model MediaOrder and check if you have set all attributes fillable.
protected $fillable = [..];

Laravel save new record returns nothing

I'm trying to save new record, all i get is a white page
even dump and dd for $applicant ->save() returns nothing.
public function store(Request $request) {
try {
if (Auth::guest()) {
return redirect()->route('login');
}
$applicant = new Applicant();
$applicant->first_name = $request->get('first_name');
$applicant->middle_name = $request->get('middle_name');
$applicant->last_name = $request->get('last_name');
dump($applicant);
$applicant->save();
dd('Saved');
} catch (Exception $e) {
Log::error("Error during orders creation:" .$e>getTraceAsString());
}
}
I expected the record should be saved
Try this instead
public function store(Request $request){
$applicant = new Applicant;
$applicant->first_name = $request->input('first_name');
$applicant->save();
}
and don't forget to include use App\Applicant model in your Controller class; If it does not solve your problem. please double check your form action post in view section.
I think save function will not return anything.
for data you must use create method.
Used return after save.
$applicant->save();
return response()->json(['success' => 'Data Added successfully.']);
Use create in stead, write less code if you can us always better.
Try this:
Application::create($request->all());
Or change it the way you need by assigning every array key to its value

laravel controller action structure

After watching many laracasts, one statement is everywhere: keep the controller as light as possible.
Ok, I am trying to familiarize myself with laravel concepts and philosophy, with the Repository and the separation of concerns patterns and I have some questions that bother me, let's assume the following:
Route::resource('/item', 'ItemController');
class Item extends \Eloquent {}
the repo
class EloquentItemRepo implements ItemRepo {
public function all()
{
return Item::all();
}
public function find($id)
{
return Item::where('id', '=', $id);
}
}
and the controller:
class ItemController extends BaseController {
protected $item;
public function __construct(ItemRepo $item)
{
$this->item = $item;
}
public function index()
{
$items = $this->item->all();
return Response::json(compact('items'))
}
}
For now, everything is simple and clean (assume that the repo is loaded by providers etc.) the controller is really simple and does nothing except loading and returning the data (I used json but anything will do).
Please assume that I am using an auth filter that checks that the user
is logged in and exists, or return an error if it doesn't, so I don't
have to do any further check in the controller.
Now, what if I need to do more checks, for instance:
response_* methods are helpers that format a Json response
public function destroy($id)
{
try {
if ($this->item->destroy($id)) {
return Response::json(['success' => true]);
}
return response_failure(
Lang::get('errors.api.orders.delete'),
Config::get('status.error.forbidden')
);
} catch (Exception $e) {
return response_failure(
Lang::get('errors.api.orders.not_found'),
Config::get('status.error.notfound')
);
}
}
In this case I have to test many things:
The desctuction worked? (return true)
The destruction failed? (return false)
There was an error during deletion ? (ex.: the item wasn't found with firstOrFail)
I have methods where many more tests are done, and my impression is that the controller is growing bigger and bigger so I can handle any possible errors.
Is it the right way to manage this ? The controller should be full of checks or the tests should be moved elsewhere ?
In the provider I often use item->firstOrFail() and let the exception bubble up to the controller, is it good ?
If someone could point me to the right direction as all the laracasts or other tutorials always use the simpler case, where not many controls are needed.
Edits: Practical case
Ok so here a practical case of my questioning:
controller
/**
* Update an order.
* #param int $id Order id.
* #return \Illuminate\Http\JsonResponse
*/
public function update($id)
{
try {
$orderItem = $this->order->update($id, Input::all());
if (false === $orderItem) {
return response_failure(
Lang::get('errors.api.orders.update'),
Config::get('status.error.forbidden')
);
}
return response_success();
} catch (Exception $e) {
return response_failure(
Lang::get('errors.api.orders.not_found'),
Config::get('status.error.notfound')
);
}
}
repo
public function update($id, $input)
{
$itemId = $input['itemId'];
$quantity = $input['quantity'] ?: 1;
// cannot update without item id
if (!$itemId) {
return false;
}
$catalogItem = CatalogItem::where('hash', '=', $itemId)->firstOrFail();
$orderItem = OrderItem::fromCatalogItem($catalogItem);
// update quantity
$orderItem->quantity = $quantity;
return Order::findOrFail($id)->items()->save($orderItem);
}
In this case thare are 3 possible problems:
order not found
catalogItem not found
itemId not set in post data
In the way I have organized that, the problem is that the top level error message won't be clear, as it will alway state: "order not found" even if it's the catalog item that couldn't be found.
The only possibility that I see is to catch multiple exceptions codes in the controller and raise a different error message, but won't this overload the controller ?

Resources