Laravel mutator do not set model attribute automatically - laravel

In My project there is a PackageRequest model and this model have a tracking_code.
I want to set this attribute automatically with a mutator.
this is my mutator:
public function setTrackingCodeAttribute()
{
$code = mt_rand(1000000, 9999999);
$this->attributes['tracking_code'] = "$code";
}
but when I save new request into database there is no tracking_code
this is my part of code that create new PackageRequest:
public function store(Request $request)
{
$input = $request->only(['requested_time', 'address_id']);
$valid = validator($input, [
'requested_time' => 'required|date',
'address_id' => 'required|numeric|exists:addresses,id'
]);
if ($valid->fails())
return Response::fail($valid->errors());
$package_request = new PackageRequest($input);
$package_request->user_id = \Auth::id();
$package_request->status = 'waiting';
if(!$package_request->save())
return Response::error(__('errors.save_data', ['attr' => __('errors.attribures.package_request')]));
return Response::success(PackageRequest::find($package_request->id), 201);
}

The mutator will receive the value that is being set on the attribute, allowing you to manipulate the value and set the manipulated value on the Eloquent model's internal $attributes property.
The mutator will be automatically called when you attempt to set the value to attribute tracking_code, and you are not setting anything to it. What you need is setting default value to tracking_code before creating, so you can add these codes in model PackageRequest:
protected static function boot()
{
parent::boot();
static::creating(function($package_request)
{
$package_request->tracking_code = mt_rand(1000000, 9999999);
});
}

Related

Put other function on FormRequest in Laravel

I'm building a Laravel 6 application, and I am concerned about "best practices." I have one controller named CustomerController. In my controller, I want to update the Customer model, so I will have a function like the following.
public function update(UpdateCustomer $request, Customer $customer){
//
}
UpdateCustomer is my form request and where I will do the validation. In my update() method, I have classic validation.
public function rules()
{
$validationArray = [];
$validationArray['customer.name'] = 'string|required';
$validationArray['customer.vat'] = 'string|required';
$validationArray['customer.email'] = 'email|required';
return $validationArray;
}
Now I have to do some particular validation other the classic.
Let's assume that I have more data in my model, and I don't want these values to be changed.
For example, I have the following: address, cap, locality. I have a second method on the UpdateCustomer request that I can validate.
public function validateForDataCantChange()
{
$data = $this->input("customer");
$customer = $this->route("customerID");
$validator = Validator::make([], []); // Empty data and rules fields
$arrayDataThatCantChange = [
'address' => $data['address'] ?? NULL,
'cap' => $data['cap'] ?? NULL,
'locality' => $data['locality'] ?? NULL
];
foreach ($arrayDataThatCantChange as $key => $v) {
if ($customer->{$key} !== $v) {
$validator->errors()->add($key, __("messages.the field :field can't be changed", ['field' => $key]));
}
}
if ($validator->errors()->any()) {
throw new ValidationException($validator);
}
}
And then in my controller, I've added the following.
public function update(UpdateCustomer $request, Customer $customer){
$request->validateForDataCantChange();
}
Is this a bad practice? Should I create a new FormRequest? How, in this case (two form requests), can I use two different requests for a single controller?
For the little effort required, I'd personally create a new form request.
If you wish to use the same form request you can do the following:
public function rules()
{
$rules = [
'title' => 'required:unique:posts'
];
// when editing i.e. /posts/2/edit
if ($id = $this->segment(2)) {
$rules['title'] .= ",$id";
}
return $rules;
}
However, I always use a separate class for each action.

Laravel - request validation

I extedned request class to create my own valdiation rules. In that class I added my custom validation function. In function I check if tags are pass regEx and I would like to filter tags to remove tags shorter then 2 characters.
And later keep in request only tags that passed validation.
public function createPost(PostRequest $request)
{
dd($request->all()); //In this place I would like to keep only tags passed through validation not all tags recived in request
}
Is it possibile to do it? How to set it in Request class?
'tags' => [
'nullable',
'string',
function ($attribute, $value, $fail){
$tagsArray = explode(',', $value);
if(count($tagsArray) > 5) {
$fail(__('place.tags_max_limit'));
}
$tagsFiltered = [];
foreach ($tagsArray as $tag){
$tag = trim($tag);
if(preg_match('/^[a-zA-Z]+$/',$tag)){
$tagsFiltered[] = $tag;
};
}
return $tagsFiltered;
}
],
EDIT:
I think we miss understanding. I would like to after validation have only tags that returned in variable $tagsFiltered; Not the same as recived in input.
You have to create this custom regex rule and use it into rules() function.
Like so:
public function rules()
{
return [
'tag' => 'regex:/[^]{2,}/'
];
}
public function createPost(PostRequest $request)
{
$request->validated();
}
And then just call it via validated() function wherever you want.
first define validation rule with this command:
php artisan make:rule TagsFilter
navigate to TagsFilter rule file and define your filter on passes method:
public function passes($attribute, $value)
{
$tagsArray = explode(',', $value);
$tagsFiltered = [];
foreach ($tagsArray as $tag){
$tag = trim($tag);
if(preg_match('/^[a-zA-Z]+$/',$tag)){
$tagsFiltered[] = $tag;
};
}
return count($tagsArray) > 5 && count($tagsFiltered) > 0;
}
then include your rule in your validation on controller:
$request->validate([
'tags' => ['required', new TagsFilter],
]);

How to check data exists in the database

I have a function to add new property. But i want to check for duplicate data at column "code" before add new data into database. If data exists will appear a message error.
function addPro(Request $req)
{
$id = $req->type_id;
$type = AssetType::find($id);
if($req->save == 'save'){
$pro = new TypeProperties;
$pro->name = $req->name;
$pro->code = $req->code;
$pro->type = $req->type;
$pro->assettype_id = $req->type_id;
$pro->save();
Schema::table($type->code, function ($table) use ($pro) {
if ($pro->type == "textbox")
$table->string($pro->code )->nullable();
if ($pro->type == "textarea")
$table->text($pro->code )->nullable();
});
return redirect(url($type->id.'/add/property'))->with('message','Save successful');
}
return redirect(url('asset/type/'.$type->id));
}
You can use laravel Request Validation
function addPro(Request $req)
{
$id = $req->type_id;
$type = AssetType::find($id);
if($req->save == 'save'){
$req->validate([
'code' => 'required|unique:tablename'
]);
$pro = new TypeProperties;
$pro->name = $req->name;
$pro->code = $req->code;
$pro->type = $req->type;
$pro->assettype_id = $req->type_id;
$pro->save();
Schema::table($type->code, function ($table) use ($pro) {
if ($pro->type == "textbox")
$table->string($pro->code )->nullable();
if ($pro->type == "textarea")
$table->text($pro->code )->nullable();
});
return redirect(url($type->id.'/add/property'))->with('message','Save successful');
}
return redirect(url('asset/type/'.$type->id));
}
The most simple way to do this is by checking if code is_null :
if (is_null($pro->code)) {
// It does not exist
} else {
// It exists
}
The other way is to make a validation using Laravel's built in ValidateRequest class. The most simple use-case for this validation, is to call it directly in your store() method like this:
$this->validate($req, [
'code' => 'required|unique,
//... and so on
], $this->messages);
With this, you're validating users $req by saying that specified columns are required and that they need to be unique, in order for validation to pass. In your controller, you can also create messages function to display error messages, if the condition isn't met:
private $messages = [
'code.required' => 'Code is required',
'code.unique' => 'Code already exists',
//... and so on
];
You can also achieve this by creating a new custom validation class:
php artisan make:request StorePro
The generated class will be placed in the app/Http/Requests directory. Now, you can add a few validation rules to the rules method:
public function rules()
{
return [
'code' => 'required|unique,
//... and so on
];
}
All you need to do now is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
public function store(StorePro $req)
{
// The incoming request is valid...
// Retrieve the validated input data...
$validated = $req->validated();
}
If you have any additional question about this, feel free to ask. Source: Laravel official documentation.
What does your migration look like for AssetType?
I ask because you can do this in the schema with ->unique() added to the column on the creation or make a migration to add the constraint.
You can also check with something like this:
// Search database table for entry
$entry = AssetType::where('code', '=', $pro->code)->first();
// If not found
if ($entry === null) {
// Save method here.
}
Otherwise, you can use the manual validator or create a Request with validation
References:
https://laravel.com/docs/5.8/queries#where-clauses
https://laravel.com/docs/5.8/validation#creating-form-requests
https://laravel.com/docs/5.8/validation#manually-creating-validators

Laravel Eloquent relationship object stale even though data correct in database

I'm using Laravel 5.7 and have a one-to-one relationship between 2 eloquent models.
I have this simple function that works well, and the correct values persist to the database:
public function saveMarketingOriginInfo(Contact $contact, $data) {
$contact->marketingOrigin()->create($data);
$this->makeOtherChangesByReference($contact->marketingOrigin);
$contact->marketingOrigin->save();
return $contact->marketingOrigin;
}
However, when writing a functional test for it, I noticed that the object that it returns is stale (doesn't have the correct values in its properties).
My tests only pass if I change the return statement to return \App\Models\MarketingOrigin::find($contact->id);.
(MarketingOrigin uses 'contact_id' as primary key.)
What am I doing wrong?
How can I return the same object that was just saved in the previous line ($contact->marketingOrigin->save();) without making a database read query (find())?
Update to respond to comments:
protected $table = 'marketing_origins';//MarketingOrigin class
protected $primaryKey = 'contact_id';
protected $guarded = [];
public function contact() {
return $this->belongsTo('App\Models\Contact');
}
The test:
public function testSaveMarketingOriginInfo() {
$helper = new \App\Helpers\SignupHelper();
$contactId = 92934;
$contact = factory(\App\Models\Contact::class)->create(['id' => $contactId]);
$leadMagnetType = 'LMT';
$audience = 'a60907';
$hiddenMktgFields = [
'audience' => $audience,
'leadMagnetType' => $leadMagnetType
];
$result = $helper->saveMarketingOriginInfo($contact, $hiddenMktgFields);
$this->assertEquals($result->contact_id, $contactId, 'contact_id did not get saved');
$this->assertEquals($result->campaignId, '6075626793661');
$this->assertEquals($result->leadMagnetType, $leadMagnetType);
$marketingOrigin = \App\Models\MarketingOrigin::findOrFail($contactId);
$this->assertEquals($marketingOrigin->adsetId, '6088011244061');
$this->assertEquals($marketingOrigin->audience, $audience);
$this->assertEquals($marketingOrigin, $result, 'This is the assertion that fails; some properties of the object are stale');
}
This is because the relationship has not been loaded yet.
You could try $contact->load('marketingOrigin'); to eager load the relationship:
public function saveMarketingOriginInfo(Contact $contact, $data) {
$contact->marketingOrigin()->create($data);
$this->makeOtherChangesByReference($contact->marketingOrigin);
$contact->marketingOrigin->save();
$contact->load('marketingOrigin'); // <---- eager load the relationship
return $contact->marketingOrigin;
}

laravel 5.7 how to pass request of controller to model and save

I am trying to pass $request from a function in controller to a function in model.
THis is my controller function:
PostController.php
public function store(Request $request, post $post)
{
$post->title = $request->title;
$post->description = $request->description;
$post->save();
return redirect(route('post.index'));
}
how save data in model Post.php?
I want the controller to only be in the role of sending information. Information is sent to the model. All calculations and storage are performed in the model
Thanks
You can make it even easier. Laravel has it's own helper "request()", which can be called anywhere in your code.
So, generally, you can do this:
PostController.php
public function store()
{
$post_model = new Post;
// for queries it's better to use transactions to handle errors
\DB::beginTransaction();
try {
$post_model->postStore();
\DB::commit(); // if there was no errors, your query will be executed
} catch (\Exception $e) {
\DB::rollback(); // either it won't execute any statements and rollback your database to previous state
abort(500);
}
// you don't need any if statements anymore. If you're here, it means all data has been saved successfully
return redirect(route('post.index'));
}
Post.php
public function postStore()
{
$request = request(); //save helper result to variable, so it can be reused
$this->title = $request->title;
$this->description = $request->description;
$this->save();
}
I'll show you full best practice example for update and create:
web.php
Route::post('store/post/{post?}', 'PostController#post')->name('post.store');
yourform.blade.php - can be used for update and create
<form action='{{ route('post.store', ['post' => $post->id ?? null]))'>
<!-- some inputs here -->
<!-- some inputs here -->
</form>
PostController.php
public function update(Post $post) {
// $post - if you sent null, in this variable will be 'new Post' result
// either laravel will try to find id you provided in your view, like Post::findOrFail(1). Of course, if it can't, it'll abort(404)
// then you can call your method postStore and it'll update or create for your new post.
// anyway, I'd recommend you to do next
\DB::beginTransaction();
try {
$post->fill(request()->all())->save();
\DB::commit();
} catch (\Exception $e) {
\DB::rollback();
abort(500);
}
return redirect(route('post.index'));
}
Based on description, not sure what you want exactly but assuming you want a clean controller and model . Here is one way
Model - Post
class Post {
$fillable = array(
'title', 'description'
);
}
PostController
class PostController extend Controller {
// store function normally don't get Casted Objects as `Post`
function store(\Request $request) {
$parameters = $request->all(); // get all your request data as an array
$post = \Post::create($parameters); // create method expect an array of fields mentioned in $fillable and returns a save dinstance
// OR
$post = new \Post();
$post->fill($parameters);
}
}
I hope it helps
You need to create new model simply by instantiating it:
$post = new Post; //Post is your model
then put content in record
$post->title = $request->title;
$post->description = $request->description;
and finally save it to db later:
$post->save();
To save all data in model using create method.You need to setup Mass Assignments when using create and set columns in fillable property in model.
protected $fillable = [ 'title', 'description' ];
and then call this with input
$post = Post::create([ 'parametername' => 'parametervalue' ]);
and if request has unwanted entries like token then us except on request before passing.
$post = Post::create([ $request->except(['_token']) ]);
Hope this helps.
I find to answer my question :
pass $request to my_method in model Post.php :
PostController.php:
public function store(Request $request)
{
$post_model = new Post;
$saved = $post_model->postStore($request);
//$saved = response of my_method in model
if($saved){
return redirect(route('post.index'));
}
}
and save data in the model :
Post.php
we can return instance or boolean to the controller .
I returned bool (save method response) to controller :
public function postStore($request)
{
$this->title = $request->title;
$this->description = $request->description;
$saved = $this->save();
//save method response bool
return $saved;
}
in this way, all calculations and storage are performed in the model (best way to save data in MVC)
public function store(Request $request)
{
$book = new Song();
$book->title = $request['title'];
$book->artist = $request['artist'];
$book->rating = $request['rating'];
$book->album_id = $request['album_id'];
$result= $book->save();
}

Resources