Codeigniter 4 - Store validation rules in separate files - validation

I have all my rules in the Validation config file, like the documentation suggest:
https://codeigniter4.github.io/userguide/libraries/validation.html#saving-sets-of-validation-rules-to-the-config-file
For example:
public $userCreate = [
'first_name' => [
'label' => 'First Name',
'rules' => 'required|string|max_length[60]'
],
'last_name' => [
'label' => 'Last Name',
'rules' => 'required|string|max_length[60]',
],
'email' => [
'label' => 'Auth.email',
'rules' => 'required|max_length[254]|valid_email|is_unique[users.email]',
],
];
In my controllers I can access my validation groups like this:
$validation = \Config\Services::validation();
$rules = $validation->getRuleGroup('userCreate');
As my app gets bigger, I need more and more validation rules, so the question is, is there a way to organize them in separate files and not to have all of them in a single config file? Something like the custom rules, which are loaded in the config file and stored separately.

Steps
Create a custom directory for storing your validation rules. I.e app/Validation.
Create a class under that directory for your 'User' rules. I.e: app/Validation/UserRules.php
<?php
namespace App\Validation;
class UserRules
{
public function create()
{
return [
'first_name' => [
'label' => 'First Name',
'rules' => 'required|string|max_length[60]'
],
'last_name' => [
'label' => 'Last Name',
'rules' => 'required|string|max_length[60]',
],
'email' => [
'label' => 'Auth.email',
'rules' => 'required|max_length[254]|valid_email|is_unique[users.email]',
],
];
}
public function update()
{
return [
// Add 'User' update rules here.
];
}
}
In the \Config\Validation config file, set the relevant 'User' validation rules in the constructor. I.e:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
// ...
class Validation extends BaseConfig
{
// --------------------------------------------------------------------
// Setup
// --------------------------------------------------------------------
public $userCreate = [];
public $userUpdate = [];
public function __construct()
{
$this->userCreate = ($userRules = new \App\Validation\UserRules())->create();
$this->userUpdate = $userRules->update();
}
// ...
}
In your Controllers, you may access validation groups as usual.

Thanks to #steven7mwesigwa I came up with a solution that suits me the most.
First I created separate classes inside the App/Validation folder. For example these 2 classes:
App\Validation\Auth.php
<?php
namespace App\Validation;
class Auth {
public $login = [
'email' => [
'label' => 'E-mail',
'rules' => 'required|max_length[254]|valid_email',
],
'password' => [
'label' => 'Password',
'rules' => 'required',
],
'remember' => [
'label' => 'Remember me',
'rules' => 'if_exist|permit_empty|integer',
]
];
}
App\Validation\User.php
<?php
namespace App\Validation;
class User {
public $userCreate = [
'first_name' => [
'label' => 'First Name',
'rules' => 'required|string|max_length[60]',
],
'last_name' => [
'label' => 'Last Name',
'rules' => 'required|string|max_length[60]',
],
'email' => [
'label' => 'E-mail',
'rules' => 'required|max_length[254]|valid_email|is_unique[users.email]',
],
];
}
The next step is to add a construct method to the existing validation config file:
App\Config\Validation.php
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Validation extends BaseConfig {
...
// --------------------------------------------------------------------
// Rules
// --------------------------------------------------------------------
public function __construct() {
$ruleGroups = [
new \App\Validation\Auth(),
new \App\Validation\User(),
];
foreach ($ruleGroups as $ruleGroupClass) {
foreach ((array) $ruleGroupClass as $groupName => $rules) {
$this->{$groupName} = $rules;
}
}
}
}

Related

Laravel Resource and Resource Collections

I'm learning the Laravel Resource API and have setup my controller to pass the data to my Resource and my Resource Collection.
This is the list of servers (index method) with the show method showing the individual server
Controller
Index method
return new DedicatedServerResourceCollection($product->where('parent_id', 1)->with('dedicatedServers')->get());
Show Method
return new DedicatedServerResource(DedicatedServer::findOrfail($id));
I need to format my collection and resource differently. How can I get my Resource Collection to loop through each item and format the changes accordingly?
Resource Collection
return [
'productTypes' => $this->map(function($data){
return [
'id' => $data->id,
'title' => $data->title,
'tagline' => $data->tagline,
'slug' => $data->slug,
'dedicatedServers' => DedicatedServerResource::collection($this->resource)
// I need to pass 'dedicatedServers' === $this->dedicated_servers
];
})
];
Resource
return [
'id' => $this->id,
'productId' => $this->product_id,
'type' => $this->type,
'price' => $this->price,
'config' => [
'processorLine1' => $this->processor_line_1,
'processorLine2' => $this->processor_line_2,
'memory' => $this->memory,
'storageLine1' => $this->storage_line_1,
'storageLine2' => $this->storage_line_2,
'data' => $this->data,
'benchmark' => [
'benchmark' => $this->benchmark,
'benchmarkPercentage' => $this->benchmark_percentage
]
]
];
You might want to create a different resource class for the DedicatedServer model, like SecondDedicatedServerResource.
You should use either "resource file" or "$this->map(...)", not both.
Index Method
return ProductCollection::make(Product::with('dedicatedServers')->get());
ProductCollection
class ProductCollection extends ResourceCollection
{
public function toArray($request)
{
return [
'productTypes' => $this->collection,
];
}
}
ProductResource
class ProductResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'tagline' => $this->tagline,
'slug' => $this->slug,
'dedicated_servers' => $this->whenLoaded('dedicatedServers'),
];
}
}
DedicatedServerResource
class DedicatedServerResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'product_id' => $this->product_id,
'type' => $this->type,
'processor_line_1' => $this->processor_line_1,
'processor_line_2' => $this->processor_line_2,
'memory' => $this->memory,
'storage_line_1' => $this->storage_line_1,
'storage_line_2' => $this->storage_line_2,
'data' => $this->data,
'benchmark' => $this->benchmark,
'product' => ProductResource::make($this->whenLoaded('product')),
];
}
}
Note: Prefer to use $this->whenLoaded('dedicatedServers') instead of $this->dedicatedServers to avoid n+1 problem.

Laravel-Auditing is not working without any errors

I've recently installed this package and configured everything with guide but some how it's not working!
By it's not working I mean it's not adding anything to database. I really don't know what is wrong with my configs but I've checked everything with guide 3 times and everything is correct but... I don't know
config/audit.php:
<?php
return [
'enabled' => env('AUDITING_ENABLED', true),
'implementation' => OwenIt\Auditing\Models\Audit::class,
'user' => [
'morph_prefix' => 'user',
'guards' => [
'web',
'api',
],
],
'resolver' => [
'user' => OwenIt\Auditing\Resolvers\UserResolver::class,
'ip_address' => OwenIt\Auditing\Resolvers\IpAddressResolver::class,
'user_agent' => OwenIt\Auditing\Resolvers\UserAgentResolver::class,
'url' => OwenIt\Auditing\Resolvers\UrlResolver::class,
],
'events' => [
'created',
'updated',
'deleted',
'restored',
'gold_mailed' => 'goldMailed',
'invited' => 'clientInvited',
],
'strict' => false,
'timestamps' => false,
'threshold' => 0,
'driver' => 'session',
'drivers' => [
'eloquent' => [
'table' => 'audits',
'connection' => null,
],
],
'console' => true,
];
My model that I want to audit:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use OwenIt\Auditing\Contracts\Auditable;
use App\Models\Expansion;
use App\Models\Audit;
class Setting extends Model implements Auditable
{
protected $table = 'settings';
use \OwenIt\Auditing\Auditable;
protected $fillable = [
'expansion_id', 'season', 'advertiser_app', 'pvp_app', 'raid_app', 'version'
];
protected $auditInclude = [
'expansion_id', 'season', 'advertiser_app', 'pvp_app', 'raid_app', 'version'
];
public function Expansion()
{
return $this->hasOne(Expansion::class, 'id', 'expansion_id');
}
}
web.php:
Route::post('/setting' , 'Admin\SuperAdminController#saveSetting')->middleware('superadmin')->name('admin_save_setting');
Controller:
public function saveSetting(Request $request)
{
$sql = Setting::where('id', 1)->update([
'expansion_id' => $request['expansion_id'],
'season' => $request['season'],
'advertiser_app' => $request['advertiser_app'],
'pvp_app' => $request['pvp_app'],
'raid_app' => $request['raid_app'],
'version' => $request['version']
]);
if ($sql) {
toastr()->success('Settings successfully updated.');
return redirect()->back();
}
toastr()->error('Something went wrong!');
return redirect()->back();
}
I don't know what infos do you need but I think this is enough
I think my problem is with "driver" in config file , I don't know if that's correct or not
[UPDATED]
Based on the controller code you showed, it didn't work because your code is being called using Builder style, and the package only works when it is called using Eloquent style.
Documentation link
So, maybe you need to change your code to:
$setting = Setting::where('id', 1)->firstOrFail();
$setting->update([
'expansion_id' => $request['expansion_id'],
'season' => $request['season'],
'advertiser_app' => $request['advertiser_app'],
'pvp_app' => $request['pvp_app'],
'raid_app' => $request['raid_app'],
'version' => $request['version']
]);
now I have another problem -_-
this is my controller:
$sql = Raid::findOrFail($request['id']);
$sql = $sql->update($request->all());
I have a array in my table , after update value will be like this:
"{\"Plate\":0,\"Cloth\":0,\"Mail\":0,\"Leather\":0}"
but it should be:
{"Plate":"0","Cloth":"0","Mail":"0","Leather":"0"}
so I will get an error
before this , I was updating like this and it was ok:
$sql = Raid::where('id', $request['id'])->update($request->all());
and this is my mode (traders and class_traders is fields that I have problem with):
use SoftDeletes;
use \OwenIt\Auditing\Auditable;
protected $table = 'raid';
protected $dates = ['date_and_time','deleted_at'];
protected $fillable = [
'admin_id', '....
];
protected $casts = [
'bosses' => 'array',
'traders' => 'array',
'class_traders' => 'array',
'boosters' => 'array',
];

How to add fields which are not available in database?

I am not able to add field which is not available in my database
I have tried adding
$this->crud->addFields([
[
'name' => 'coupon_type',
'label' => 'Coupon For',
'type' => 'select_from_array',
'options' => [
// Options
],
'allows_null' => true,
'default' => 1,
'attributes' => [
'id' => 'coupon_type'
]
]
]);
I want to add fields in my create page.
You can do this by defining accessors for your "virtual" attributes
public function getIsAdminAttribute()
{
return $this->attributes['admin'] == 'yes';
}
and defining the appends on your model
protected $appends = ['is_admin'];
Find everything in the docs here:
https://laravel.com/docs/5.8/eloquent-serialization#appending-values-to-json

Laravel Backpack Polymorphic CRUD field

I'm wanting to be able to handle polymorphic relations with Backpack CRUD. I can't find any good explanations and I'm struggling to unpick its implementation in their other packages (e.g. PermissionManager). What I'm wanting to do is to be able to change the specialties Users & Clinicians are linked to - similar to how roles & permissions change in the Permission Manager.
I have a polymorphic n-n relationship with Users & Clinicians to Specialties. Each model CRUDTrait.
Specialty Model
public function user()
{
return $this->morphedByMany(User::class, 'model', 'model_has_specialties');
}
public function clinician()
{
return $this->morphedByMany(Clinician::class, 'model', 'model_has_specialties');
}
User Model
public function specialties()
{
return $this->morphToMany(Specialty::class, 'model', 'model_has_specialties');
}
Clinician Model
public function specialties()
{
return $this->morphMany(Specialty::class, 'model', 'model_has_specialties');
}
The pivot table is 'model_has_specialties' and contains:
$table->increments('id');
$table->timestamps();
$table->integer('model_id');
$table->string('model_type');
$table->integer('specialty_id');
$table->unique(['model_id', 'model_type', 'specialty_id']);
I have tried a number of different addField() configurations however I'm really struggling.
Example of addField() tried:
$this->crud->addField([
'label' => 'specialties',
'type' => 'select',
'morph' => true,
'name' => 'model_id',
'entity' => 'ModelHasSpecialties',
'attribute' => 'model_id',
'model' => 'App\Models\Clinician',
'pivot' => true,
]);
** Edit **
Here's the ClinicianCrudController which is the link between the clinician class & bootstrap.
class ClinicianCrudController extends CrudController
{
public function setup()
{
/*
|--------------------------------------------------------------------------
| CrudPanel Basic Information
|--------------------------------------------------------------------------
*/
$this->crud->setModel('App\Models\Clinician');
$this->crud->setRoute(config('backpack.base.route_prefix') . '/clinician');
$this->crud->setEntityNameStrings('clinician', 'clinicians');
$this->crud->setColumns(['surname', 'forename', 'title', 'specialties']);
$this->crud->addField([
'name' => 'surname',
'type' => 'text',
'label' => 'Surname'
]);
$this->crud->addField([
'name' => 'forename',
'type' => 'text',
'label' => 'Forename'
]);
$this->crud->addField([
'name' => 'title',
'type' => 'select_from_array',
'options' => [
'Dr' => 'Dr',
'Miss' => 'Miss',
'Mr' => 'Mr',
'Mrs' => 'Mrs',
'0Ms' => 'Ms',
'Prof' => 'Prof',
],
'label' => 'Title',
'allows_null' => false,
]);
$this->crud->addField([
'label' => 'specialties',
'type' => 'select',
'morph' => true,
'name' => 'model_id',
'entity' => 'ModelHasSpecialties',
'attribute' => 'model_id',
'model' => 'App\Models\Clinician',
'pivot' => true,
]);
/*
|--------------------------------------------------------------------------
| CrudPanel Configuration
|--------------------------------------------------------------------------
*/
// TODO: remove setFromDb() and manually define Fields and Columns
$this->crud->setFromDb();
// add asterisk for fields that are required in ClinicianRequest
$this->crud->setRequiredFields(StoreRequest::class, 'create');
$this->crud->setRequiredFields(UpdateRequest::class, 'edit');
}

How can I add condition in validator interface laravel?

I see from here : https://github.com/andersao/laravel-validator
For example the code like this :
use \Prettus\Validator\LaravelValidator;
class PostValidator extends LaravelValidator {
protected $rules = [
ValidatorInterface::RULE_CREATE => [
'title' => 'required',
'text' => 'min:3',
'author'=> 'required'
],
ValidatorInterface::RULE_UPDATE => [
'title' => 'required'
]
];
}
I want to add condition if user is member(if(Auth::id())) then the field author not required
So the validator to be like this :
ValidatorInterface::RULE_CREATE => [
'title' => 'required',
'text' => 'min:3'
],
The author required if the user is guest(no login)
Whether it can be done?
the Laravel documentation has a section on custom validators
Since you cannot execute instructions in a class properties, you can try to override the create method of your Repository instance, in order to modify the $rules parameter before the create actually takes place.
So in your Repository class, override the method create:
public function create(array $attributes)
{
$oldRule = $this->rules[ValidatorInterface::RULE_CREATE]['author'];
if(Auth::guest()){ // or use the preferred check
unset($this->rules[ValidatorInterface::RULE_CREATE]['author']);
}
$this->makeValidator();
$res = parent::create($attributes);
$this->rules[ValidatorInterface::RULE_CREATE]['author'] = $oldRule;
return $res;
}
EDIT
Another method could be specifying custom validation logic in your PostValidator as follows:
use \Prettus\Validator\LaravelValidator;
class PostValidator extends LaravelValidator {
const RULE_CREATE_FOR_MEMBER = 'RULE_CREATE_FOR_MEMBER';
protected $rules = [
ValidatorInterface::RULE_CREATE => [
'title' => 'required',
'text' => 'min:3',
'author'=> 'required'
],
self::RULE_CREATE_FOR_MEMBER => [
'title' => 'required',
'text' => 'min:3'
],
ValidatorInterface::RULE_UPDATE => [
'title' => 'required'
]
];
public function passes($action = null)
{
if($action == ValidatorInterface::RULE_CREATE && \Auth::id()) {
$action = self::RULE_CREATE_FOR_MEMBER;
}
return parent::passes($action);
}
}
But again, you need to override the standard behavior, it's up to you to decide which is the simplest solution for your needs.

Resources