I need to implement xsolla payment solution into my cakePHP 2.6 webapp.
By contract my site should communicate via REST with Xsolla.
Xsolla does all the requests to the same url (e.g. http://example.com/rest) and specifies the type of request in the JSON body,
e.g. request from Xsolla:
URL: http://example.com/rest
Accept: application/json
Content-Type: application/json
Content-Length: 78
Authorization: Signature 8189119fb35327cdee7787990df41001c4bd9122
{"data":{"notification_type":"user_validation","user":{"id":"user_id"}}}
I should return HTTP/1.1 400 Bad Request with error code if the user id is invalid or HTTP/1.1 200 OK if the user id is valid.
notification_type could be user_validation, payment and so on.
I implemented all the communication in XsollaController.php in a single function xsolla:
//XsollaController.php:
public function xsolla() {
//...
$data = $this->request->data;
if(array_key_exists("notification_type", $data) &&
$data["notification_type"]=="user_validation") {
//user_validation
}
else if(array_key_exists("notification_type", $data) &&
$data["notification_type"]=="payment") {
//payment
}
//...
}
How can I have different functions based on notification_type?
e.g. function userValidation($data), payment($data) etc.
Also what the proper way to return simple JSON with specified HTTP code?
Now I do the following:
if(userIdIsValid($userID)) {
$body = array('data' => array('user' => array(
"id"=>$data["user"]["id"]), 'message' => 'USER_IS_VALID'));
$this->response->type('json');
$this->response->statusCode(200);
$this->response->body(json_encode($body));
$this->response->send();
$this->_stop();
exit();
}
else {
$body = array('error' => array(
'code' => 'INVALID_USER',
'message' => 'INVALID_USER',
'user' => array("id"=>$data["user"]["id"])));
$this->response->type('json');
$this->response->statusCode(400);
$this->response->body(json_encode($body));
$this->response->send();
$this->_stop();
exit();
}
My code works but seems hard coded. I believe CakePHP provide a better way to do it.
For your first question of executing different functions based on the notification_type, you are almost there (unless I am oversimplifying your question).
//XsollaController.php:
public function xsolla() {
//...
$data = $this->request->data;
if(array_key_exists("notification_type", $data) &&
$data["notification_type"]=="user_validation") {
$this->userValidation($data);
}
else if(array_key_exists("notification_type", $data) &&
$data["notification_type"]=="payment") {
$this->payment($data);
}
//...
}
private function userValidation($data) {
// user validation
}
private function payment($data) {
// payment
}
As far as responding with JSON content and the correct HTTP status code, your code looks fine. There's room for some minor improvements, but if it's functional, I'd leave it. You are setting the response code correctly. If you want the truly "cake" way of responding with JSON content, read up on JSON and XML views in the CakePHP docs.
Related
I'm building a small application to store contacts in the database, I've finished the GET/POST routes, and worked fine, now I'm on the API routes (in order to use AJAX calls). I can store the information if all fields are present in the POST request, nonetheless, If I want to send messages back to the call (to send feedback about why the contact hasn't been stored) the response is sending me to the main route www.myapp.com (with no messages) and I want to send a json back with the "reason".
At this moment I only validate if the 'nombre', 'correo', 'telefono' have information with standard Laravel's request validate method.
This is my LeadController
public function storeApi(Request $request)
{
$request -> validate([
'nombre' => 'required',
'correo' => 'required' ,
'telefono' => 'required'
]);
if(Lead::create($request->all())){
$result[] = ['saved' => true];
}else{
$result[] = ['saved' => false,
'reason' => 'Some data is missing'];
return response()-> json($result);
};
return response()-> json($result);
}
When the record is stored, it does send back the Json {'saved' : true} but when fails It just sends you back to the '/' Route: www.myapp.com
How can I send the messages back to the POST call?
It is redirecting back to "/" because $request->validate() method throws \Illuminate\Validation\ValidationException exception..
There are try ways to handle this request.
Put try catch block around your validate code
Or Handle this expection in app\Exception\Handler.php, and return the response in JSON format.
After some further reading I just change the way the information is validated using the Validator Class:
public function storeApi(Request $request)
{
$validator = \Validator::make($request->all(), ['nombre' => 'required', 'correo' => 'required', 'telefono' => 'required']);
if($validator->fails()){
return response()->json($validator->errors(), 422);
}else {
//ready to store
}
}
This way I don't let the ValidationException exception occurs before sending the feedback to the call.
I have a store() method.
public function create(StorePost $request)
{
$post = Post::create([
'title' => $request->title,
'description' => $request->description,
]);
return response()->json([
'post' => $post
], 201);
}
In my StorePost class, I validated the request.
public function rules()
{
return [
'title' => 'required|string|max:255',
'description' => 'required',
];
}
When I tested it with Postman with wrong entries, for example, if I fill 'description' with a null value, it returns to the home page without any response or error. How can I retrieve the validation errors?
I solved it just by adding 'Accept': 'application/json' in my request's header.
This happens because Laravel Form Request works both for API and non-API requests.
On requests made by a form, it will redirect back (to the original form url or home if not sent from a form) with an error bag on $errors variable available to the view.
On requests made by ajax (usually on APIs) usually have the header Accept: application/json, so Laravel automatically knows that you want the validation error bag as json on the response body instead of a redirect that makes no sense for an API.
Hope this can clarify thing to you.
I created an API client for my company to fetch orders from our distributors. I need to acknowledge download of orders back to them with a PUT. The PUT is working properly but I get an error on their confirmation of my acknowledgement.
Using Postman, I get a JSON body message back.
When I PUT a acknowledgement back, I get the following error:
Type error: Argument 1 passed to GuzzleHttp\Client::send() must
implement interface Psr\Http\Message\RequestInterface, instance of
GuzzleHttp\Psr7\Response given, called in
/var/www/orders/app/Http/Controllers/edi/OrderController.php on line 86
This is line 86:
$response = $client->send($apirequest);
The relevant code:
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Client as GuzzleHttpClient;
use GuzzleHttp\Psr7\Stream;
use Illuminate\Support\Facades\Input;
use Response;
use XmlParser;
use Psr\Http\Message\RequestInterface;
public function orderConfirm()
{
$uri = config('services.orders.orderack');
$formdata = Input::all();
$orders = Input::get('orders');
try {
$client = new GuzzleHttpClient([
'headers'=> [
'Authorization' => '$user',
'ContractID' => '$contract',
'Content-Type' => 'application/json']
]);
$apirequest = $client->request('PUT', $uri,
['body' => json_encode(
[
$orders
]
)]
);
$response = $client->send($apirequest);
$contents = (string) $response->getBody();
return $contents;
}
catch (RequestException $ex) {
//Exception Handling
echo $ex;
}
Output from Postman was:
"Number of Orders Acknowledged: 1"
from other posts on SO, this:
$contents = (string) $response->getBody();
is the way to get the body and other people fixed their problems, but it's not working for me.
Obviously I'm still missing something here!
Calling $client->request() actually does the request (which is why it's returning an instance of GuzzleHttp\Psr7\Response) instead of building a request object to send later. You don't need to tell the client to send anything because it's already been sent; you just need to set the $response variable to the value of the call to $client->request().
This can be seen in the Body example in their PSR7 documentation.
$response = $client->request('GET', 'http://httpbin.org/get');
To build a request object manually, you will have to create an instance of GuzzleHttp\Psr7\Request using its constructor, as documented under Requests.
// Create a request using a completely custom HTTP method
$request = new \GuzzleHttp\Psr7\Request('MOVE', 'http://httpbin.org/move');
echo $request->getMethod();
// MOVE
I have read the RequestHandler part in cookbook. There are isXml(), isRss(), etc. But there's no isJson().
Any other way to check whether a request is JSON?
So when the url is mysite.com/products/view/1.json it will give JSON data, but without .json it will give the HTML View.
Thanks
I dont think cakePHP has some function like isJson() for json data, you could create your custom though, like:
//may be in your app controller
function isJson($data) {
return (json_decode($data) != NULL) ? true : false;
}
//and you can use it in your controller
if( $this->isJson($your_request_data) ) {
...
}
Added:
if you want to check .json extension and process accordingly, then you could do in your controller:
$this->request->params['ext']; //which would give you 'json' if you have .json extension
CakePHP is handling this correctly, because JSON is a response type and not a type of request. The terms request and response might be causing some confusing. The request object represents the header information of the HTTP request sent to the server. A browser usually sends POST or GET requests to a server, and those requests can not be formatted as JSON. So it's not possible for a request to be of type JSON.
With that said, the server can give a response of JSON and a browser can put in the request header that it supports a JSON response. So rather than check what the request was. Check what accepted responses are supported by the browser.
So instead of writing $this->request->isJson() you should write $this->request->accepts('application/json').
This information is ambiguously shown in the document here, but there is no reference see also links in the is(..) documentation. So many people look there first. Don't see JSON and assume something is missing.
If you want to use a request detector to check if the browser supports a JSON response, then you can easily add a one liner in your beforeFilter.
$this->request->addDetector('json',array('callback'=>function($req){return $req->accepts('application/json');}));
There is a risk associated with this approach, because a browser can send multiple response types as a possible response from the server. Including a wildcard for all types. So this limits you to only requests that indicate a JSON response is supported. Since JSON is a text format a type of text/plain is a valid response type for a browser expecting JSON.
We could modify our rule to include text/plain for JSON responses like this.
$this->request->addDetector('json',array('callback'=>function($req){
return $req->accepts('application/json') || $req->accepts('text/plain');
}));
That would include text/plain requests as a JSON response type, but now we have a problem. Just because the browser supports a text/plain response doesn't mean it's expecting a JSON response.
This is why it's better to incorporate a naming convention into your URL to indicate a JSON response. You can use a .json file extension or a /json/controller/action prefix.
I prefer to use a named prefix for URLs. That allows you to create json_action methods in your controller. You can then create a detector for the prefix like this.
$this->request->addDetector('json',array('callback'=>function($req){return isset($req->params['prefix']) && $req->params['prefix'] == 'json';}));
Now that detector will always work correctly, but I argue it's an incorrect usage of detecting a JSON request. Since there is no such thing as a JSON request. Only JSON responses.
You can make your own detectors. See: http://book.cakephp.org/2.0/en/controllers/request-response.html#inspecting-the-request
For example in your AppController.php
public function beforeFilter() {
$this->request->addDetector(
'json',
[
'callback' => [$this, 'isJson']
]
);
parent::beforeFilter();
}
public function isJson() {
return $this->response->type() === 'application/json';
}
Now you can use it:
$this->request->is('json'); // or
$this->request->isJson();
Have you looked through and followed the very detailed instructions in the book?:
http://book.cakephp.org/2.0/en/views/json-and-xml-views.html
class TestController extends Controller {
public $autoRender = false;
public function beforeFilter() {
$this->request->addDetector('json', array('env' => 'CONTENT_TYPE', 'pattern' => '/application\/json/i'));
parent::beforeFilter();
}
public function index() {
App::uses('HttpSocket', 'Network/Http');
$url = 'http://localhost/myapp/test/json';
$json = json_encode(
array('foo' => 'bar'),
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP
);
$options = array('header' => array('Content-Type' => 'application/json'));
$request = new HttpSocket();
$body = $request->post($url, $json, $options)->body;
$this->response->body($body);
}
public function json() {
if ($this->request->isJson()) {
$data = $this->request->input('json_decode');
$value = property_exists($data, 'foo') ? $data->foo : '';
}
$body = (isset($value) && $value === 'bar') ? 'ok' : 'fail';
$this->response->body($body);
}
}
Thanks a lot Mr #Schlaefer. I read your comment and try, Wow it's working now.
//AppController.php
function beforeFilter() {
$this->request->addDetector(
'json', [
'callback' => [$this, 'isJson']
]
);
parent::beforeFilter();
...
}
public function isJson() {
return $this->response->type() === 'application/json';
}
//TasksController.php
public $components = array('Paginator', 'Flash', Session','RequestHandler');
//Get tasks function return all tasks in json format
public function getTasks() {
$limit = 20;
$conditions = array();
if (!empty($this->request->query['status'])) {
$conditions = ['Task.status' => $this->request->query['status']];
}
if (!empty($this->request->query['limit'])) {
$limit = $this->request->query['limit'];
}
$this->Paginator->settings = array('limit' => $limit, 'conditions' => $conditions);
$tasks = $this->paginate();
if ($this->request->isJson()) {
$this->set(
array(
'tasks' => $tasks,
'_serialize' => array('tasks')
));
}
}
In case anybody is reading this in the days of CakePHP 4, the correct and easy way to do this is by using $this->request->is('json').
How can I set a cookie with a json response?
I noticed, for me at least, the following command is the only thing working that sets a cookie:
return Redirect::to('/')
->withCookie(Cookie::make('blog', $cookie_values, 1000));
Of course if it was an ajax request it would return the target of the redirect.
How could I translate this to an ajax request and return a json response with the cookie?
I was able to set a cookie with a json response with the following code:
$cookie_values = array(
'name' => Input::get('name'),
'id' => Auth::user()->id,
'login_success' => 1);
if(Request::ajax())
{
$cookie = Cookie::make('blog', $cookie_values, 1000);
$response = Response::json($cookie_values);
$response->headers->setCookie($cookie);
return $response;
}
Great hint!
Having a look at Symfony\Component\HttpFoundation\ResponseHeaderBag also revealed how to set headers for a json response when having problems with HTTP access control:
$response->headers->set('Access-Control-Allow-Origin', '/* your subdomain */');