My User_model have a public method called is_unique_email($email). This method checks if a user has a uniqe mail adress with some status flag checks. This is also the reason why I can't use the standard is_unique validation rule from CodeIgniter.
I'm using a form_validation.php with config array for my validation rule groups. My question is: How can I call the model method for checking the new user's e-mail address? I searched and tried so many things, but nothing work. My preferred call would be with | pipe separator.
Like: trim|required|max_length[70]|valid_email|<~ here comes the model callback ~>
Is there any solution for this callback or is there no way and I have to extend the Form_validation system library?
I'm using CodeIgniter 3.1.7.
Thanks in advance!
UPDATE:
Because I've always done things via extending the form_validation library I forgot about this:
https://codeigniter.com/user_guide/libraries/form_validation.html#callbacks-your-own-validation-methods
and this (anonymous functions):
https://codeigniter.com/user_guide/libraries/form_validation.html#callable-use-anything-as-a-rule
Might be better for you. When in doubt, always read the docs ;)
Yes you can extend the form_validation library. In application/library make a MY_Form_validation.php and have it extend CI's as such:
class MY_Form_validation extends CI_Form_validation {
then in it you can do something like this:
/**
* Checks to see if bot sum is valid
* e.g. equals the session stored values
*
* #param int $sum
* #return boolean
*/
public function valid_bot_sum($sum) {
$generated = $this->CI->session->bot_first_number + $this->CI->session->bot_second_number;
if ($generated !== intval($sum)) {
$this->set_message('valid_bot_sum', 'Invalid bot sum.');
return false;
} else {
return true;
}
}
and now your function can be accessed via pipe separators as any native form_validation validation function. Just be sure to set the message on false as I've done, otherwise you will get an error. You can access the CI instance like so $this->CI.
In your case you can either migrate the function from the model into this file, or you can call it by loading the model in the function and calling the function and just testing to see if it evaluates to true/false and handling as above.
Related
With Codeigniter 3 it was possible to use "trim" as a validation rule.
It seems it is no more possible with Codeigniter 4.
Then how can I trim input values before validating, in case the user left whitespaces at the beginning or the end of the input?
$validation->setRule('username', 'Username', 'trim|required|min_length[3]');
I thought using a custom rule but these functions can only return true or false. They can't modify the input. The other solution is using the php trim function but I can't see where to use it.
Thanks for your help!
I'm guessing you're validating the post request directly. For what you need I would validate your modified array instead of the post request directly.
One of the great things in codeigniter 4 validation is that your can actually validate anything. Unlike codeigniter 3 where you could only use it to validate the $_POST data.
Let's say you have two fields, username and password and want to trim the username.
In you controller that would get the post date you would do the following.
$validation = \Config\Services::validation();
$validation->setRules([
'username' => 'required',
'password' => 'required|min_length[10]'
]);
$data = $this->request->getPost();
$data['username'] = trim($data['username']);
if (!$validation->run($data)) {
// handle validation errors
}
If you're doing the validation in the model, I'm not sure if the validation is run before the callbacks but its worth a try. So You would define a function in your beforeInsert callback and handle the trim there.
More about callbacks here:
https://codeigniter.com/user_guide/models/model.html#specifying-callbacks-to-run
If that does not work you can even remove the username from your validation rules in your model and then in a beforeFind and beforeUpdate function validate the username yourself and trim it.
I had the exact same question. For me, it makes sense to trim most POST variables before any validation. Even some of the common validation rules are best executed with already-trimmed values.
As #micthi stated, CodeIgniter 3 offered an easy way to trim just before validation. CodeIgniter 4 makes it much less straightforward. A custom validation rule can't modify data for us, and the model event methods such as beforeInsert and beforeUpdate also don't help us in running callbacks as they all execute after validation.
Below is a CodeIgniter 4 solution that works to allow trimming of POST variables before any validation. In brief, a filter is created and then configured to run before any controller code is executed for any POST method. It loops thru the $request object to trim the POST variables and then allows the newly-trimmed version if the $request to proceed to the controller.
Create: app/Filters/TrimFilter.php
namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class TrimFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null) {
$trimmed_post = [];
foreach($request->getPost() as $var => $val) {
$trimmed_post[$var] = trim($val);
}
$request->setGlobal('post', $trimmed_post);
}
}
Modify: app/Config/Filters.php
// Add to the use statements
use App\Filters\TrimFilter;
// Add to the $aliases array
public $aliases = [
'trim' => TrimFilter::class
];
// Add to the $methods array
public $methods = [
'post' => ['trim']
];
ANOTHER OPTION: Instead of using the filter approach, you could instead perform the trimming loop within the BaseController.php file. In this case, remember to use $this-> to reference the request while within the BaseController.
In the Struts documentation, it says:
Another common workflow stategy is to first render a page using an alternate method, like input and then have it submit back to the default execute method.
https://struts.apache.org/core-developers/action-configuration.html#post-back-default
How to do it using annotation only? It seems that only the execute() method is called.
In the documentation it's said to render a page can be used an alternate method like input. This means that when you submit a form on the page it can return back with the input result. Usually it happens automatically during validation process if the validation fails or it hasErrors. Then you can submit the form back to the default action's execute method. You don't need to specify a method in the action configuration. Also if you didn't specify the action attribute in the form tag then the same action will execute which was used to render a page.
Configuring actions you can use the same page for success result when rendering a page using GET method and input when POST method is requested.
To use annotations to configure actions mapping you can use a Convention Plugin.
Also note, to map a class method to the action you should put #Action annotation directly on this method rather than on the class.
More detailed explanation and documentation you can find here.
#Namespace("/")
public class ProductAction extends ActionSupport {
public String execute() {
return SUCCESS;
}
#Action(value="product",
results=#Result(location="/product-list.jsp")
)
public String search() {
return SUCCESS;
}
}
Notice, that the method execute is not mapped, so it will not execute. If you need that method execute you should create mapping to it. For this purpose you could place annotation on class or on method execute.
Backpack controllers do not contain Rest methods as is typical with Laravel, but use traits to implement CRUD operations, and occasionally (but not always - delete does not for example) setup methods (setupListOperation for example).
For authorization, for the rest of my app I use Gate declarations in AppServiceProvider, and declare $this->authorize() to check authorization in each of my controllers.
Where can I use authorize() to check each of the operations I implement from Backpack? I couldn't find a method that seemed appropriate to override in order to run that authorization before proceeding.
You will normally do this in your FormRequest classes, see https://backpackforlaravel.com/docs/4.1/crud-tutorial#the-request
Example:
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
use Illuminate\Foundation\Http\FormRequest;
class TagRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
// only allow updates if the user is logged in
return backpack_auth()->check();
}
}
Then you'd set the request as a validator for the given opperation:
Example
protected function setupCreateOperation()
{
$this->crud->setValidation(TagRequest::class);
// TODO: remove setFromDb() and manually define Fields
$this->crud->setFromDb();
}
NOTE: While its not clear in the documentation or generated controllers (if you use the command line generator) you can in fact set a setup method for ALL opperations:
If you look at the packages allin.com/vendor/backpack/crud/src/app/Http/Controllers/CrudController.php file, in the setupConfigurationForCurrentOperation method you'll find:
/**
* Load configurations for the current operation.
*
* Allow developers to insert default settings by creating a method
* that looks like setupOperationNameOperation (aka setupXxxOperation).
*/
protected function setupConfigurationForCurrentOperation()
{
$operationName = $this->crud->getCurrentOperation();
$setupClassName = 'setup'.Str::studly($operationName).'Operation';
//.....
/*
* THEN, run the corresponding setupXxxOperation if it exists.
*/
if (method_exists($this, $setupClassName)) {
$this->{$setupClassName}();
}
}
This means that if your controller defines a setupDeleteOperation function, it WILL be called during the setup of the delete route for your CRUD.
After making use of #Wesley Smith's answer, I discovered a one-step approach to this.
As Wesley mentions, you can create setup methods for all of the crud operations, and this works as an excellent place to pass an auth. However, it does not update the other operation's links. For example, list will still contain a link to "edit," even if it's unauthorized. You can remove these with individual lines, but there's an easier way.
Instead, you can use the Setup method to pass allow/deny methods. Here's what my setup() now appears as.
public function setup()
{
CRUD::setModel(Workshop::class);
CRUD::setRoute(config('backpack.base.route_prefix') . '/workshop');
CRUD::setEntityNameStrings('workshop', 'workshops');
if (Gate::denies('admin.workshop.list'))
$this->crud->denyAccess('list');
if (Gate::denies('admin.workshop.show'))
$this->crud->denyAccess('show');
if (Gate::denies('admin.workshop.create'))
$this->crud->denyAccess('create');
if (Gate::denies('admin.workshop.update'))
$this->crud->denyAccess('update');
if (Gate::denies('admin.workshop.delete'))
$this->crud->denyAccess('delete');
}
This will not only deny access to the methods, but update each method with the appropriate #can blade directives, meaning unauthorized methods won't appear as links.
My middleware need is to:
add an extra query param to requests made by a REST API client derived from GuzzleHttp\Command\Guzzle\GuzzleClient
I cannot do this directly when invoking APIs through the client because GuzzleClient uses an API specification and it only passes on "legal" query parameters. Therefore I must install a middleware to intercept HTTP requests after the API client prepares them.
The track I am currently on:
$apiClient->getHandlerStack()-push($myMiddleware)
The problem:
I cannot figure out the RIGHT way to assemble the functional Russian doll that $myMiddleware must be. This is an insane gazilliardth-order function scenario, and the exact right way the function should be written seems to be different from the extensively documented way of doing things when working with GuzzleHttp\Client directly. No matter what I try, I end up having wrong things passed to some layer of the matryoshka, causing an argument type error, or I end up returning something wrong from a layer, causing a type error in Guzzle code.
I made a carefully weighted decision to give up trying to understand. Please just give me a boilerplate solution for GuzzleHttp\Command\Guzzle\GuzzleClient, as opposed to GuzzleHttp\Client.
The HandlerStack that is used to handle middleware in GuzzleHttp\Command\Guzzle\GuzzleClient can either transform/validate a command before it is serialized or handle the result after it comes back. If you want to modify the command after it has been turned into a request, but before it is actually sent, then you'd use the same method of Middleware as if you weren't using GuzzleClient - create and attach middleware to the GuzzleHttp\Client instance that is passed as the first argument to GuzzleClient.
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Command\Guzzle\GuzzleClient;
use GuzzleHttp\Command\Guzzle\Description;
class MyCustomMiddleware
{
public function __invoke(callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
// ... do something with request
return $handler($request, $options);
}
}
}
$handlerStack = HandlerStack::create();
$handlerStack->push(new MyCustomMiddleware);
$config['handler'] = $handlerStack;
$apiClient = new GuzzleClient(new Client($config), new Description(...));
The boilerplate solution for GuzzleClient is the same as for GuzzleHttp\Client because regardless of using Guzzle Services or not, your request-modifying middleware needs to go on GuzzleHttp\Client.
You can also use
$handler->push(Middleware::mapRequest(function(){...});
Of sorts to manipulate the request. I'm not 100% certain this is the thing you're looking for. But I assume you can add your extra parameter to the Request in there.
private function createAuthStack()
{
$stack = HandlerStack::create();
$stack->push(Middleware::mapRequest(function (RequestInterface $request) {
return $request->withHeader('Authorization', "Bearer " . $this->accessToken);
}));
return $stack;
}
More Examples here: https://hotexamples.com/examples/guzzlehttp/Middleware/mapRequest/php-middleware-maprequest-method-examples.html
I'm used to have model and form validation together, in another framework. But I'm migrating to Laravel and trying to understand its mindset.
What's the best approach for data validation? I've seen some classes that help out on creating forms and validating requests, but isn't it unsafe to have models saving data without validating it before?
How could I integrate form (frontend), request (backend) and model validation so they all play nicely together? Or this is not done in the Laravel world at all?
As a starter in Laravel myself, I can tell a mind of a learner.
The first thing to understand is that Laravel is very very very very very abstract. It offers you thousands of solutions for a single problem. Since you're just starting out, I'm going to assume you're using Laravel 5 (5.1 to be more specific).
The $this->validate() from Controllers
You can use $this->validate() in your controllers.
class SomeController extends Controller {
public function store(Request $request){
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// This passed validation.
}
}
The Validation Facade
Inside the config/app.php you will find a aliases field that defines the Validator Facade as 'Validator' => Illuminate\Support\Facades\Validator::class. You can make validators from basically anywhere.
public function store(Request $request) {
$validator = Validator::make($request->all(), [
'email' => 'required|unique:emails|email',
]);
if ($validator->fails()) {
// Error logic
}
// Store the blog post...
}
Form Requests
Personally, I like Form Requests. They allow you to reuse validation logic defined once in any controller you feel like it. You can run in your project
php artisan make:request MyCustomRequest
That will generate a new request inside app/Http/Requests where you can write your rules inside the rules method. And then, when you want to use it, just type-hint your controller method.
Here is how to use it:
public function store(CompanyRequest $request){
// This code will only be executed if the rules inside CompanyRequest
// Are true.
}
Here is the file defining CompanyRequest.
class CompanyRequest extends Request {
/**
* 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 [
'name' => 'required|max:255',
'domain' => 'required|max:40'
];
}
}
Conclusion
There are probably a few more ways to do it. You can, for instance, use Validator::make facade from within your Eloquent models. Laravel offers multiple ways of handling basic problems. You just have to find what is best for you and major in it.
I'm not sure this is the best method or not.
But I'm using this concept and its most popular way of validating forms in most popular CMS.
As usual the form action should point to a controller method. Inside controller method you can init a validator class like below.
$validation = \Validator::make(\Input::all(), with(new UserValidation)->getRules());
if ($validation->fails()) {
return redirect()->route('your route path ')->withErrors($validation)->withInput();
}
Then in your controller use the namespace like .
use VendorName\PackageName\validations\UserValidation;
Here I put the validations in a separate folder called validations. Also notice that I'm using Package development concept in Laravel 5.x you can read more about that here.
Then in that UserValidation class you can put all the validation rules.
class UserValidation {
public function getRules() {
return [
'name' => 'required|max:200|unique:client',
'address'=>'required',
'status' => 'required',
];
}
}
This has several advantage the controller looks much neat and validation customization will be entirely separate file.
Also you can split up the route to a separate folder too.
Hope it make sense..
In the end, the best way I found to solve this matter was stitching together two Laravel extensions: Ardent, an Eloquent extension that includes validation, and a fork of Laravalid, that extends the Form with jQuery basic validation: that fork includes Ardent integration.