How to validate params comming from GET request in Yii2 RESTful service - json

I have a language parameter that needs to be sent to my documents endpoint. So I have to validate that user has sent this parameter in his GET request.
Making rule in my model didn't do anything:
public function rules()
{
return [
[['language'], 'required'],
];
}
Because of that I have tried this:
1) I have created ParamsValidator class:
<?php
namespace app\modules\v1\components;
use yii\web\UnprocessableEntityHttpException;
use yii\base\Component;
use Yii;
/**
* Class that is responsible for validating input params.
*/
class ParamsValidator extends Component
{
public function validate($params)
{
if (!isset($params['language'])) {
throw new UnprocessableEntityHttpException("Language parameter is required");
}
}
}
I am invoking its validate() method inside my controllers init() method:
public function init()
{
$this->_params = Yii::$app->request->queryParams;
$validator = new ParamsValidator();
$validator->validate($this->_params);
}
And this sort of work. Code works, but I get ugly response back. Instead of nice JSON response, I get bunch of html starting like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Unprocessable entity (#422)</title>
<style>
body {
font: normal 9pt "Verdana";
color: #000;
background: #fff;
}
Instead of this html, I would like some nice JSON response like this:
{
"name": "Forbidden",
"message": "You are not authorized to do this.",
"code": 0,
"status": 403,
"type": "yii\\web\\ForbiddenHttpException"
}
This nice JSON error that you see is made by:
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
'auth' => [$this, 'authenticate']
];
But obviously my Validator is not doing this.
Questions:
1) How to validate params that comes via GET request ?
2) If my method is right, how to get this nice JSON error response ?

A simple option is by overriding the ActiveController::checkAccess method by adding this inside controller:
public function checkAccess($action, $model = null, $params = [])
{
if ($action === 'index' or $action === 'view')
{
$params = Yii::$app->request->queryParams;
if (!isset($params['language'])) {
throw new \yii\web\ForbiddenHttpException('You are not authorized to do this.');
}
}
}
In case you need to do it at model level you'll need to use the addError() method instead of directly throwing the error. Your model instance will hold it when invoking its validate method. Then inside your action you can simply return it. it will be serialized and errors will be outputted in the right format. There is many ways to achieve this. A simple example may be by using the load method to pass both query and body params to the model before validating it (scenarios may be required):
$queryParams = Yii::$app->request->queryParams;
$bodyParams = Yii::$app->request->bodyParams;
$params = array_merge($queryParams,$bodyParams );
$model = new $modelClass;
$model->load($params , '');
if ($model->validate() === false) return $model;
else {
// do whatever you need
}

Related

Attach header to all response()->json()->send() laravel;

For some reasons I send JSON response from my helper/library instead of controller using below code:
response()->json(
[
"status" => false,
"message" => "This product is out of stock",
],
200
)->send();
exit;
my problem is no middleware header if that response sent. How to attach header to all response()->json()->send();exit; function?
Below us my response header of default controller ):
Above response has all header from all middleware and below is my header response from response()->json()->send();exit;:
above not showing headers from the middleware.
I know I can send the header manually by add ->header('X-Header-One', 'Header Value') like code below:
response()->json(
[
'status' => false,
'message' => 'This voucher is not for selected products',
]
)->header('X-Header-One', 'Header Value')->send();
exit;
But I already have so many that responses, and I dont want to try to WETing my code.
After doing some digging, you could also create a Response Macro
https://laravel.com/docs/8.x/responses#response-macros
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
class ResponseMacroServiceProvider extends ServiceProvider
{
/**
* Register the application's response macros.
*
* #return void
*/
public function boot()
{
Response::macro('custom', function ($value) {
return Response::json($value)->headers();
});
}
}
Then in your code, just use
return response()->custom($data);
In your Controller.php
Add a function and call it whatever you want
EG
public function MyCustomResponse()
{
...
}
Then allow that to take in the params you want, in your case it is an array and an int, (data and status)
public function MyCustomResponse(array $data, int $status)
{
...
}
Then handle the logic in there
public function MyCustomResponse(array $data, int $status)
{
response()->json($data, $status)->header('X-Header-One', 'Header Value')->send();
}
Now when you want to use it, ensure that you are extending the controller where you have placed this code and just do
return $this->myCustomResponse($data, 200);
A better option depending on your need is to use a middleware
public function handle($request, Closure $next)
{
$request->headers->set( ... );
return $next($request);
}
And apply to your route
Create a middleware SetHeader.php
then
<?php
namespace App\Http\Middleware;
use Closure;
class setHeader
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$request->headers->set('X-Header-One', 'Header Value');
return $next($request);
}
}
like that you can add as may as headers and apply to those routes which you want to send
Well, the answer to this question is already given, But I suggest using the power of the middleware concept. Middleware is not just working for requests but also works for the response.
By using the response macro we have to change the reference to use a custom function instead of json.
Here is the middleware code.
SetResponseHeaders.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class SetResponseHeaders
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$response->header('X-Header-One', 'XValue');
return $response;
}
}
and don't forget to register the middleware.
Http/Kernel.php
protected $middleware = [
// other middlewares
\App\Http\Middleware\SetResponseHeaders::class,
]
Special notes:- If you are using the CORS concept then you have the cors.php file under the config folder. In my case, I am using fruitcake/laravel-cors package. so you have to expose the header otherwise you will not get the value.
cors.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
// other config
'exposed_headers' => ['X-Header-One'],
]

Using the same action controller to render and save in yii2

the controllers actions of my yii2 application render and validate/save http input data. The first time (when the route was requested) it renders the form but with errors as follows:
I need to render form in the first time without error label. Is there way to solve this using the same action to render and save ?
This is my code:
public function actionCreate()
{
$model = new CourseForm;
$model->attributes = Yii::$app->request->post('CourseForm');
if ($model->validate()) {
// save...
} else {
return $this->render( 'create', [ 'model' => $model, 'errors' => $model->errors ]);
}
}
Because you are loading the attribute and calling the validate() every time the action is called and when the validation fails the view loads with the errors highlighted. So that pretty much makes sense why is it doing like that.
Secondly you can just use $model->load() and supply the post() array to it it will load the attributes automatically.
Your code can be reduced to the following and it will not show the error labels when the page loads untill unless you submit the form
public function actionCreate()
{
$model = new CourseForm();
if($model->load(Yii::$app->request->post()) && $model->validate()){
// save...
}
return $this->render('create', ['model' => $model, 'errors' => $model->errors]);
}

FOSRest validation form

What's the best way to obtain this response to invalid form?
Response example of an invalid form
Actually I have this action
public function postUserAction(Request $request)
{
...
$form->handleRequest($request);
if ($form->isValid()) {
...
return $this->handleView($view);
}
$errors = $form->getErrors(true);
$view = $this->view($errors);
return $this->handleView($view);
}
But the response is the next json object:
{ form: Object, errors: Array }
I work with JMSSerializerBundle. I saw in FormErrorNormalizer class the method normalize in FOSRestBundle.
Thanks,
Request's handleRequest is for html forms. You have to use submit instead for FOSREST.
$form->submit($Request->getContent());

Handle json array CAKEPHP

Here's my javascript snipet
<script type="text/javascript">
$(document).ready(function() {
$('form#FormId button.btn').click(function(event){
$.ajax({
type: "POST",
url: "/controller/edit",
data: $("#FormId").serialize(),
success: function(response) {
alert(response.message);
alert(response['message']);
}
});
});
});
Here's my controller action
public function edit() {
$this->autoRender = false; // We don't render a view in this example
$this->request->onlyAllow('ajax'); // No direct access via browser URL
echo json_encode(array('message'=>'Welcome','type'=>'success'));
exit;
}
Both alerts on the javascript are returning "undefined" how to handle?
So nobody is getting this correct.
You need to use the JSON view
See how to enable with this section
class PostsController extends AppController {
public $components = array('RequestHandler');
public function index() {
$this->request->onlyAllow('ajax');
$this->set(array(
'data' => array('message'=>'Welcome','type'=>'success'),
'_serialize' => 'data',
));
}
}
Cake will now automatically set the correct headers, serialize as JSON and not render the layout.
As a side note, your code alert(response.message); does not work beacuse response is a string. your header is text/html not application/json. Try console.log(response) and you will see it is just a string.
I get this to work using the following code in the controller
public function edit()
{
$this->RequestHandler->respondAs('json'); // Very important without this it will not work
$this->autoRender = false;
$data = array('message'=>'Welcome','type'=>'success');
return json_encode($data);
}
try add
dataType: 'json',
to your ajax method.
if it not work, try this:
add
$this->response->type('text/plain');
to your index method.
ie brower did not know json format text.
and what broswer did you used for this test? use F12 open the developer kit to check what the server response, is it a objct
From my experience echoing content doesn't work. You should instead return the json_encoded data.
This will make your edit() function look like this:
public function edit()
{
$this->autoRender = false;
$this->request->onlyAllow('ajax');
return json_encode(array('message' => 'Welcome', 'type' => 'success'));
}
To make sure it works; just alert the whole response object to see what is returned.

Getting aplication/json params with post

I'm using https://github.com/sahat/satellizer to login.
It sends requests with header Content-Type:application/json (body with json ex:{login:login, password:pass})
On server side I use Slim Framework.
In order to retrive sent values I'm getting it through BODY
$app = new \Slim\Slim();
$app->add(new \Slim\Middleware\ContentTypes());
$app->post('/auth/login', function () use ($app) {
$params = $app->request()->getBody();
print_r($params);//I've got it
});
Can I retrive those params through post?
if ($app->request()->isPost()) {
$email = $app->request->post('email');
$password = $app->request->post('password');
}
$_POST is meant for form data. As you noticed you can can find JSON in request body instead.
$app->post('/auth/login', function () use ($app) {
$params = $app->request->getBody();
print_r(json_decode($params, true));
});
I don't know if this is built in to Slim but I use my own middleware to make it work.
class MyMiddleware extends \Slim\Middleware {
function call() {
$body = $this->app->request->getBody();
if (is_array($body))
$this->app->environment()['slim.request.form_hash'] = $body;
$this->next->call();
}
}
This should be added before the ContentTypes middleware to ensure the correct ordering.
$app->add(new MyMiddleware());
$app->add(new \Slim\Middleware\ContentTypes());