I'm learning to build API with symfony (using FOSRestBundle). I'm following a french tutorial. Obviously, I first try to write the code by myself but even with a copy/paste, it keeps get me empty JSON array when I do a GET request to the appropriate route (rest-api.local/places).
The code works OK if I "format" the code in php array:
public function getPlacesAction(Request $request)
{
$places = $this->get('doctrine.orm.entity_manager')
->getRepository('AppBundle:Place')
->findAll();
/* #var $places Place[] */
$formatted = [];
foreach ($places as $place) {
$formatted[] = [
'id' => $place->getId(),
'name' => $place->getName(),
'address' => $place->getAddress(),
];
}
return new JsonResponse($formatted);
}
but then I try to serialize directly $places, using the view handler of fost Rest (in config.yml)
fos_rest:
routing_loader:
include_format: false
view:
view_response_listener: true
format_listener:
rules:
- { path: '^/', priorities: ['json'], fallback_format: 'json' }
and changing my function in my controller to the following code, I get my JSON response but without anything between "{ [],[],[]}" (and I do have 3 entries in my DB):
public function getPlacesAction(Request $request)
{
$places = $this->get('doctrine.orm.entity_manager')
->getRepository('AppBundle:Place')
->findAll();
/* #var $places Place[] */
return $places;
}
It's my first post on stackoverflow, so I hope my question is clear, Have a nice day.
In my app/config/services.yml I've added this:
services:
object_normalizer:
class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
# Important! Tag this service or it wouldn't work
tags:
- { name: serializer.normalizer }
I still can't get why it works but all of a sudden I've got a nice json in response.
Related
I'm implementing a simple REST API in my Symfony 4 project. When I test the getArticle() function with Postman this is the error:
The controller must return a response (Object(FOS\RestBundle\View\View) given).
With a var_dump($articles) content is displayed as expected so I guess the problem could be the FOSRestBundle but I don't know other ways to do this job.
class ArticleController extends FOSRestController
{
/**
* Retrieves an Article resource
* #Rest\Get("/articles/{id}")
*/
public function getArticle(int $articleId): View
{
$em = $this->getDoctrine()->getManager();
$article = $em->getRepository(Article::class)->findBy(array('id' => $articleId));
// In case our GET was a success we need to return a 200 HTTP OK response with the request object
return View::create($article, Response::HTTP_OK);
}
}
Define a view response listener in your config/packages/fos_rest.yaml. For details why and what it does read the FOSRest documentation here.
fos_rest:
view:
view_response_listener: true
Also add this to your fos_rest.yaml to select your output format -> here json
fos_rest:
[...]
format_listener:
rules:
- { path: ^/, prefer_extension: true, fallback_format: json, priorities: [ json ] }
I've found a solution by myself returning a HttpFoundation\Response, it might be helpful to someone.
/**
* Lists all Articles.
* #FOSRest\Get("/articles")
*/
public function getArticles(Request $request): Response
{
$em = $this->getDoctrine()->getManager();
$articles = $em->getRepository(Article::class)->findAll();
return new Response($this->json($articles), Response::HTTP_OK);
}
I'm trying to figure out why the form is not validating when I create one with a pre-populated entity with values coming from a json request.
Here is the controller in Symfony with the FosRestBundle already configured:
public function createAction(Request $request)
{
$house = new House();
$house->setTitle($request->get('title'));
$house->setDescription($request->get('description'));
$house->setPostcode($request->get('postCode'));
$house->setPhoneNumber((int) $request->get('phoneNumber'));
$availability = $request->get('available') ? true : false;
$house->setAvailability($availability);
$form = $this->createCreateForm($house);
$form->handleRequest($request);
$response = new JsonResponse();
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($house);
$em->flush();
return $response->setData(array(
'success' => true
));
}
return $response->setData(array(
'success' => false,
'errors' => $this->getFormErrors($form)
));
}
private function createCreateForm(House $entity)
{
$form = $this->createForm(new HouseType(), $entity, array(
'action' => $this->generateUrl('houses_create'),
'method' => 'POST',
'csrf_protection' => false
));
return $form;
}
the yaml config file:
# app/config/config.yml
fos_rest:
param_fetcher_listener: true
body_listener: true
routing_loader:
default_format: json
exception:
enabled: true
# configure the view handler
view:
force_redirects:
html: true
formats:
json: true
xml: true
templating_formats:
html: true
# add a content negotiation rule, enabling support for json/xml for the entire website
format_listener:
enabled: true
rules:
- { path: ^/, priorities: [ json, xml, html ], fallback_format: html, prefer_extension: false }
If I execute $form->get('title')->getData() for example I can see that the form is filled correctly but still doesn't pass the validation and when I execute $this->getFormErrors($form) I just get an empty array.
Any idea on how I could debug this issue?
In order to receive json, you need to enable the body listener feature.
In your config.yml:
fos_rest:
body_listener: true
You should also check the documentation for advanced usage.
I noticed a typo when you initiate the $form: "createCreateForm" -> it should be just "createForm".
If that doesn't work check the values from $form->getData() after the "if ($form->isValid()) { " part. I suspect the framework doesn't know how to handle the request (not coming from a standard form). It that's the case then you can set manually the values on the entity and save it.
$house->setStuff($request->request->get('stuff');
...
$em->persist($house);`$em->flush();`
I am working on my yii2 api and I was looking for a way to get data from my controller actions. This is a sample of what I need on my response in json or xml.
{"success": true,
"message": {data},
"session": "U0phRm51az",
"metadata": "metadata"
}
I am getting message from the controller whereas success checks whether response is OK, session is the session data and metadata is other data.
My actions looks like these.
public function actionIndex(){
$data = array();
}
All these use the same functions so I do not want to repeat in all actions. I would like to know how to get $data from each action using afterAction or beforeSend event of the response component on my module class (not config file). If this is not possible, how can I achieve this?
If your actions return data as an array, you can add more stuff to that array in afterAction method of your controller.
public function actionIndex()
{
//...
//$data contains an array
return [
'data' => $data
];
}
public function afterAction($action, $result)
{
$result = parent::afterAction($action, $result);
$result['session'] = '...';
$result['metadata'] = '...';
return $result;
}
I have configured FOSRestBundle as following:
#FOSRestBundle
fos_rest:
param_fetcher_listener: true
body_listener: true
format_listener:
rules:
- { path: ^/, priorities: [ json, html ], fallback_format: ~, prefer_extension: true }
media_type:
version_regex: '/(v|version)=(?P<version>[0-9\.]+)/'
body_converter:
enabled: true
validate: true
view:
mime_types:
json: ['application/json', 'application/json;version=1.0', 'application/json;version=1.1']
view_response_listener: 'force'
formats:
xml: false
json: true
templating_formats:
html: true
exception:
codes:
'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404
'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT
messages:
'Symfony\Component\Routing\Exception\ResourceNotFoundException': true
allowed_methods_listener: true
access_denied_listener:
json: true
And I have this at controller:
namespace PDI\PDOneBundle\Controller\Rest;
use FOS\RestBundle\Controller\FOSRestController;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\Get;
class RepresentativeRestController extends FOSRestController
{
/**
* Get all representatives.
*
* #return array
*
* #ApiDoc(
* resource = true,
* https = true,
* description = "Get all representatives.",
* statusCodes = {
* 200 = "Returned when successful",
* 400 = "Returned when errors"
* }
* )
* #Get("/api/v1/reps")
*/
public function getRepsAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('PDOneBundle:Representative')->findAll();
if(!$entities)
{
return $this->view(null, 400);
}
return $this->view($entities, 200);
}
}
But when I try the following URL app_dev.php/api/v1/reps I got this error:
Unable to find template "". 500 Internal Server Error -
InvalidArgumentException 3 linked Exceptions: Twig_Error_Loader »
InvalidArgumentException » InvalidArgumentException »
I expect that API return a well formed JSON as the following example:
{
"id":"30000001",
"veeva_rep_id":"0055648764067SwzAAE",
"display_name":"John Know",
"avatar_url":"http://freelanceme.net/Images/default%20profile%20picture.png",
"rep_type":"VEEVA",
"username":"john#mail.com",
"first":"John",
"last":"Know",
"title":"Sales Representative",
"phone":"800-555-1212",
"email":"john#mail.com",
"territory_id":"200454001",
"inactive":"no",
"total_contacts":"6",
"total_shares":"0",
"totalViews":"0",
"lastLoginAt":"2015-05-05 15:45:57",
"lastVeevaSyncAt":"2015-05-05 15:45:57",
"createdAt":"2015-05-05 15:45:57",
"updatedAt":"2015-05-05 15:45:57"
}
Is not FOSRestBundle configured for return JSON? Why still asking for Twig template? How can I fix this?
First test:
As #Jeet suggest me I have tried using Postman (is the same as the extension he told me) and after set the header Content-Type to application/json the error turns into this
Malformed JSON
so, the FOSRestBundle is not setting up headers as should be and controller is not returning a valid JSON, how do I fix those ones?
Second test:
As suggested by #Jeet I run this test:
/**
* Get all representatives.
*
* #return array
*
* #ApiDoc(
* resource = true,
* https = true,
* description = "Get all representatives.",
* statusCodes = {
* 200 = "Returned when successful",
* 400 = "Returned when errors"
* }
* )
* #Get("/api/v1/reps")
* #View()
*/
public function getRepsAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('PDOneBundle:Representative')->findAll();
$temp = array("1", "2", "3");
$view = $this->view($temp, Codes::HTTP_OK);
return $this->handleView($view);
}
And still the same issue:
Unable to find template "". 500 Internal Server Error -
InvalidArgumentException 3 linked Exceptions: Twig_Error_Loader »
InvalidArgumentException » InvalidArgumentException »
What else can be wrong here? Did I'm missing something at configuration?
I forgot to add app/config/routing.yml and src/PDI/PDOneBundle/Resources/config/routing.yml at first so here them goes, perhaps this is the missing piece on the puzzle and give you a better idea of where the problem comes from:
#app/config/routing.yml
#PDOne
pdone:
resource: "#PDOneBundle/Resources/config/routing.yml"
template:
resource: "#TemplateBundle/Resources/config/routing.yml"
#FOSUserBundle
fos_user:
resource: "#FOSUserBundle/Resources/config/routing/all.xml"
prefix: /
#NelmioApiDocBundle:
NelmioApiDocBundle:
resource: "#NelmioApiDocBundle/Resources/config/routing.yml"
prefix: /api/doc
#SonataAdmin
admin:
resource: '#SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
prefix: /admin
_sonata_admin:
resource: .
type: sonata_admin
prefix: /admin
#src/PDI/PDOneBundle/Resources/config/routing.yml
pdone:
resource: "#PDOneBundle/Controller/"
type: annotation
prefix: /
Third test:
Definitely something is wrong with request from client side, if I use a tool like Postman and set proper headers I got the entities as I want, see pic below:
I can't find where the problem is so I desperately need someone's help here because I was already out of ideas
As guys suggested: only Accept header or extension could give you a JSON. Seems like you've got this sorted with Accept header.
In order to use extension you must tell how do you want to set format things in Symfony.
This code should give you an output you want:
namespace RestTestBundle\Controller;
use FOS\RestBundle\Controller\Annotations\View;
use FOS\RestBundle\Controller\Annotations\Get;
class YourController
{
/**
* #Get("/api/v1/reps.{_format}", defaults={"_format"="json"})
* #View()
*/
public function indexAction()
{
return array(
'status' => 'ok',
'companies' => array(
array('id' => 5),
array('id' => 7),
),
);
}
}
Edit1: if you do not want to use a View class, but pure arrays: do not forget to disallow View handling of SensioExtraBundle
sensio_framework_extra:
view: { annotations: false }
Edit2: If you do not use HTML format and only want to have a json output you can use such fonfiguration:
fos_rest:
# ....
format_listener:
rules:
- { path: ^/, priorities: [ json ], fallback_format: json, prefer_extension: true }
# ....
Explanation why you do see an error "view not found":
TL;DR: your browser send an Accept header that tells FOSRestBundle to output a 'html' variant.
Background: This bundle works mostly with Accept headers, it's a good practice to have all possible output formats that are available: html (you can test your REST API with forms you provide, lists of objects, details of objects easily this way), json, xml. Sometimes even image mime types like image/jpeg, image/png as default or json/xml as a variant (you can use base64 image representation).
Explanation: If you open up a "network" tab of a browser and check out headers it sends you will notice something like: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 which means "use in such order":
text/html
application/xhtml+xml
application/xml with priority of 0.9 which is forbidden according to your configuration
*/* with priority 0.8 which meand any format
If you look close to this is you will see that according to your configuration text/html is one of variants that your configuration has ('html') and */* is another one ('json'), but text/html has a priority of 1, while */* has a priority of 0.8, so text/html matches and FOSRestBundle tries to find a HTML representation and fails.
PS: If you post question multiple times - please make sure you watch for all responses in every thread.
You can use simply
$view = new View($form);
$view->setFormat('json');
return $this->handleView($view);
You can give response in two ways
return View::create($entities, Codes::HTTP_OK);
or
$view = $this->view($entities, Codes::HTTP_OK);
return $this->handleView($view)
FosRestBundle leverages the Accept Header. This means that it returns a response based on what you request. By accessing the route "app_dev.php/api/v1/reps", you are implicitly requesting an html format, so it tries to provide a template.
Does app_dev.php/api/v1/reps.json return what you need?
You should also test app_dev.php/api/v1/reps.xml and expect an xml output
I'm just getting started with Symfony2 and I'm trying to figure out what the correct approach is for echoing out JSON from a controller (e.g., People) for use in an ExtJS 4 grid.
When I was doing everything using a vanilla MVC approach, my controller would have method called something like getList that would call the People model's getList method, take those results and do something like this:
<?php
class PeopleController extends controller {
public function getList() {
$model = new People();
$data = $model->getList();
echo json_encode(array(
'success' => true,
'root' => 'people',
'rows' => $data['rows'],
'count' => $data['count']
));
}
}
?>
What does this kind of behavior look like in Symfony2?
Is the controller the right place for this kind of behavior?
What are the best practices (within Symfony) for solving this kind of problem?
Is the controller the right place for this kind of behavior?
Yes.
What does this kind of behavior look like in Symfony2?
What are the best practices (within Symfony) for solving this kind of problem?
In symfony it looks pretty much alike, but there are couple of nuances.
I want to suggest my approach for this stuff. Let's start from routing:
# src/Scope/YourBundle/Resources/config/routing.yml
ScopeYourBundle_people_list:
pattern: /people
defaults: { _controller: ScopeYourBundle:People:list, _format: json }
The _format parameter is not required but you will see later why it's important.
Now let's take a look at controller
<?php
// src/Scope/YourBundle/Controller/PeopleController.php
namespace Overseer\MainBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class PeopleController extends Controller
{
public function listAction()
{
$request = $this->getRequest();
// if ajax only is going to be used uncomment next lines
//if (!$request->isXmlHttpRequest())
//throw $this->createNotFoundException('The page is not found');
$repository = $this->getDoctrine()
->getRepository('ScopeYourBundle:People');
// now you have to retrieve data from people repository.
// If the following code looks unfamiliar read http://symfony.com/doc/current/book/doctrine.html
$items = $repository->findAll();
// or you can use something more sophisticated:
$items = $repository->findPage($request->query->get('page'), $request->query->get('limit'));
// the line above would work provided you have created "findPage" function in your repository
// yes, here we are retrieving "_format" from routing. In our case it's json
$format = $request->getRequestFormat();
return $this->render('::base.'.$format.'.twig', array('data' => array(
'success' => true,
'rows' => $items,
// and so on
)));
}
// ...
}
Controller renders data in the format which is set in the routing config. In our case it's the json format.
Here is example of possible template:
{# app/Resourses/views/base.json.twig #}
{{ data | json_encode | raw }}
The advantage of this approach (I mean using _format) is that it if you decide to switch from json to, for example, xml than no problem - just replace _format in routing config and, of course, create corresponding template.
I would avoid using a template to render the data as the responsibility for escaping data etc is then in the template. Instead I use the inbuilt json_encode function in PHP much as you have suggested.
Set the route to the controller in the routing.yml as suggested in the previous answer:
ScopeYourBundle_people_list:
pattern: /people
defaults: { _controller: ScopeYourBundle:People:list, _format: json }
The only additional step is to force the encoding in the response.
<?php
class PeopleController extends controller {
public function listAction() {
$model = new People();
$data = $model->getList();
$data = array(
'success' => true,
'root' => 'people',
'rows' => $data['rows'],
'count' => $data['count']
);
$response = new \Symfony\Component\HttpFoundation\Response(json_encode($data));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
?>
To use return new JsonResponse(array('a' => 'value', 'b' => 'another-value'); you need to use the right namespace:
use Symfony\Component\HttpFoundation\JsonResponse;
As described here: http://symfony.com/doc/current/components/http_foundation/introduction.html#creating-a-json-response
Instead of building your own response you can also use the built-in JsonResponse.
You define the route like in the other answers suggested:
ScopeYourBundle_people_list:
pattern: /people
defaults: { _controller: ScopeYourBundle:People:list, _format: json }
And use the new response type:
<?php
class PeopleController extends controller {
public function listAction() {
$model = new People();
$data = $model->getList();
$data = array(
'success' => true,
'root' => 'people',
'rows' => $data['rows'],
'count' => $data['count']
);
return new \Symfony\Component\HttpFoundation\JsonResponse($data);
}
}
For more information see the api or the doc (version 2.6).
Simple. Use FOSRestBundle and only return the People object from the controller.
use
return JsonResponse($data, StatusCode, Headers);