Laravel - limit FormRequest to certain parameters - php

In a form request class I use a method like this to validate input data.
class SignupRequest extends FormRequest
{
...
public function rules()
{
return [
'user.email' => 'required_with:user|email',
'user.domain_name' => 'required_with:user|string',
'user.password' => 'required_with:user|string|min:8',
'user.username' => 'required_with:user',
];
}
...
}
Later in a controller I use something like this
$data = $request->get('user', []);
return $this->response($this->userService->create($data, false), 201);
I want somehow to write to my SignupRequest which fields it should allow to be passed. So when later I get $data = $request->get('user', []); I'm sure there are only allowed fields in it.
Is this possible inside the FormRequest?
P.S. I'm aware of $request->only(['field1', 'field2', 'field3']) way, but if I want to limit the fields in SignupRequest extends FormRequest. Because if I use $request->only([...]) in my code several times, I would have to change it several times later. I want to keep it in one place.

You wouldn't need to do this with the request.
One option would be to do something like:
$user = $request->input('user', []);
$data = array_only($user, ['email', 'domain_name', 'password', 'username']);
Or you could even inline it:
$data = array_only($request->input('user', []), ['email', 'domain_name', 'password', 'username']);
Hope this helps!

FormRequest is meant to validate your request data, not control them. You could always extract the inputs you need by doing so.
$data = $request->only(['user.name', 'user.password']);
Edit : Based on your comment, you can do something like this. This allows you to store all the field names within a single request to keep them organised and easier to update.
Add this to your SignupRequest
public function loginData()
{
return array_only($this->input('user', []), ['username', 'password']);
}
Use it in the controller like so
$request->loginData();
return $this->response($this->userService->create($request->loginData(), false), 201);

Related

How to avoid duplication in Laravel validation rules

For validating form validation rules I currently stored them in User Model and use it in Register Controller, User controller in admin panel, User Controller in APIs and some other places, but currently it's very hard to maintain because each controller needs a slightly different set of rules and when I change the rules in User Model other controllers will not work anymore. So how to avoid duplication in rules and still keep the code maintainable?
Approach I often use is to write a HasRules trait for my models, it looks something like this:
trait HasRules
{
public static function getValidationRules(): array
{
if (! property_exists(static::class, 'rules')) {
return [];
}
if (func_num_args() === 0) {
return static::$rules;
}
if (func_num_args() === 1 && is_string(func_get_arg(0))) {
return array_get(static::$rules, func_get_arg(0), []);
}
$attributes = func_num_args() === 1 && is_array(func_get_arg(0))
? func_get_arg(0)
: func_get_args();
return array_only(static::$rules, $attributes);
}
}
Looks messy, but what it does is allows you to retrieve your rules (from a static field if such exists) in a variety of ways. So in your model you can:
class User extends Model
{
use HasRules;
public static $rules = [
'name' => ['required'],
'age' => ['min:16']
];
...
}
Then in your validation (for example, in your FormRequest's rules() method or in your controllers when preparing rules array) you can call this getValidationRules() in variety of ways:
$allRules = User::getValidationRules(); // if called with no parameters all rules will be returned.
$onlySomeRules = [
'controller_specific_field' => ['required'],
'name' => User::getValidationRules('name'); // if called with one string parameter only rules for that attribute will be returned.
];
$multipleSomeRules = User::getValidationRules('name', 'age'); // will return array of rules for specified attributes.
// You can also call it with array as first parameter:
$multipleSomeRules2 = User::getValidationRules(['name', 'age']);
Don't be afraid to write some code for generating your custom controller specific rules. Use array_merge and other helpers, implement your own (for example, a helper that adds 'required' value to array if it's not there or removes it etc). I strongly encourage you to use FormRequest classes to encapsulate that logic though.
You can try using laravel's validation laravel documentation
it is really easy to use and maintain just follow these steps:
run artisan command: php artisan make:request StoreYourModelName
which will create a file in App/Http/Requests
in the authorize function set it to:
public function authorize()
{
return true;
}
then write your validation logic in the rules function:
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}
Custom error messages add this below your rules function:
public function messages()
{
return [
'title.required' => 'A title is required',
'body.required' => 'A message is required',
];
}
Lastly to use this in your controller just add it as a parameter in your function.
public function create(Request $request, StoreYourModelName $storeYourModelName)
{
//
}
and that's all you need to do this will validate on form submission if validation passes it will go to your controller, keep in mind your validation logic does not have to be like mine thought i would show you one way that it can be done..

Laravel Spark, Swap/Interact, and Private Variables

Using Laravel Spark, if I wanted to swap in a new implementation for the configureTeamForNewUser, at first it looks like it's possible because of the Spark::interact call here
#File: spark/src/Interactions/Auth/Register.php
Spark::interact(self::class.'#configureTeamForNewUser', [$request, $user]);
i.e. the framework calls configureTeamForNewUser using Spark::interact, which means I can Spark::swap it.
However, if I look at the configureTemForNewUser method itself
#File: spark/src/Interactions/Auth/Register.php
public function configureTeamForNewUser(RegisterRequest $request, $user)
{
if ($invitation = $request->invitation()) {
Spark::interact(AddTeamMember::class, [$invitation->team, $user]);
self::$team = $invitation->team;
$invitation->delete();
} elseif (Spark::onlyTeamPlans()) {
self::$team = Spark::interact(CreateTeam::class, [
$user, ['name' => $request->team, 'slug' => $request->team_slug]
]);
}
$user->currentTeam();
}
This method assigns a value to the private $team class property. It's my understanding that if I use Spark::swap my callback is called instead of the original method. Initial tests confirm this. However, since my callback can't set $team, this means my callback would change the behavior of the system in a way that's going to break other spark functionality.
Is the above a correct understanding of the system? Or am I missing something, and it would be possible to swap in another function call (somehow calling the original configureTeamForNewUser)?
Of course, you can swap this configureTeamForNewUser method. Spark create a team for a user at the registration. You have to add the swap method inside the Booted() method of App/Providers/SparkServiceProvider.php class.
in the top use following,
use Laravel\Spark\Contracts\Interactions\Auth\Register;
use Laravel\Spark\Contracts\Http\Requests\Auth\RegisterRequest;
use Laravel\Spark\Contracts\Interactions\Settings\Teams\CreateTeam;
use Laravel\Spark\Contracts\Interactions\Settings\Teams\AddTeamMember;
In my case I want to add new field call "custom_one" to the teams table. Inside the booted() method, swap the method as bellow.
Spark::swap('Register#configureTeamForNewUser', function(RegisterRequest $request, $user){
if ($invitation = $request->invitation()) {
Spark::interact(AddTeamMember::class, [$invitation->team, $user]);
self::$team = $invitation->team;
$invitation->delete();
} elseif (Spark::onlyTeamPlans()) {
self::$team = Spark::interact(CreateTeam::class, [ $user,
[
'name' => $request->team,
'slug' => $request->team_slug,
'custom_one' => $request->custom_one,
] ]);
}
$user->currentTeam();
});
In order to add a new custom_one field, I had to swap the TeamRepository#createmethod as well. After swapping configureTeamForNewUser method, swap the TeamRepository#create method onside the booted(),
Spark::swap('TeamRepository#create', function ($user, $data) {
$attributes = [
'owner_id' => $user->id,
'name' => $data['name'],
'custom_one' => $data['custom_one'],
'trial_ends_at' => Carbon::now()->addDays(Spark::teamTrialDays()),
];
if (Spark::teamsIdentifiedByPath()) {
$attributes['slug'] = $data['slug'];
}
return Spark::team()->forceCreate($attributes);
});
Then proceed with your registration.
See Laravel Spark documentation

How can I get the list of required parameters of a yii 2.0 action as an array?

When a user visits a particular url in my yii 2.0 application without required parameters, I want to present a form to collect the required missing parameters.
for this purpose, I need the names of missing parameters, e.g. I have a function
public function actionBlast ($bomb, $building) {
}
I expect the results as an array like this
$args = [0=>'bomb', 1=>'building'];
I tried func_get_args() but it returns null, and the undocumented ReflectionFunctionAbstract::getParameters ( void ) etc. Any other way out?
I think the best way to achieve what you want is to override the default ErrorAction.
Inside your controllers directory, create:
controllers
actions
ErrorAction.php
In ErrorAction.php, add:
<?php
namespace frontend\controllers\actions;
use Yii;
use yii\web\ErrorAction as DefaultErrorAction;
class ErrorAction extends DefaultErrorAction
{
public function run()
{
$missing_msg = 'Missing required parameters:';
$exception = Yii::$app->getErrorHandler()->exception;
if (substr($exception->getMessage(), 0, strlen($missing_msg)) === $missing_msg) {
$parameters = explode(',', substr($exception->getMessage(), strlen($missing_msg)));
return $this->controller->render('missing_params_form' ?: $this->id, [
'parameters' => $parameters,
]);
}
return parent::run();
}
}
In your controller add:
public function actions()
{
return [
'error' => [
'class' => 'frontend\controllers\actions\ErrorAction',
],
];
}
and create a view "missing_params_form.php" in your controller `s view directory, where you can generate your form fields.
I believe this to be your best option, though you may need to update it in case a Yii update changes the error message.

Handle form submission Laravel 5

I just start learning Laravel 5, and I want to know what the proper way to handle submitted forms. I found many tutorials where we create two separate actions, where first render form, and the second actually handle form. I am came from Symfony2, where we create a single action for both, render and handle submitted form, so I want to know I need to create two separate actions because thats Laravel-way, or I can place all logic into single action, I do this like the folowing, but I dont like code what I get:
public function create(Request $request)
{
if (Input::get('title') !== null) {
$v = Validator::make($request->all(), [
'title' => 'required|unique:posts',
'content' => 'required',
]);
if ($v->fails()) {
return redirect()->back()->withErrors($v->errors());
}
$post = new Post(Input::all());
if ($post->save()) {
return redirect('posts');
}
}
return view('add_post');
}
So can somebody give me advice how I need do this properly? Thanks!
One of the most important reason to create two actions is to avoid duplicate form submissions . You can read more about Post/Redirect/Get pattern.
Another important reason is the way you keep the code cleaner. Take a look at this first change:
public function showForm(){
return view('add_post');
}
public function create(Request $request)
{
$v = Validator::make($request->all(), [
'title' => 'required|unique:posts',
'content' => 'required',
]);
if ($v->fails()) {
return redirect()->back()->withErrors($v->errors());
}
$post = new Post(Input::all());
if ($post->save()) {
return redirect('posts');
}
return redirect()->route('show_form')->withMessage();
}
The first thing that you can notice is that create() function is not rendering any view, it is used to manage the creation logic (as the name itself suggests). That is OK if you plan to stay in low-profile, but what happens when you do need to add some others validations or even better, re-utilize the code in other controllers. For example, your form is a help tool to publish a comment and you want to allow only "authors-ranked" users to comment. This consideration can be manage more easily separating the code in specific actions instead making an if-if-if-if spaghetti. Again...
public function showForm(){
return view('add_post');
}
public function create(PublishPostRequest $request)
{
$post = new Post($request->all());
$post->save()
return redirect('posts');
}
Take a look on how PublishPostRequest request takes place in the appropriated function. Finally, in order to get the best of Laravel 5 you could create a request class to keep all the code related with validation and authorization inside it:
class PublishPostRequest extends Request{
public function rules(){
return [
'title' => 'required|unique:posts',
'content' => 'required',
]
}
public function authorize(){
$allowedToPost = \Auth::user()->isAuthor();
// if the user is not an author he can't post
return $allowedToPost;
}
}
One nice thing about custom request class class is that once is injected in the controller via function parameter, it runs automatically, so you do not need to worry about $v->fails()

Laravel validation service with unique fields

I'm using a validation service to validate user submitted form input (something along the lines of: http://laravel.io/bin/vrk).
Using this approach (validation service classes) to validate user submitted form data against a set of rules, how can I validate user submitted data when rules have a unique rule. For example, if a user has the username of John then when I try to update the model validation fails (because John exists as a username, even though it belongs to the current model).
To solve this in Laravel I can do something like 'username' => 'required|alpha_dash|unique:users,username'.$id. How should I modify my current code, in the link, to best accommodate this? Should I have separate validator classes depending on the scenario (for example, UserCreateValidator, UserUpdateValidator, etc). Or should I do something like create separate validation rules in UserValidator class and pass which rule I want as an argument to either the constructor or the passes() method when calling UserValidator?
I think you could do something like this
First update UserValidator rules like this.
class UserValidator extends Validator {
// Override parent class $rules
protected $rules = [
'default' => [
'username' => 'required|alpha_dash|unique:users',
'password' => 'required|between:6,16|confirmed',
'password_confirmation' => 'required|between:6,16'
],
'update' => [
'username' => null,
]
];
}
Then modify Validator's passes method like this
public function passes($rule = null) {
$rules = $this->rules['default'];
if ($rule && isset($this->rules[$rule])) {
$rules = array_merge($rules, $this->rules[$rule]);
}
$validator = \Validator::make($input, $rules);
if ($validator->fails()) {
$this->validator = $validator;
return false;
}
return true;
}
Then in your controller's PUT method, this will merge update rules to default rules
$rule = 'update';
// user has changed his username
if ($input['username'] !== $old_username) {
$rule = 'create'; // validate uniqueness
}
else {
unset($input['username']); // remove it, we don't validate it anymore since it's the same
}
$validator->passes($rule); // override 'default' rules with 'update' rules
You don't have to change your controller's POST method, it'll stay the same
$validator->passes(); // use 'default' rules
If I'm understanding right, you have issues updateng data because of primary key constraints on your model. What you need to do is to create 2 sets of rules, one for insert, and one for update.
Asuming you have a set of rules like this:
protected $rules = [
'id' => 'required|unique:users'
]
You should implement something like this:
protected $rules = [
'id' => 'required|unique|unique:users,id,' . $this->id
];
This should tell laravel to ignore the duplicate id in the table users for the specified id, in this case, the id for the current object.
You can read more about this on laravel's documentation at http://laravel.com/docs/validation
unique:table,column,except,idColumn
The field under validation must be unique on a given database table.
If the column option is not specified, the field name will be used.
Well, what are you doing on post?
Because this is what you should be doing:
$user = User::find($userId);
$user->username = $input['username'];
$user->email = $input['email'];
$user->save();
To update a record.
Or
$input = array('username' => 'w0rldart', 'email' => 'hahafu#dumbledore.com');
// Retrieve the user by the attributes, or create it if it doesn't exist,
// based on the data above, which can come from an Input::all();
$user = User::firstOrCreate($input);
... many possibilities. But you could also do:
$input = array_forget($input, 'username');
To comply with your case, by removing the username index from the input array.
This is all I call tell you, based on the information you gave us. If you want more, post the controller's put method.
Update:
Here's my version of your PUT method: http://laravel.io/bin/OaX
I really think that try catch syntax is useless, since it's obvious that a User model will always be there. But I still don't know what you're trying to update. Even though I can't test it right now, I don't think that updating should be giving that problem, and if it does, retrieve user by username/id then unset the username index in your input array, and update it according to your specifications.
A little modification in UserValidator class
class UserValidator extends Validator {
// Override parent class $rules
protected $rules = [
'username' => 'required|alpha_dash|unique:users',
'password' => 'required|between:6,16|confirmed',
'password_confirmation' => 'required|between:6,16'
];
// ADD THIS
public function __construct(Array $rules = array())
{
parent::__construct();
if(count($rules)){
foreach($rules as $k => $v) $this->rules[$k] = $v;
}
}
}
In your controller putUpdate method
$user = User::whereUsername($username)->firstOrFail();
$rules = ['username' => 'required|alpha_dash|unique:users,username,'. $user->id];
// Pass the rule to update the rule for username in this method
$validator = \Services\Validators\UserValidator(Input::all(), $rules);
Check the manual here.

Categories