Why is Yii2 rest controller giving response in XML format? - php

Currently I am using the following initialization code on my api module
public function init()
{
parent::init();
Yii::$app->response->format = Response::FORMAT_JSON;
}
My api gives back response in XML format in the following example.
public function actionTest()
{
$items = ['one', 'two', 'three' => ['a', 'b', 'c']];
return $items;
}
This is the response:
<response>
<item>one</item>
<item>two</item>
<three>
<item>a</item>
<item>b</item>
<item>c</item>
</three>
</response>
The only way I can get it to work is by adding this line to each of the controller behaviors. I have read the documentation which says I can initalize this on the module class so I do not need to do it in every controller. I do not know why it gives XML though. Also just in case the only way is to add it to my behaviors, do I have to write code to handle name, code, status, type, previous and code or does Yii provide yii\rest\Controller and yii\rest\ActiveController which automatically handle this. Clearly they are being output automatically when there is an error.
{"name":"Not Found"
"message":"Page not found.",
"code":0,
"status":404
"type":"yii\\web\\NotFoundHttpException"
"previous":{"name":"Invalid Route","message":"Unable to resolve the request: api/home/",
"code":0,"type":"yii\\base\\InvalidRouteException"
}
}

After three painful days, I have found the solutions. It is sometimes quite difficult to explain this problem when you are coming from a whole JSON world of ExpressJS and NodeJS. Logically what Yii2 does is perfectly fine, on the other hand 90% RESTful APIs expect the output to be in JSON hence you do not explicitly want to set request headers every time you make an API call.
Browsers by default add request headers as "Application/XML" hence what you see on screen is XML instead of JSON.
Yii2's content negotiator upon receipt of headers as application/xml formats your output in XML. If you make a same request using CURL or PostMan with headers as "Application/JSON" you will get the desired output.
If you wish to override this behaviour then just add the below function in your controller and include the following:-
use yii\web\Response;
use yii\helpers\ArrayHelper;
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
[
'class' => 'yii\filters\ContentNegotiator',
'only' => ['view', 'index'], // in a controller
// if in a module, use the following IDs for user actions
// 'only' => ['user/view', 'user/index']
'formats' => [
'application/json' => Response::FORMAT_JSON,
],
'languages' => [
'en',
'de',
],
],
]);
}

In Yii2 applications default response type is XML (and I guess it's default for REST too). During HTTP connection both sides declares what type of data are able to send and/or receive. If this information is not passed to server default data type is send(even if you specified that it should be JSON in your app) to guarantee proper communication. If you want to receive JSON data you must add Accept: application/json header to your request. And probably you don't have to specify it in php code because Yii2 should deduct it from request headers.
You can find more explanation how it works here.

I test Your code and it's work perfectly
my controller this :
<?php
namespace backend\controllers;
use yii\rest\Controller;
use yii;
use yii\web\Response;
class TestController extends Controller{
public function init()
{
parent::init();
Yii::$app->response->format = Response::FORMAT_JSON;
}
public function actionTest(){
$items = ['one', 'two', 'three' => ['a', 'b', 'c']];
return $items;
}
}
Output :
{"0":"one","1":"two","three":["a","b","c"]}
check your namespace OR send your code !

Related

execute global function automatically on running controller in yii2

We have web pages, where user will be redirected to $this->goHome(), if the session timeouts or user logouts. We have to destroy the all the session so, we have to add a function with destroying session. This function should be executed before running any action/controller in Yii2 i.e. similar to hooks in codeigniter. We have tried a helper function with destroying session and we have called the function as HomeHelper::getHelpDocUrlForCurrentPage(); in main.php layout, but the layout will be executed after running action in controller, it should work on running any controller as we have 100+ controllers. How this can be achieved, please suggest us in right way. Thanks in advance.
in
config/main.php
you could try using 'on beforeAction'
return [
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'bootstrap' => [
'log',
....
],
'on beforeAction' => function($event){
// your code ..
} ,
'modules' => [
....
],
...
];
While #ScaisEdge solution would work I believe application config is not proper place to hold application logic.
You should use filters to achieve result you want.
First you need to implement filter with your logic. For example like this:
namespace app\components\filters;
class MyFilter extends yii\base\ActionFilter
{
public function beforeAction() {
// ... your logic ...
// beforeAction method should return bool value.
// If returned value is false the action is not run
return true;
}
}
Then you want to attach this filter as any other behavior to any controller you want to apply this filter on. Or you can attach the filter to application if you want to apply it for each action/controller. You can do that in application config:
return [
'as myFilter1' => \app\components\filters\MyFilter::class,
// ... other configurations ...
];
You might also take a look at existing core filters if some of them can help you.

Best practice for handling POST request in PHP

I'm working on a PHP project which has many pages calling a POST request, such as login, register, commenting and etc.
I've tried processing all POST requests in a single file named post.php, with each request containing 'formtype' parameter, like so,
$formtype = $_POST['formtype'];
if ($formtype == "register") {
register_function_here();
} else if ($formtype == 'login') {
login_function_here();
} else {
die("Error: No FORMTYPE");
}
and I've also tried having separate files for separate functions, such as login.php, register.php, comment.php and etc.
Which method is better for processing POST requests?
Are there any disadvantages for processing all POST requests in a single file, as I've done?
Thanks in advance!
I guess you mean you do not want to:
GET index.php
POST user/register.php
POST user/login.php
index.php
user/
register.php
login.php
404.php
What #ArtisticPhoenix about the MVC (model, view, controller) is actually what you tried. Well, for the Controller part i mean.
You try to create router.
You could do that. If you are new to coding and you got time i even would say: do it.
If you dont have time and need a solution then i suggest searching a framework - at least for routing.
To get started:
First i found was this: https://www.taniarascia.com/the-simplest-php-router/
If you want to go further then you SHOULD start using OOP.
A class is then a controller, a method an action.
(some got like every action is a class like zend expressive framework).
Example:
Create a routing config
// file: config/routes.php
return [
'routes' => [
[
'path' => "/login",
'class' => LoginController::class,
'method' => 'loginAction',
'allowed_methods' => ['POST'],
],
[
'path' => "/logout",
'class' => LoginController::class,
'method' => 'logoutAction',
'allowed_methods' => ['GET', 'POST'],
],
// ...
],
];
Create Controllers
// file: Controller/LoginController.php
namespace Controller;
class LoginController
{
public function loginAction()
{
// ...
}
public function logoutAction()
{
// ...
}
}
Now use the requested path and route it to the controller.
If no route found then return a HTTP 404 "Not Found" response .
// file: index.php
// load routing config
$routes = require 'config/routes.php';
// ...
// ... this now is up to you.
// you should search in the config if the requested path exists
// and if the request is in the allowed_methods
// and then create a new controller and call the method.
I strongly recommend Object Oriented Programming, using classes, one source file per class.

Laravel 5.4 - form/API/view

I created an API (store) that saves the data on the database and returns 201 if successful or 404 if not.
if ($visit->save()){
$visit->view_visit = [
'href' => 'api/v1/visit/' . $visit->id,
'method' => 'GET'
];
$response = [
'msg' => 'Visit created.',
'visit' => $visit
];
return response()->json($response, 201);
}
$response = [
'msg' => 'Error during creation.'
];
return response()->json($response, 404);
It works perfectly. Using postman you can see that the status will be <<201 Created>>.
This API should be used in two ways: called by another application or called by a Laravel form. This is the question:
How do I call it in a way if it successful, it will load a given view on the browsers?
In other words, is there a way to make the form call a route (the api itself, something like ../api/visit/) and in case of success loads the other view? Also, I would like to pass the content of response['msg'] to this new view.
I know I could do it inside the store method by filtering the HTTP referrer, but I would like to keep the controller code strictly to manage the record creation. Besides that, I have to send the 201/404 codes along with the returned data.
I also considered creating another controller to handle the API response and then call the form, but it still sounds too much -- it's supposed to be easy, I guess.
In laravel you can use a helpful method which determines if the request that has been sent is an AJAX request or just a normal request, which is:
$request->wantsJson()
So, Inside your controller in the return function, you will make an if statement:
if ($request->wantsJson()) {
return response()->json();
}else{
return view(...);
}

Laravel 5.2 PHPUnit JSON Api request body not being set

I am testing POSTing data to an API endpoint we've created using Laravel 5.2, but none of the parameters seem to be reaching the application in the test. The endpoint expects json and responds with json and uses a FormRequestValidator which has required rules for active and make parameters. The test fails with status code 422 and the examining the response body it states the active and make parameters are required even though they are being passed in the call so therefore for some reason when the request reaches the the Form Request Validator, the input is not there.
However, when I invoke the endpoint with json body including make and active from Postman or the UI app we've built it works fine, it is only failing in the PHPUnit tests therefore it must be something with PHPUnit or the test setup being incorrect. Here is the test:
public function testItStoresCars()
{
// Arrange
$user = User::first();
//Act
$this->json(Request::METHOD_POST, '/v1/cars', [
'active' => true,
'make' => 'Audi'
],
['Authorization' => 'Bearer '.\JWT::fromUser($user)]));
// Assert
$this->assertResponseOk();
}
I know the Authorisation header is set correctly from other tests passing.
I've tried disabling middleware, using the $this->post helper method and manually setting the headers as well as using the $this->call method with setting the Headers and encoding the data using json_encode but I always get the same 422 response. I'm wondering has anyone encountered this issue or can see an error?
Controller Code
public function store(CreateCarRequest $request)
{
$car = $this->carRepo->save($request->all());
return response()->json(car);
}
FormRequest
class CreateCarRequest extends Request
{
public function rules()
{
return [
'active' => 'required|boolean',
'make' => 'required',
];
}
}
422 is the error response for validation errors.. which means either your data is not posted or it doesn't pass server validation, try
$this->json(Request::METHOD_POST, '/v1/cars', [
'active' => true,
'make' => 'Audi'
],
['Authorization' => 'Bearer '.\JWT::fromUser($user)]))->dump();
dump() should show you the errors

CakePHP + Crud "Add" action (POST) returning 412 Precondition Failed

The Add action (POST method) in CakePHP 3 is not working and it is asking fields that I already informed in json message. Let me explain:
What is my scenario:
I'm using WAMP and my Apache is mod_rewrite activated;
I'm using composer and I installed CakePHP 3 and CRUD (friendsofcake/crud);
I set MySQL database connection correctly;
I'm using MySQL example database sakila;
I generated Country model from Bake;
I changed the AppController class content to load CRUD plugins changing it's content to:
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller {
use \Crud\Controller\ControllerTrait;
public $components = [
'RequestHandler',
'Crud.Crud' => [
'actions' => [
'Crud.Index',
'Crud.View',
'Crud.Add',
'Crud.Edit',
'Crud.Delete'
],
'listeners' => [
'Crud.Api',
'Crud.ApiPagination'
]
]
];
}
I added the Router::extensions(['json', 'xml']) in app/config/routes.php;
I added $routes->resources('Country') without deleting the default routes existing in app/config/routes.php;
My root site address is http://localhost:8080/mtag
How problem happen:
I'm using Postman for Chrome and setting header Accept and Content-Type value to application/json and sending POST to following link: http://localhost:8080/mtag/country with the following json content:
{
"country": "BlaBlaBla",
"last_update": "2015-12-24"
}
But I receive the following status code: 412 Precondition Failed.
In returned json:
success: false,
data: {
code: 412,
message: "2 validation errors occured",
}
The missing fields indicated is country and last_update but I informed them in json. What I did wrong? Did someone have an example how to use CakePHP CRUD correctly using json?
When I am using Postman for testing the cakePHP CRUD functions I do NOT use application/json for Content-Type but leave it blank. And for the body part just use form-data instead of a json object while using a raw body.

Categories