I use this model but use this model show below error:
Failed calling App\User::jsonSerialize()
but remove "$this->customer->name" result is ok.
thanksssssssssssssssssssssssssssssssssssssssssssssssssssssssssss.
class User extends Authenticatable
{
/**
* Get the user's customer name.
*
* #param string $value
* #return array
*/
public function getCustomerIdAttribute($value)
{
return [
'id' => $value,
'name' => $this->customer->name
];
}
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = [
'customer_id' => 'array',
];
/**
* Get the customer record associated with the user.
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
}
Your issue is that $this->customer is returning null, which is causing $this->customer->name to cause an error.
When you json_encode a Model, or convert it to a string, or otherwise call toJson on it, it will call the jsonSerialize() method.
At some point, this ends up calling your getCustomerIdAttribute() accessor you have defined. Inside this accessor, you have the statement $this->customer->name. However, if the current model is not related to a customer record, then $this->customer will return null, and then $this->customer->name will cause an error. When $this->customer->name causes an error, it causes jsonSerialize() to fail.
In your accessor, just make sure to check if $this->customer is valid before attempting to access the name attribute:
public function getCustomerIdAttribute($value)
{
return [
'id' => $value,
'name' => ($this->customer ? $this->customer->name : null)
];
}
Related
I am trying to validate a nested JSON object in Laravel. I have created a custom rule to do this however I have an issue currently, I want to be able to pass the object at the current array index to my custom validator:
<?php
namespace App\Http\Requests\App;
use App\Rules\CheckoutDepatureCheck;
use App\Rules\SeatIsAvailable;
use Illuminate\Foundation\Http\FormRequest;
class CheckoutRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
"company" => "required",
"seats" => "required|array",
"seats.*.seat_no" => ['required', new SeatIsAvailable()], // would like to pass seat.* to the constructor of my custom validator here
"seats.*.schedule_id" => "required|numeric",
"seats.*.date" => "required|date"
];
}
}
The point for this is my custom validator needs schedule_id and data as well as the seat_no to successfully validate the request.
How do I do this in Laravel?
You can dynamically add rules depending on the length of the seats' array input
<?php
namespace App\Http\Requests\App;
use App\Rules\CheckoutDepatureCheck;
use App\Rules\SeatIsAvailable;
use Illuminate\Foundation\Http\FormRequest;
class CheckoutRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$rules = [
'company' => 'required',
'seats' => 'required|array',
];
return array_merge($rules, $this->seatsRules());
}
private function seatsRules(): array
{
$rules = [];
foreach ((array) $this->request->get('seats') as $key => $seat) {
$rules["seats.$key.seat_no"] = ['required', new SeatIsAvailable($seat)];
$rules["seats.$key.schedule_id"] = 'required|numeric';
$rules["seats.$key.date"] = 'required|date';
}
return $rules;
}
}
tldr:
How do you dynamically get an instance of a model just by its DB table name?
What you get from the request:
ID of the model
table name of the model (it varies all the time!)
What you don't know:
Namespace of the model
Longer explanation:
I have a reporting system, that users can use to report something. For each reporting, the ID and the table name is sent.
Until now, every model was under the Namespace App\*. However, since my project is too big, I needed to split some code into Modules\*
Here is an example, how the report is saved in the database:
Example:
Request contains rules:
public function rules()
{
return [
'id' => 'required|string',
'type' => 'required|in:users,comments,offer_reviews, ......',
'reason' => 'required|string',
'meta' => 'nullable|array',
'meta.*' => 'string|max:300'
];
}
In the database, we save the data into :
id reportable_type ...
1 App\User ...
4 Modules\Review\OfferReview ...
How would you create an instance of a model dynamically, when you just know the database table name for example offer_reviews?
There is one solution that jumps to my mind, however, I'm not sure if it adds more security issues. What is if the user sends the full namespace + class name? With that, I know directly where to resolve an instance.
Have a look what I'm doing right now
(before I changed to modules)
//In my controller
class ReportController extends Controller
{
/**
* Stores the report in DB.
*/
public function store(StoreReportRequest $request)
{
$model = $request->getModel();
$model->report([
'reason' => $request->reason,
'meta' => $request->meta
], auth()->user());
return response()->json(['status' => 'Submitted'], 201);
}
}
//in StoreReportRequest
/**
* Gets the Model dynamically.
* If not found we throw an error
* #return \App\Model
*/
public function getModel()
{
return get_model($this->type)
->findOrFail(\Hashids::decode($this->id)[0]);
}
//in Helpers
/**
* Gets me the model of a table name.
* #param String $table Has to be the name of a table of Database
* #return Eloquent The model itself
*/
function get_model($table)
{
if (Schema::hasTable(strtolower($table))) {
return resolve('App\\' . Str::studly(Str::singular($table)));
}
throw new TableNotFound;
}
I don't know if there is a better solution, but here you go. My code is looking for a method with namespace when it's not found we are using App\ as the namespace.
Maybe this code helps someone :)
class StoreReportRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'id' => 'required|string',
'type' => 'required|in:mc_messages,profile_tweets,food,users,comments,offer_reviews,user_reviews',
'reason' => 'required|string',
'meta' => 'nullable|array',
'meta.*' => 'string|max:300'
];
}
/**
* Gets the Model dynamically.
* If not found we throw an error
* #return \App\Model
*/
public function getModel()
{
$namespace = $this->getNamespace();
return $this->resolveModel($namespace);
}
protected function getNamespace(): string
{
$method = $this->typeToMethod();
if (method_exists($this, $method)) {
return $this->$method();
}
return 'App\\';
}
protected function typeToMethod(): string
{
return 'get' . \Str::studly(\Str::singular($this->type)) . 'Namespace';
}
protected function resolveModel(string $namespace)
{
return get_model($this->type, $namespace)
->findOrFail(\Hashids::decode($this->id)[0]);
}
protected function getOfferReviewNamespace(): string
{
return 'Modules\Review\Entities\\';
}
protected function getUserReviewNamespace(): string
{
return 'Modules\Review\Entities\\';
}
}
Actually, I tried to create rule object which is able to validate every image type in array of images and not only enough but also, I must to show custom message in override message function in rule object.
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class ImagesArray implements Rule
{
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
return [$attribute => 'mimes:jpeg,jpg,png' ];
here i need to validate these file types.
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'The validation error message.';
here, I need to show my custom messgae.
}
}
You should use Request.
For example, create q request class: php artisan make:request MyRequest.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'image' => 'mimes:jpeg,jpg,png',
];
}
public function messages()
{
return [
'image.mimes' => 'This image is not supported.',
];
}
}
In your controller import class MyRequest and in the method use MyRequest
e.g:
public function store(MyRequest $request)
{ // your code
}
Let me know if that was helpful. Thanks!
When validating arrays or nested parameters, you should use . in your rules access a specific array index. but if you want to apply a rule to every index on that array, you can use .*.
$validator = Validator::make($request->all(), [
'image.*' => 'mimes:jpeg,jpg,png',
], [
'image.*' => 'Invalid file type.',
]);
Or if you're using Request Forms
public function rules(){
return [
'image.*' => 'mimes:jpeg,jpg,png',
];
}
public function mesages(){
return [
'image.*' => 'Invalid file type.',
];
}
For more info, see Laravel's Documentation on Validation Arrays
I want to check if a form input 'departement' is filled only if two 'villes' have the same name.
within controller this code wokds perfectly :
$rules=[ 'nom' => 'required', 'ville'=> 'required|exists:villes,nom'];
$messages = [
'depart.required' => 'Deux villes portent le même nom, preciser le
département'];
$validator = Validator::make($request->All(), $rules,$messages);
$validator->sometimes('depart_id', 'required|exists:departs,id', function
($input) {
return Ville::where('nom',$input->ville)->count()>1;
});
if ($validator->fails()) {
return redirect('admin/etab/create')
->withErrors($validator)
->withInput();
}
I put the same code in a Form Request class:
public function rules()
{
$rules=[ 'nom' => 'required', 'ville'=> 'required|exists:villes,nom'];
$messages = [
'depart.required' => 'Deux villes portent le même nom, preciser le
département',
];
$validator = Validator::make($this->All(), $rules,$messages);
$validator->sometimes('depart_id', 'required|exists:departs,id', function
($input) {
return Ville::where('nom',$input->ville)->count()>1;
});
return $validator;
}
I get "Type error: Argument 2 passed to Illuminate\Validation\Factory::make() must be of the type array, object given," I think error message is inadequate but I cannot find why this way does not work
Thanks ......
You can check out the FormRequest class in vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php and check what it does.
It contains these 2 method at the top:
/**
* Get the validator instance for the request.
*
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function getValidatorInstance()
{
$factory = $this->container->make(ValidationFactory::class);
if (method_exists($this, 'validator')) {
$validator = $this->container->call([$this, 'validator'], compact('factory'));
} else {
$validator = $this->createDefaultValidator($factory);
}
if (method_exists($this, 'withValidator')) {
$this->withValidator($validator);
}
return $validator;
}
/**
* Create the default validator instance.
*
* #param \Illuminate\Contracts\Validation\Factory $factory
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function createDefaultValidator(ValidationFactory $factory)
{
return $factory->make(
$this->validationData(), $this->container->call([$this, 'rules']),
$this->messages(), $this->attributes()
);
}
So you can basically provide a validator method in your own FormRequest class to create a custom Validator object, that method will get the ValidatorFactory as param.
In your case you would not need to do this, because you just want to append the sometimes rule to a default validator. Looking at the code above, it checks for the existence of the withValidator method, if it exists, it is called:
if (method_exists($this, 'withValidator')) {
$this->withValidator($validator);
}
You could create the FormRequest, make sure the rules, messages and authorize methods are properly used, e.g. rules and messages return arrays and authorize returns a bool.
Then create a withValidator method in which you attach the sometimes rule to the Validator.
/**
* Do foo with Validator
*
* #param \Illuminate\Contracts\Validation\Validator $validator
* #return void
*/
public function withValidator(Validator $validator)
{
$validator->sometimes('depart_id', 'required|exists:departs,id', function {
return Ville::where('nom', $this->input('ville'))->count() > 1;
});
}
This way sometimes is attached to your validator before the validation is performed.
You don't put all the validation logic in the rules method like that. Only the rule definitions go there. All you need is this:
public function rules()
{
return [
'nom' => 'required',
'ville'=> 'required|exists:villes,nom',
];
}
Laravel will handle the validation from there on out. You don't need to manually create a Validator class when using FormRequests.
Customizing the message involves creating a messages method within the class like so:
public function messages()
{
return [
'depart.required' => 'Deux villes portent le même nom, preciser le départemen'
];
}
Update
As for the sometimes rule, I'd suggest creating a Rule object and customizing how you need to check for 2 vills having the same name.
Rule classes
I want show related table columns (customers.name) in all select of model (User) laravel.
I use accessor laravel.
user table:
id name customer_id
1 hassan 1
customer table:
id name
1 customer1
now use
$user = Auth::user();
return $user;
I want show:
id: 1,
name: "hassan",
customer_id: {
id: 1,
name: "customer1"
}
but show this error:
Failed calling App\User::jsonSerialize()
class User extends Authenticatable
{
use EntrustUserTrait;
/**
* Get the user's customer name.
*
* #param string $value
* #return array
*/
public function getCustomerIdAttribute($value)
{
return [
'id' => $value,
'name' => $this->customer->name
];
}
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = [
'customer_id' => 'array',
];
/**
* Get the customer record associated with the user.
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
}
So, you want to get customer name from users table based on customer_id in users.
In User.php model
public function customer(){
return $this->hasOne(Customer::class,'id','customer_id');
}
Then,
$user = Auth::user();
$user->customer;
Seems like this is a known issue (https://github.com/FrozenNode/Laravel-Administrator/issues/879).
I would suggest, after getting your ::all() result, you loop through it and call $user->customer.
foreach($result as $user) $user->customer;
You should remove the $casts property. There is no way an integer can be converted to an array directly from the database, as the casts property is immediately used when selecting attributes from the database.
Also, there is no need for the getCustomerIdAttribute method, as the customer will be automatically be converted to a fictive attribute called 'customer'.
In short: just defining the customer relationship is enough. It should return the requested output except for it being called 'customer' instead of 'customer_id'.
I find answer.
public function getCustomerNameAttribute()
{
return $this->attributes['customer_name'] = ($this->customer ? $this->customer->name : null);
}
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = [
'customer_name',
];