How can I chain multiple form validation requests together? - php

In a Laravel project I'm working on, I'm wanting to create an API. In that API there will be certain JSON keys that will be required in every request. E.g. a token, or other fields that are ALWAYS required. I am familiar with Laravel's form request feature which allows you to easily create a class with a rules method containing an array of validation logic. However, I am wanting to know if there is a way where I can make one Request class that handles the "always required" fields and then bolt on another request class containing specific field validation for that endpoint.
E.g.
// MasterRequest.php
public function rules() {
return [
'api_key' => 'required|exists:users,api_key',
];
}
// ProductRequest.php
public function rules() {
return [
'product_id' => 'required|integer',
];
}
And then some way always call MasterRequest validation on EVERY api route, and then specify the type of request validation for each route's unique needs?
Is this doable, or even the correct approach?

This is fairly easy to set up, use OOP properties of PHP.
The easiest way (and obvious one):
Make yourself "master class for always required fields" you can also declare it as abstract class.
File AlwaysRequired.php
abstract class AlwaysRequired extends FormRequest
{
public function rules() {
return [
'api_key' => 'required|exists:users,api_key',
];
}
}
and ProductRequest.php
class ProductRequest extends AlwaysRequired
{
public function rules() {
return array_merge(parent::rules(),
['product_id' => 'required|integer']);
}
}
Array merge on php.net
The property way:
Make yourself master class in which you will declare property with "always required" rules and then just array_merge(array,...) it in child class (just like example above).
The "hardest" and most confusing way, but fully automatic:
You can leverage magic functions and method/property visibility of PHP language.
Again make yourself a master class in which you will have a protected property with rules array and implementation for __call() magic method.
Note: You can test code below in interactive shell of PHP $php -a and copy-paste the code.
abstract class A { // Master class
protected $rules = ['abc' => 'required'];
function __call($name, $arg) {
if(method_exists($this, 'rules')){
return array_merge($this->rules, $this->rules());
} else {
//or handle any other method here...
die(var_dump($name, $arg));
}
}
}
class B extends A { //Generic class just like ProductRequest...
protected function rules() { // function must be declared as protected! So its invisible for outside world.
return ['def' => 'required'];
}
}
$b = new B();
var_dump($b->rules());
How it works?
Laravel behind the scenes tries to run rules() method on the request class you specify (in your case ProductRequest), declaring it as protected means that it can not be called except from itself or by another child, which means that __call() method is called instead which is declared in abstract parent class. __call() method simply identifies if caller wanted to call non-existent (because of protected visibility is set) method rules() if that is so it merges the child's rules() result with $rules and returns it.
Checking for correct API key should be handled in Middleware.

You can manually execute Request classes one by one:
public function store()
{
try{
app(MasterRequest::class);
} finally {
app(ProductRequest::class);
}
/*... */
}

Related

Newbie question about controller and request in Laravel in my case

I am new in Laravel, what I try to achieve is very simple thing, I would like to use FormRequest provided by Laravel to do validation of the request, but I encounter some puzzles (which I am sure is easy things to solve if you are experienced in Laravel).
Here is what I tried:
I have route maps to controller:
Route::put('user/{name}', 'UserController#show');
I can get the name parameter in show function:
class UserController {
public function show($name)
{
// validtion rules to apply
...
}
}
I have validation rules to apply to the request, so I decided to create form request by php artisan make:request ShowRequest, which creates the form request class:
class ShowRequest extends FormRequest {
public function authorize()
{
return true;
}
public function rules()
{
return [
// my validation rules here
];
}
}
Since I have above request class, so I refactored the show function in controller to receive the ShowRequest .
class UserController {
public function show(ShowRequest $request)
{
// now I don't need validtion rules in this function
// but how can I access the 'name' parameter now
...
}
}
I have two questions to ask:
Inside the refactored show function, how can I now access the route parameter name ?
If we forget about the parameter is a name (please don't focus on what to validate for name, imaging it is an object or value to validate in general). How to add custom logic for handling validation error instead of using Laravel default behaviour. I want to inject code like dummy code below:
if (!$validator->pass())
{
//my custom code for handling validation failure
}
Where to put my custom code for handling validation error now? I mean I don't know where to have this logic, in controller? in the request class? how?
You still can add the parameter $name in the show() method of your controller as it's part of the routed url more than the validated form/data. (recommanded)
class UserController {
public function show(ShowRequest $request, $name)
{
//...
}
}
You can also access it from the request object
class UserController {
public function show(ShowRequest $request)
{
$request->input('name');
}
}
As for the error messages (not the exception) you can add the messages() method to your ShowRequest::class
class ShowRequest extends FormRequest
{
/**
* #return array
*/
public function messages()
{
return [
'name.required' => 'The name is required',
'name.numeric' => 'The name must be a number',
//...
];
}
}
If you instead need to validate that the name catched by the route is only composed of letter OR really exists as a field in your DB (like a slug of a post) you need to add some validation in your route declaration.
Setup a route that catches request only if it is composed of letters.
Route::get('/user/{name}', 'Controller#show')->where(['name' => '[a-z]+']);
Setup a route that catches request only if the "name" exists in DB:
User.php
Class User //..
{
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'name';
}
}
web.php
//
Route::get('/user/{user:name}', 'Controller#show');
And adapt your controller to take a user directly
class UserController {
public function show(ShowRequest $request, User $user)
{
//...
}
}
You can access the values of the Form Request using this
$validated = $request->validated();
The $validated will have all the values which had been validated by the FormRequest.
To answer your second question, if you want to throw custom validation, you can always use the following
throw ValidationException::withMessages(['name' => 'Something is wrong']);

In Laravel, how to give another implementation to the service container when testing?

I'm creating a Laravel controller where a Random string generator interface gets injected to one of the methods. Then in AppServiceProvider I'm registering an implementation. This works fine.
The controller uses the random string as input to save data to the database. Since it's random, I can't test it (using MakesHttpRequests) like so:
$this->post('/api/v1/do_things', ['email' => $this->email])
->seeInDatabase('things', ['email' => $this->email, 'random' => 'abc123']);
because I don't know what 'abc123' will be when using the actual random generator. So I created another implementation of the Random interface that always returns 'abc123' so I could assert against that.
Question is: how do I bind to this fake generator at testing time? I tried to do
$this->app->bind('Random', 'TestableRandom');
right before the test, but it still uses the actual generator that I register in AppServiceProvider. Any ideas? Am I on the wrong track completely regarding how to test such a thing?
Thanks!
You have a couple options:
Use a conditional to bind the implementation:
class AppServiceProvider extends ServiceProvider {
public function register() {
if($this->app->runningUnitTests()) {
$this->app->bind('Random', 'TestableRandom');
} else {
$this->app->bind('Random', 'RealRandom');
}
}
}
Second option is to use a mock in your tests
public function test_my_controller () {
// Create a mock of the Random Interface
$mock = Mockery::mock(RandomInterface::class);
// Set our expectation for the methods that should be called
// and what is supposed to be returned
$mock->shouldReceive('someMethodName')->once()->andReturn('SomeNonRandomString');
// Tell laravel to use our mock when someone tries to resolve
// an instance of our interface
$this->app->instance(RandomInterface::class, $mock);
$this->post('/api/v1/do_things', ['email' => $this->email])
->seeInDatabase('things', [
'email' => $this->email,
'random' => 'SomeNonRandomString',
]);
}
If you decide to go with the mock route. Be sure to checkout the mockery documentation:
http://docs.mockery.io/en/latest/reference/expectations.html
From laracasts
class ApiClientTest extends TestCase
{
use HttpMockTrait;
private $apiClient;
public function setUp()
{
parent::setUp();
$this->setUpHttpMock();
$this->app->bind(ApiConfigurationInterface::class, FakeApiConfiguration::class);
$this->apiClient = $this->app->make(ApiClient::class);
}
/** #test */
public function example()
{
dd($this->apiClient);
}
}
results
App\ApiClient^ {#355
-apiConfiguration: Tests\FakeApiConfiguration^ {#356}
}
https://laracasts.com/discuss/channels/code-review/laravel-58-interface-binding-while-running-tests?page=1&replyId=581880

Specify different set of rules based on which function makes the request

I wish to create a Request in a Laravel 5.1 application which has a specific set of rules based on the function that calls it.
For example, say, there are two functions in UsersController namely, login() and register(). The login() function requires only two inputs- username and password, whereas the register() function requires three inputs- username, password and email among other constraints. How can I create a single Request, call it UserRequest, that can handle the rules corresponding to both the above functions based on whichever makes the Request?
I am not sure if what I'm attempting could be done, or whether it is a good practice. Please suggest regarding the same as well.
You have a lot of flexibility since you can do whatever in rules() method, you just need a way to distinguish who is using the Request.
For this example, i would simply use the route() method(it tells you what route was called), you can do something like this:
class MyRequest extends Request {
/*
* Request rules
*/
protected $rules = [
// login rules
'login_route' => [
'login' => 'required',
],
// register rules
'register_route' => [
'login' => 'sometimes',
],
// depends if you need it
'default' => [
'login' => 'sometimes',
]
];
public function authorize()
{
return true; // or whatever
}
public function rules()
{
// where did this request come from?
$route = $this->route();
if(array_key_exists($route, $this->rules))
return $this->rules[$route];
return $this->rules['default'];
}
}
There are other ways, it depends on your problem, you could check the request method (GET, POST..) using getMethod() or check a segment, or instanciate an object on construct (dependency injection) to check if a user is logged in or not for example, really, it depends.
However, if the use case is complex, it is better to seperate in two requests.
Hope this helps.
You mean form requests. Before your method will be excecuted, the validator checks the rules. If not valid, the not-valid messages array will be returned with http status 422. See example below from https://mattstauffer.co/blog/laravel-5.0-form-requests
<?php namespace App\Http\Controllers;
use App\Http\Requests\FriendFormRequest;
use Illuminate\Routing\Controller;
use Response;
use View;
class FriendsController extends Controller
{
public function getAddFriend()
{
return view('friends.add');
}
public function postAddFriend(FriendFormRequest $request)
{
return Response::make('Friend added!');
}
}
And then your form request class:
<?php namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Response;
class FriendFormRequest extends FormRequest
{
public function rules()
{
return [
'first_name' => 'required',
'email_address' => 'required|email'
];
}
public function authorize()
{
// Only allow logged in users
// return \Auth::check();
// Allows all users in
return true;
}
// OPTIONAL OVERRIDE
public function forbiddenResponse()
{
// Optionally, send a custom response on authorize failure
// (default is to just redirect to initial page with errors)
//
// Can return a response, a view, a redirect, or whatever else
return Response::make('Permission denied foo!', 403);
}
// OPTIONAL OVERRIDE
public function response()
{
// If you want to customize what happens on a failed validation,
// override this method.
// See what it does natively here:
}
}
You can make different 'standalone' form requests and use them for your controller methods.
Official documentation here: http://laravel.com/docs/master/validation#form-request-validation

Yii Validate an un-bound Variable (non-stored)

Classic problem:
verify that a user accepted the contract terms but the value of the acceptance is not stored (bound) in the database...
Extend CFormModel rather than CActiveForm (because CActiveForm binds
values to DB)
Post a CFormModel to a controller action
Validate a CFormModel
I'm asking this question to answer it because the existing questions end in see the documentation...
extend CFormModle, define the rules and got to validate. With bound variables you validated as part of save. Now you validate() by itself but Validate requires a list of attributes which is not defined in CFormModel. So, what do you do? You do this:
$contract->validate($contract->attributeNames())
Here's the full example:
class Contract extends CFormModel
{
...
public $agree = false;
...
public function rules()
{
return array(
array('agree', 'required', 'requiredValue' => 1, 'message' => 'You must accept term to use our service'),
);
}
public function attributeLabels()
{
return array(
'agree'=>' I accept the contract terms'
);
}
}
Then in the controller you do this:
public function actionAgree(){
$contract = new Contract;
if(isset($_POST['Contract'])){
//$contract->attributes=$_POST['Contract']; //contract attributes not defined in CFormModel
...
$contract->agree = $_POST['Contract']['agree'];
...
}
if(!$contract->validate($contract->attributeNames())){
//re-render the form here and it will show up with validation errors marked!
}
The results:

Codeception\Util\Stub methods ::exactly and ::once don't work

I am using Codeception\Util\Stub to create unit tests. And I want to be sure that my method called several times. For this I am using method 'exactly'.
Example:
use \UnitTester;
use \Codeception\Util\Stub as StubUtil;
class someCest
{
public function testMyTest(UnitTester $I)
{
$stub = StubUtil::makeEmpty('myClass', [
'myMethod' => StubUtil::exactly(2, function () { return 'returnValue'; })
]);
$stub->myMethod();
}
}
As you can see I called myMethod once. But test passed.
The same problem with method ::once , because this method is using the same class PHPUnit_Framework_MockObject_Matcher_InvokedCount ('matcher' below).
Test will fail only if I will call more then expected times ( >2 ). Because matcher's method 'invoked' checks if count more then expected. But can't see if someone call matcher's method 'verify' to check if myMethod called less then expected.
Sorry stackoverflow, this is my first question.
UPDATE
My fast and BAD temporary solution:
Add stub into helper
$I->addStubToVerify($stub);
Add method into helper to validate:
protected $stubsToVerify = [];
public function verifyStubs()
{
foreach ($this->stubsToVerify as $stub) {
$stub->__phpunit_getInvocationMocker()->verify();
}
return $this;
}
Call this method in Cest's method _after():
public function _after(UnitTester $I)
{
$I->verifyStubs();
}
You need to pass $this as a third parameter to makeEmpty:
$stub = StubUtil::makeEmpty('myClass', [
'myMethod' => StubUtil::exactly(2, function () { return 'returnValue'; })
], $this);
Instead of use \Codeception\Util\Stub to Expected::once(), modify your unit tests to extends \Codeception\Test\Unit then use $this->make() or $this->makeEmpty() to create your stubs. It will works as you expect ;)
For example:
class MyProcessorTest extends \Codeception\Test\Unit
{
public function testSomething()
{
$processor = new MyProcessor(
$this->makeEmpty(EntityManagerInterface::class, [
'remove' => Expected::never(),
'persist' => Expected::once(),
'flush' => Expected::once(),
])
);
$something = $this->somethingFactory(Processor::OPERATION_CREATE);
$processor->process($something);
}
}
Cheers!
Looks like your method does not exist in the target class that you mock.
If the method exists then Codeception replaces it with the stub you provide. And if this method does not exist then Codeception adds a field with this name to the stub object.
It is because methods and properties are passed in the same array so Codeception has no other way to tell methods from properties.
So first create a method myMethod in your class myClass.

Categories