I'm creating a package and want hook functionality (the package should inject some extra validation rules when a user updates a field in my app).
I managed to do this using the event system. What I do is pass the $rules variable and $request into the listener, I modify the $rules variable and return it.
Would this be bad practice? What would be the recommended way of doing it?
I mean, it works. I'm just unsure if this is the best way to go about it.
Code below:
SettingsController.php (this is under App/ and where I'm validating on update)
public function update(Setting $setting, Request $request)
{
$rules = [
'package' => 'required|in:'.implode(config('app.packages'),','),
'name' => 'required|max:255|alpha_dash|not_contains:-|unique:auth_setting,name,'.$setting->id.',id,package,'.$setting->package,
'description' => '',
];
// Is this bad??
$rules = Event::fire(new SettingsWereSubmitted($request,$rules))[0];
$v = Validator::make($request->all(),$rules);
Then in my package (packages/exchange/src/Listeners) I got this listener (ValidateSettings.php):
public function handle(SettingsWereSubmitted $event)
{
if($event->request->package == 'exchange')
{
// Add rules
$rules = [
'fee' => 'required|decimal|min_amount:0|max_amount:1|max_decimal:8',
'freeze_trade' => 'required|in:1,0',
];
$event->rules['value'] = $rules[$event->request->name];
return $event->rules;
}
}
I'm looking at this piece of your code
if($event->request->package == 'exchange')
and think that you can achieve the same behaviour easier by using required_if validation rule.
$rules = [
'package' => 'required|in:'.implode(config('app.packages'),','),
'name' => 'required|max:255|alpha_dash|not_contains:-|unique:auth_setting,name,'.$setting->id.',id,package,'.$setting->package,
'description' => '',
'fee' => 'required_if:package,exchange|decimal|min_amount:0|max_amount:1|max_decimal:8',
'freeze_trade' => 'required_if:package,exchange|in:1,0',
];
ADDED:
By the way, I would suggest using Request classes to validate income requests and remove validation code from controllers because validation of request is responsibility of Request but not Controller.
It's pretty easy in Laravel. First, you create your request class in your Http\Requests folder:
class UpdateSomethingRequest extends Requst
{
public function rules()
{
return [
'package' => 'required|in:'.implode(config('app.packages'),','),
'name' => 'required|max:255|alpha_dash|not_contains:-|unique:auth_setting,name,'.$setting->id.',id,package,'.$setting->package,
'description' => '',
'fee' => 'required_if:package,exchange|decimal|min_amount:0|max_amount:1|max_decimal:8',
'freeze_trade' => 'required_if:package,exchange|in:1,0',
];
}
}
And then just remove that code from you Controller and type-hint new request class to update method like following:
public function update(Setting $setting, UpdateSomethingRequest $request)
{
// Your request is already validated here so no need to do validation again
}
Related
I am trying to cleanup my controller. I have a lot form fields so I want to use observer to insert for the other model that have relationship with the main model
I have already successfully insert the request to the database in a controller but it seems to long and heavy. See code below
function insert(Request $request){
$bankStatementName = time().'.'.request()->bankStatement->getClientOriginalExtension();
request()->bankStatement->move(public_path('bankStatement'), $bankStatementName);
$identityName = time().'.'.request()->identity->getClientOriginalExtension();
request()->identity->move(public_path('identity'), $identityName);
$passportName = time().'.'.request()->passport->getClientOriginalExtension();
request()->passport->move(public_path('passport'), $passportName);
$customer = Customer::find(Auth::user()->id);
$relations = new Customer_relationship([
'kinName' => $request->kinName,
'kinGender' => $request->kinGender,
'kinEmail' => $request->kinEmail,
'kinRelation' => $request->kinRelation,
'kinAddress' => $request->kinAddress
]);
$company = new Customer_company([
'compName' => $request->compName,
'compEmail' => $request->compEmail,
'compPhone' => $request->compPhone,
'compAddress' => $request->compAddress
]);
$bank = new Customer_bank([
'accNumber' => $request->accNumber,
'bankName' => $request->bankName,
'accName' => $request->accName
]);
$document = new Customer_document([
'identity' => $identityName,
'bankStatement' => $bankStatementName,
'passport' => $passportName
]);
$customer->relation()->save($relations);
$customer->company()->save($company);
$customer->bank()->save($bank);
$customer->document()->save($document);
Customer::where('user_id', Auth::user()->id)
->update([
'title' => $request->title,
'middlename' => isset($request->middlename) ? $request->middlename : "",
'phone' => $request->phone,
'gender' => $request->gender,
'DOB' => $request->DOB,
'marital' => $request->marital,
'residential_address' => $request->residential_address,
'city' => $request->city,
'state' => $request->state,
'lga' => $request->lga,
'nationality' => $request->nationality,
'complete_registration' => 1 ]);
}
So how can I access the form request field from Updating function from observer to do a controller cleanup
Welcome to SO!
If you want to use Observers here, you should start by reading up on https://laravel.com/docs/5.8/eloquent#observers and https://laravel.com/docs/5.8/queues
This will likely work if you have all the data needed on your parent model, since you would just pass that model into the job that was triggered by the observer. If not, then observer/job might not be the best solution in your case. Instead I would probably create some sort of service, where you move the responsibility for creating these relationships. That way you can keep a clean controller level that only calls a service to create the models and then returns the result.
An example of this could be:
namespace App\Http\Controllers;
use App\Models\Something\SomeService;
class SomeController extends Controller
{
/**
* #var SomeService
*/
private $someService;
public function __construct(SomeService $someService)
{
$this->someService = $someService;
}
public function store()
{
$request = request();
$name = $request->input('name');
$something = $this->someService->create($name);
return response()->json(['data' => $something]);
}
}
namespace App\Models\Something;
class SomeService
{
public function create(string $name): Something
{
// Do whatever in here...
}
}
This is a simplified example of how I would do it. Hope it helps you a bit.
If you still want to use a job to take care of this, then I still don't think an observer is the right solution for you, as those are triggered on model events, such as created. This mean that you will not have access to the request object at that time, but only was was created (The model). Instead you could dispatch a job directly from the controller/service. That is all described in the queue link I posted at the top of the answer.
Lets say I have the following Custom Request:
class PlanRequest extends FormRequest
{
// ...
public function rules()
{
return
[
'name' => 'required|string|min:3|max:191',
'monthly_fee' => 'required|numeric|min:0',
'transaction_fee' => 'required|numeric|min:0',
'processing_fee' => 'required|numeric|min:0|max:100',
'annual_fee' => 'required|numeric|min:0',
'setup_fee' => 'required|numeric|min:0',
'organization_id' => 'exists:organizations,id',
];
}
}
When I access it from the controller, if I do $request->all(), it gives me ALL the data, including extra garbage data that isn't meant to be passed.
public function store(PlanRequest $request)
{
dd($request->all());
// This returns
[
'name' => 'value',
'monthly_fee' => '1.23',
'transaction_fee' => '1.23',
'processing_fee' => '1.23',
'annual_fee' => '1.23',
'setup_fee' => '1.23',
'organization_id' => null,
'foo' => 'bar', // This is not supposed to show up
];
}
How do I get ONLY the validated data without manually doing $request->only('name','monthly_fee', etc...)?
$request->validated() will return only the validated data.
Example:
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
$validatedData = $request->validated();
}
Alternate Solution:
$request->validate([rules...]) returns the only validated data if the validation passes.
Example:
public function store(Request $request)
{
$validatedData = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
}
OK... After I spent the time to type this question out, I figured I'd check the laravel "API" documentation: https://laravel.com/api/5.5/Illuminate/Foundation/Http/FormRequest.html
Looks like I can use $request->validated(). Wish they would say this in the Validation documentation. It makes my controller actions look pretty slick:
public function store(PlanRequest $request)
{
return response()->json(['plan' => Plan::create($request->validated())]);
}
This may be an old thread and some people might have used the Validator class instead of using the validator() helper function for request.
To those who fell under the latter category, you can use the validated() function to retrieve the array of validated values from request.
$validator = Validator::make($req->all(), [
// VALIDATION RULES
], [
// VALIDATION MESSAGE
]);
dd($validator->validated());
This returns an array of all the values that passed the validation.
This only starts appearing in the docs since Laravel 5.6 but it might work up to Laravel 5.2
In my PageTemplates.php I have a field like this:
$this->crud->addField([
'name' => 'adres',
'label' => 'Adres',
'type' => 'address',
'fake' => true,
]);
Now I would like to save also the latitude and longitude of the address they give in (if it can be found). I've copied the PageCrudController and changed the config in config/backpack/pagemanager.php to:
return [
'admin_controller_class' => 'App\Http\Controllers\Admin\PageCrudController',
'page_model_class' => 'App\Models\Page',
];
In my store function I have:
public function store(StoreRequest $request)
{
$address = $request->request->get('adres');
$addressObj = app('geocoder')->geocode($address)->get()->first();
if($addressObj)
{
}
$this->addDefaultPageFields(\Request::input('template'));
$this->useTemplate(\Request::input('template'));
return parent::storeCrud();
}
But what do I place in the if statement? How can I add (= set) an extra field to the extras field in my database?
In backpack 4.1, I solved my issue by the following way :
Override the store method in my controller, set my extra field in request and then call the backpack store method
Don't forget to add include backpack trait
Hope the solution will help someone
use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation { store as traitStore; }
public function store()
{
$this->crud->setOperationSetting('saveAllInputsExcept', ['save_action']);
$this->crud->getRequest()->request->add(['updated_by' => backpack_user()->id]);
return $this->traitStore();
}
Fixed it by doing the following:
Add latitude and longitude as hidden fields:
$this->crud->addField([
'name' => 'latitude',
'type' => 'hidden',
'fake' => true,
]);
$this->crud->addField([
'name' => 'longitude',
'type' => 'hidden',
'fake' => true,
]);
Set attributes by doing the following:
if($addressObj)
{
$request['latitude'] = $addressObj->getCoordinates()->getLatitude();
$request['longitude'] = $addressObj->getCoordinates()->getLongitude();
}
}
Change parent::updateCrud to parent::updateCrud($request);.
For people still looking at this issue, I'd recommend you follow the advice in the note under the Callbacks section of Laravel Backpack's docs if you don't just want to observe changes made from the Backpack admin panel, you just need to create an Observable.
To do this you can do the following:
Create an Observer class: php artisan make:observer YourObserver --model=YourModel
Add your code to the generated event methods you wish to observe.
Register the Observer by calling the observe method on the model you wish to observe in your EventServiceProvider's boot method like so:
public function boot()
{
YourModel::observe(YourObserver::class);
}
Or equally you can register the Observer to the $observers property of your applications' EventServiceProvider class:
protected $observers = [
YourModel::class => [YourObserver::class],
];
How can i make a custom validation rule to an input which value must be an integer and starting with 120?
I already read about making custom messages but didnt understand about rules.
I want to use a regex to validate the data. ^120\d{11}$ here is my regex.
I'm new in Laravel that's why cant now imagine how to do that.
A custom validation to use it in $this->validate($request, []);
Now i'm validating data like so:
$this->validate($request, [
'user_id' => 'integer|required',
'buy_date' => 'date',
'guarantee' => 'required|unique:client_devices|number',
'sn_imei' => 'required|unique:client_devices',
'type_id' => 'integer|required',
'brand_id' => 'integer|required',
'model' => 'required'
]);
The input that i want to add custom validation is guarantee
The quickest neatest way is an inline validator in your controller action:
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'number' => [
'regex' => '/^120\d{11}$/'
],
]);
if ($validator->fails()) {
return redirect('post/create')
->withErrors($validator)
->withInput();
}
return view('welcome');
}
Where number is the name of the field being submitted in the request.
If you have a lot of validation to do, you might want to consider using a Form Request instead, as a way of consolidating a lot of validation logic.
You can create custom validations in your controller as:
$name = Input::get('field-name')
$infoValidation = Validator::make(
array( // Input array
'name' => $name,
),
array( // rules array
'name' => array("regex:/^120\d{11}$"),
),
array( // Custom messages array
'name.regex' => 'Your message here',
)
); // End of validation
$error = array();
if ($infoValidation->fails())
{
$errors = $infoValidation->errors()->toArray();
if(count($errors) > 0)
{
if(isset($errors['name'])){
$response['errCode'] = 1;
$response['errMsg'] = $errors['name'][0];
}
}
return response()->json(['errMsg'=>$response['errMsg'],'errCode'=>$response['errCode']]);
}
Hope this helps.
Since Laravel 5.5, you can make the validation directly on the request object.
public function store(Request $request)
{
$request->validate([
'guarantee' => 'regex:/^120\d{11}$/'
]);
}
In laravel 4, I used the sometimes() method as below:
$validator = \Validator::make(
\Input::all(),
array(
'name' => array('required'),
'recurrence' => array('required_if:recurring,on'),
)
);
$validator->sometimes('recurrence', 'integer|min:1', function($input) {
return $input->recurring == 'on';
});
Notice integer|min:1 are applied to recurring only if recurrence is presented.
In laravel 5, I tried to implement the validation as a request class:
class CreateProductRequest extends Request {
public function authorize(){
return true;
}
public function rules(){
return [
'name' => array('required'),
'recurrence' => array('required_if:recurring,on'),
];
}
}
Looks like from a request class I am unable to call sometimes() method. The idea is to avoid validation code at controller.
Ok, I have emulated the behaviour expected using a custom condition without be 100% sure weather is the best practice:
$rules = [
'name' => array('required'),
'recurrence' => array('required_if:recurring,on'),
];
if ($this->has('recurring')){
$rules['recurrence'] = $rules['recurrence'] + ['integer', 'min:1'];
}
return $rules;