I'm trying to handle this problem:
My app send JSON POST request with several information encoded in a Json. Example:
{"UserInfoA":{"1":123,"2":"hello","3":"bye","4":{"subinfo":1,"subinfo2":10}},
"UserInfoB":{"a":"12345678","b":"asd"}} // and so on...
each UserInfo have:
Its own entity (although some request may have information of more than one entity).
A controller to persist this Object on DB and then give back the ID on this DB.
So, to achieve this problem I did another controller like JsonHandler, which receive this request and then forward to each controller after gut this JSON into differents objects. Example:
public function getJson (Request $request){
if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
$data = json_decode($request->getContent(), true);
}
if (!isset($data['UserInfoA'])){
return new JsonResponse('ERROR');
}
$uia = $data['UserInfoA'];
$idInfoA = $this->forward('Path::dataAPersist',array('data'=>$uia));
}
// same with userinfoB and other objects
return $idInfoA ;
This works perfectly but Is it correct? Should i use services instead?
EDIT : I need to response the ID in a Json and this->forward returns a Response, so i can't use JsonResponse and if a send directly $idInfoA just send the IDNUMBER not in a JSON, how can i do it?
To sum up : a Json listener that receive the information, work it and then dispatch to the corresponding controller. This listener, should be a controller, a service or another thing?
I recommend the use of symfony-bundles/json-request-bundle since it does the JsonRequestTransformerListener for you. You just need to recieve the request parameters as usual:
...
$request->get('some_parameter');
...
hi you have to use service to make the Transformation
class php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class JsonRequestTransformerListener {
public function onKernelRequest(GetResponseEvent $event) {
$request = $event->getRequest();
$content = $request->getContent();
if (empty($content)) {
return;
}
if (!$this->isJsonRequest($request)) {
return;
}
if (!$this->transformJsonBody($request)) {
$response = Response::create('Unable to parse request.', 400);
$event->setResponse($response);
}
}
private function isJsonRequest(Request $request) {
return 'json' === $request->getContentType();
}
private function transformJsonBody(Request $request) {
$data = json_decode($request->getContent(), true);
if (json_last_error() !== JSON_ERROR_NONE) {
return false;
}
if ($data === null) {
return true;
}
$request->request->replace($data);
return true;
}
}
And In Your Service.yml
kernel.event_listener.json_request_transformer:
class: You\NameBundle\Service\JsonRequestTransformerListener
tags:
- { name: "kernel.event_listener", event: "kernel.request",method: "onKernelRequest", priority: "100" }
Now You can call The Default request function to get Data
$request->request->all();
You can use symfony ParamConverter to to convert the json into any object you want and raise a meaningful Exception if anything goes wrong.
You can add custom ParamConverters and use them in your actions with annotation
Related
I am making rest API with Yii Plus when I am trying to print_r request (using Postman) it's empty, can anyone let me know what I am doing wrong.
<?php
namespace frontend\controllers;
use Yii;
use yii\rest\Controller;
class ApiController extends Controller
{
Const APPLICATION_ID = 'ASCCPE';
private $format = 'json';
public function actionUserRegister()
{
$request = \Yii::$app->request->post(); $post = (file_get_contents("php://input"));
print_r($request);
die('sdw');
}
}
Output
You are not trying to print request. You are trying to print post data, but you are not sending any post data by your request.
The \Yii::$app->request->post(); returns data from $_POST array. This array is filled from request body only for data that have been sent in form-data or x-www-form-urlencoded format.
In postman click open the body part of request, select one of the two mentioned formats and fill the data you want to send.
If you want to use other format for request, like json or xml, you have to read it from php://input. You already have it in your code:
$post = (file_get_contents("php://input"));
So try to print the $post instead of $request variable. But you still need to fill the body part of request in postman.
The params you've set in postman are the GET params. Those are part of request's url. You can get them for example like this:
$request = \Yii::$app->request->get();
You are returning with the message in the die function.
Instead of this you can try this way:
die(print_r($request, true));
Example:
public function actionCreate()
{
$request = Yii::$app->request->post();
die(print_r($request, true));
}
better:
return print_r($request, true);
Example:
public function actionCreate()
{
$request = Yii::$app->request->post();
return print_r($request, true);
}
better:
// include the VarDumper class
\Yii::info(VarDumper::dumpAsString($request));
// output will be located in your app.log file
More info on the print_r function
Example:
public function actionCreate()
{
return Yii::$app->request->getBodyParams();
}
This May sound like a silly question but, I have a method that normally returns a JSON response to the client side. But now I need the same method within the class to prevent DRY. Something like:
public function deleteChild($id){
// delete the element with given id ...
if($success){
return response()->json(['success'=>'successfully deleted'], 200);
}else{
return response()->json(['error'=>'could not be deleted'], 422);
}
}
This method is already used by the client side. But now I have another one that needs this method as well. Something like:
public function deleteMaster($id){
$master = Master::find($id);
foreach($child as $master->children){
$child_json_response = $this->deleteChild($child->id);
$response_data = $child_json_response->getData();
if($response_data->error){
// handle child error response
// ...
}
// delete master itself
}
}
Here I can extract the response data with getData() and process it.
Is this the right way to use a sibling function that returns a JSON response (actually made for client-side) or should I create another method that returns direct results serving server-side only?
You should use a Response macro
Into a service provider's boot, add this:
\Illuminate\Http\Response::macro('deleteJson', function ($success) {
return $success
? $this->json(['success'=>'successfully deleted'], 200)
: $this->json(['error'=>'could not be deleted'], 422);
}
public function deleteChild($id){
// delete element with given id ...
return response()->deleteJson($success);
}
I have a function in my Laravel application that generates TwiML for a holding queue. It seems that when I try to dynamically generate the value for the waitUrl attribute, I end up getting a 500 server error during runtime. Routes are properly established and I'm able to view the correct XML at the waitURL in the browser. However, the error persists.
If I create a static XML file with the same exact content, or use a TwiML Bin, it works like a charm.
Here are the relevant functions:
public function wait() {
return $this->generateWaitTwiml();
}
public function onHold($agentId) {
return $this->generateHoldQueueTwiml($agentId, '/phone/wait');
}
private function generateHoldQueueTwiml($agentId, $waitUrl = null) {
$queue = $agentId . '_hold';
if ($waitUrl === null){
$waitUrl = 'path_to_static.xml';
}
$queue = $agentId . '_hold';
$response = new Twiml();
$response->enqueue(
$queue,
['waitUrl' => $waitUrl]
);
return response($response)->header('Content-Type', 'application/xml');
}
private function generateWaitTwiml() {
$response = new Twiml();
$response
->play('http://path_to_my.mp3');
return response($response)->header('Content-Type', 'application/xml');
}
This was resolved by excluding the URIs from the CSRF verification (in VerifyCsrfToken.php):
class VerifyCsrfToken extends Middleware {
protected $except = [
'uri/',
'uri2/*',
];
}
I try to log if user type wrong url parameter for a route with
'constraints' => array('personalnumber' => '[0-9]*')
$error = $e->getError();
if ($error == Application::ERROR_ROUTER_NO_MATCH) {
$url = $e->getRequest()->getUriString();
$sm->get('Zend\Log\RouteLogger')->warn('Url could not match to routing: ' . $url);
}
Can I get a specific error like: Value for Parameter "id" must type integer?
That won't be so easy. You would have to build your own functionality to find out the exact details on why the route didn't match.
Route matching is checked using the RouteInterface::match method from the corresponding class. For example for segment routes this method can be found in the Zend\Router\Http\Segment class on line 359-404.
If there is no match, the class returns null/void. Details on why the route didn't match is not part of the response, so you would have to do such in depth analysis yourself and write your own custom error response.
Such a solution could be to do manually validate the person number (for example by isolating it from the request url) when the dispatch error event is triggered and return your own custom response before the default 404 response.
<?php
namespace Application;
use Zend\Http\Response;
use Zend\Mvc\MvcEvent;
use Zend\Router\Http\RouteMatch;
class Module{
public function onBootstrap(MvcEvent $event)
{
$eventManager = $event->getApplication()->getEventManager();
$eventManager->attach(MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'validatePersonNumber'), 1000);
}
public function validatePersonNumber(MvcEvent $event)
{
if ($event->getError() !== Application::ERROR_ROUTER_NO_MATCH) {
// Not a 404 error
return;
}
$request = $event->getRequest();
$controller = $event->getController();
if($controller !== 'Application\Expected\ControllerName'){
// not a controller for person number route
return;
}
$url = $request->getRequestUri();
$personNumber = ''; //...manually isolate the person number from the request...
/** #var Response $response */
$response = $event->getResponse();
$response->setStatusCode(404);
$viewModel = $event->getViewModel();
$viewModel->setTemplate('error/404');
$event->setViewModel($viewModel);
$event->stopPropagation(true);
if (strlen($personNumber) !== 12) {
$viewModel->setVariable('message', 'A person number should have 12 characters');
}
if(...additional check...){
$viewModel->setVariable('message', 'Some other message');
}
}
}
To make things prettier you could consider moving all this into a Listener class (instead of polluting your module.php file) and you could also consider the 404 code here. Most likely there is a more suitable status code for such validation response.
Note: This is not a completely finished example, it needs more work!
I have below code that save the country information in Database. Below code works fine. There is no problem in that.
private function SaveChanges(\App\Http\Requests\CountryRequest $request) {
if($request['CountryID'] == 0) {
$Country = new \App\Models\CountryModel();
}
else {
$Country = $this->GetCountry($request['CountryID']);
}
$Country->Country = $request['Country'];
$Country->CountryCode = $request['CountryCode'];
$Country->save();
return redirect()->route($this->AllCountries);
}
Now, I decided to shift the working of above method inside a new class like below. Here I am reading the JSON data
class CountryData {
public function CreateCountry($CountryObject) {
$obj = json_decode($CountryObject);
$Country = new \App\Models\CountryModel();
$Country->Country = $CountryObject->Country;
$Country->CountryCode = $CountryObject->CountryCode;
$Country->save();
return true;
}
}
and the original function is changed like below. Sending the Request parameter in the form of JSON.
private function SaveChanges(\App\Http\Requests\CountryRequest $request) {
$data = array(
'Country' => $request['Country'],
'CountryCode' => $request['CountryCode'],
'CountryID' => $request['CountryID']
);
if($request['CountryID'] == 0) {
$result = (new \CountryData())->CreateCountry( json_encode($data) );
}
return redirect()->route($this->AllCountries);
}
Question: Is my approach correct to send converted request object to JSON object and reading in an another Class .
I am doing that so that I can create a new controller and call the CreateCountry from class CountryData to return JSON data for an Android App.
Well, I don't think it's a good approach. Your CountryData class acts as a service, so I think it hasn't have to know anything about JSON, that is part of the interface between your business logic and the external side of your system (Android app, web interface, etc.)
Your new Controller may receive JSON objects and answer with JSON objects, but it must convert the JSON received to your business classes, then pass them to your services, in this case CountryData (not a good name, though).
So the logic should be:
Controller:
- receive request data
- call service and save or whatever
- encode to JSON
- send the response in JSON format
So your business classes don't know anything about JSON.
A not fully code solution is provided as an idea, but it lacks error management, and more work to do. It's based on some Laravel 5 features. Also I don't know if you're using REST or what kind of request are you doing...
use App\Http\Controllers\Controller;
class CountryController() extends Controller {
public function store(\App\Http\Requests\CountryRequest $request) {
// TODO manage errors
$countryModel = $this->createOrUpdateCountry($request);
// Laravel way to response as JSON
return redirect()->json($this->country2Array($countryModel);
}
private function createOrUpdateCountry(\App\Http\Requests\CountryRequest $request) {
$countryId = $request['CountryID'];
if($id == 0) {
$countryModel = new \App\Models\CountryModel();
} else {
$countryModel = $this->GetCountry($countryId);
}
$countryModel->Country = $request['Country'];
$countryModel->CountryCode = $request['CountryCode'];
// You must have an initialised instance of CountryDAO
// TODO manage errors
$countryDAO->saveOrUpdate($countryModel);
return $countryModel;
}
private function country2Array($countryModel) {
$data = array(
'country' => $countryModel->Country,
'countryCode' => $countryModel->CountryCode,
'countryId' => $countryModel->CountryID
);
return $data;
}
}
/**
* Formerly CountryData
*/
class CountryDAO {
public function saveOrUpdate($countryModel) {
// TODO Manage errors or DB exceptions
// I'd put the DB save access/responsability here instead of in CountryModel
$countryModel->save();
return true;
}
}
First of you should not do any conversions to objects and so on.
Second, since the request object should be an array as shown on your example I suggest you to use the "fill" method of Laravel, instead of looping on hand all of the request elements.
Your code for saving the request should be as follows:
class CountryData {
public function CreateCountry($requestData) {
$Country = new \App\Models\CountryModel();
$country->fill($requestData);
$Country->save();
return true;
}
}
The "fill" method loops all of the array keys and tries to set them into the object instance if it has those keys as properties. If there are any extra fields, they are trimmed and you wont get any errors.
Cheers! :)