HTTP 500 error with FOSRestBundle when getting an entity with relations - php

I'm starting with FOSRestBundle and when I get the values of an entity without relations and display it in the browser, I have no problem. But when I try to get an entity with relations, it shows me an error with code: 500.
Here is the code:
app/config/config.yml:
fos_rest:
routing_loader:
default_format: json
param_fetcher_listener: true
body_listener: true
format_listener: true
view:
view_response_listener: 'force'
ApiRestBundle/Controller/UserController (this works fine)
/**
* #return array
* #Rest\Get("/users")
* #Rest\View()
*/
public function getUsersAction()
{
$response = array();
$em = $this->getDoctrine()->getManager();
$users = $em->getRepository('CASUsuariosBundle:User')->findAll();
$view = $this->view($users);
return $this->handleView($view);
}
APIRestBunde/Controller/CategoryController (this doesn't works)
/**
* #return array
* #Rest\Get("/categories")
* #Rest\View()
*/
public function getCategoriesAction()
{
$response = array();
$em = $this->getDoctrine()->getManager();
$categories = $em->getRepository('CASEventBundle:Category')->findAll();
$view = $this->view($categories);
return $this->handleView($view);
}
the error code:
{"error":{"code":500,"message":"Internal Server
Error","exception":[{"message":"Notice: Undefined index:
name","class":"Symfony\Component\Debug\Exception\ContextErrorException","trace":[{"namespace":"","short_class":"","class":"","type":"","function":"","file":"C:\xampp\htdocs\CASPruebas\vendor\doctrine\orm\lib\Doctrine\ORM\Persisters\BasicEntityPersister.php","line":1758,"args":[]}...

your problem is a little bit complicated to solve.
This error code can mean a lot of different things!
But I think it is not directly a FOSRestBundle problem. Maybe you have a relation problem on your Category entity...
What is the result of this: doctrine:schema:validate
Edit : Maybe it will be more simple to solve it if you give us the full error code.

Related

Symfony 4 implement REST API

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);
}

Can't guess how to get a Doctrine instance

In a symfony projects, I'm trying to persist a line of an association table (profil_role) composed of two objects (profil and role).
First, I developed The create action in the ProfilRoleController of the second project this way:
/** #var Roles $role */
$em = $this->getDoctrine()->getManager('main_project');
$role = $em->getRepository("MyBundle\Entity\Roles")->find($roleId);
$profil = $em->getRepository("MyBundle\Entity\Profil")->find($profilId);
$profilRole = new ProfilRoles();
$profilRole->setRoleId($role->getId());
$profilRole->setProfilId($profil->getId());
$em->persist($profilRole);
$em->flush();
This part of code, call then the post entity action present in the main project:
/**
* #Rest\View(statusCode=Response::HTTP_CREATED)
* #Rest\Post("/profil_roles")
*/
public function postEntityAction(ProfilRoles $profilRole)
{
$em = $this->getDoctrine()->getManager();
$em->persist($profilRole);
$em->flush();
return $profilRole;
}
When I try to execute my code i'm getting this king of error:
Execution failed for request: POST /api/profil_roles? HTTP/1.1 {"profil":{"id":"12"},"role":{"id":"3"}}: HTTPCode 500, body {"code":500,"message":"Unable to guess how to get a Doctrine instance from the request information."}
I've tried to use the #ParamConverter annotation, but I don't how to use it my case.
try this:
public function postEntityAction() {
$postData = $request->request->all();
$profileRole = $postData['profile_role']
Instead of this:
public function postEntityAction(ProfilRoles $profilRole)
#AlessandroMinoccheri I've tried to be inspired by your reply to do this and i'ts workin, i don't know if it's the correct way.
/**
* #param ProfilRoles $profilRole
* #param Request $request
* #return ProfilRoles
* #Rest\View(statusCode=Response::HTTP_CREATED)
* #Rest\Post("/profil_roles")
*/
public function postEntityAction(Request $request)
{
$profilRole = new ProfilRoles();
$em = $this->getDoctrine()->getManager();
$requete = $request->request->all();
$profilRole->setProfilId($requete['profil']['id']);
$profilRole->setRoleId($requete['role']['id']);
$em->persist($profilRole);
$em->flush();
return $profilRole;
}

How to handle invalid forms for a REST POST?

I am developing a website that offers a REST service. All the GET actions are OK and rendered using a .json.twig template, but I am having a hard time understanding how to output form errors if the query made to create a new record is not valid.
If I try to do a simple
return $form;
I get the following exception from SF:
"exception":[{"message":"Unable to find template \"SomeBundle:Customers:postCustomer.json.twig\"}]
The template does not exist, that's true, but I have no idea how to create one in JSON format to tell the requestor that his query is incomplete / malformed.
If I try anything else dealing with views but without specifying a template, the result is the same. Is there a way to do that automatically so that if the form is modified the change are reflected as well in the error ?
Or a way to tell FOSRestBundle / JMSSerializerBundle to deal with the serialization themselves ? Before switching to Twig responses the error was nicely handled, and I'd like to have that back, along with the Twig templates for normal operations.
For information, my current controller's action is:
/**
* #ApiDoc(
* resource=false,
* input="SomeBundle\Form\CustomerType",
* description="Create a new customer",
* section="Customers",
* statusCode={
* 201="Action successful",
* 403="Authorization required but incorrect / missing information or insufficient rights",
* 500="Returned if action failed for unknown reasons"
* }
* )
*
* --View(template="SomeBundle:Customers:add.json.twig", templateVar="form", statusCode=400)
* #View(templateVar="form", statusCode=400)
* #param Request $request
* #return \FOS\RestBundle\View\View
*/
public function postCustomerAction(Request $request) {
$data = json_decode($request->getContent(), true);
$manager = $this->getManager();
$customer = new Customer();
$form = $this->getForm($customer);
//$form->submit($data);
//$manager->create($customer);
// $form->handleRequest($request);
// if ($form->isSubmitted() && $form->isValid()) {
// $manager->create($customer);
//
// return $this->redirectView($this->generateUrl('api_get_customer_internal', ['uuid' => $customer->getInternalUuid()], true),
// 201);
// }
return $form;
//return $this->handleView($this->view($form, 400));
//return \FOS\RestBundle\View\View::create($form, 400);
}
And the FOSRestBundle configuration:
fos_rest:
param_fetcher_listener: true
body_listener: true
format_listener:
enabled: true
view:
view_response_listener: 'force'
formats:
json: true
templating_formats:
json: true
force_redirects:
html: true
failed_validation: HTTP_BAD_REQUEST
default_engine: twig
routing_loader:
include_format: false
default_format: json
serializer:
serialize_null: true
sensio_framework_extra:
view:
annotations: true
Thanks to jorge07 at https://github.com/FriendsOfSymfony/FOSRestBundle/issues/1620 I was able to find a way to circumvent that in a rather proper way (at least IMHO), here's the updated Controller action (no change in the fosrestbundle settings required):
/**
* #Route("/customers")
* #ApiDoc(
* resource=false,
* input="NetDev\CoreBundle\Form\CustomerType",
* description="Create a new customer",
* section="Customers",
* statusCode={
* 201="Action successful",
* 403="Authorization required but incorrect / missing information or insufficient rights",
* 500="Returned if action failed for unknown reasons"
* }
* )
*
* #View(template="NetDevRestBundle:Common:form_error.json.twig", templateVar="errors", statusCode=400)
*
* #RequestParam(name="customerName", nullable=false)
* #RequestParam(name="customerIndex", nullable=false)
*
* #return \FOS\RestBundle\View\View
*/
public function postCustomerAction(ParamFetcher $fetcher)
{
$customer = new Customer();
$form = $this->getForm($customer);
$form->submit($fetcher->all(), true);
if ($form->isValid()) {
$manager = $this->getManager();
$manager->create($customer);
return $this->redirectView($this->generateUrl('api_get_customer_internal', ['uuid' => $customer->getInternalUuid()], true), 201);
}
$err = $form->getErrors();
$errorsList = [];
foreach ($err as $it) {
$errorsList[(string)$it->getOrigin()->getPropertyPath()] = $it->getMessage();
}
return $this->view([$errorsList])
->setTemplateVar('errors')
;
}

Symfony Doctrine Flush Entity

I have entity SourceElanceProfileImport, and I create this entity in action and set some data and when I flush have error:
Catchable Fatal Error: Object of class Proxies\__CG__\Artel\ProfileBundle\Entity\Teams could not be converted to string
and question, why if I find teams like this I have Proxies object
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
I tra hard code write id and have nor entity Teams
$id = '2';
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
// $team entity Teams, not Proxies
what’s happened not right in this action?
action:
public function elanceProfileAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$url = $request->get('url');
$email = $request->get('email');
$firstName = $request->get('firstName');
$user = $em->getRepository('ArtelProfileBundle:Users')->getCompanyByEmail($request->get('email'));
$id = $user[0]->getTeams()->getId();
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
if (!empty($user)) {
$elance_import = new SourceElanceProfileImport();
$hepler = $this->container->get('artel.profile.additional_function');
$pass = $hepler->generatePassword();
$elance_import
->setEmail($email)
->setSecurityHash(sha1($pass))
->setElanceUrl($url)
->setTeamId($team)
;
$em->persist($elance_import);
$em->flush();
and entity:
class SourceElanceProfileImport
{
/**
* #var \Teams
*
* #ORM\ManyToOne(targetEntity="Teams")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="team_id", referencedColumnName="id", onDelete="CASCADE")
* })
*/
private $teamId;
/**
* #var integer
*
* #ORM\Column(name="team_id", type="integer")
*/
public $team;
when I add __toString to entity Teams I have:
public function __toString()
{
return $this->company;
}
Error in one or more bulk request actions:
index: /aog/sourceelanceprofileimport/5 caused MapperParsingException[failed to parse [team_id]]; nested: NumberFormatException[For input string: "Gutkowski LLC"];
why in another entity work fine, what’s wrong I don’t know ((
update
I solved but I think this is solved not fine and that’s one I don’t delete this question
I add in User entity:
private function _load()
{
// lazy loading code
}
/**
* Get teams
*
* #return \Artel\ProfileBundle\Entity\Teams
*/
public function getLoadTeams()
{
$this->_load();
return parent::getId();
}
and in my action
$user = $em->getRepository('ArtelProfileBundle:Users')->getCompanyByEmail($request->get('email'));
$id = $user[0]->getLoadTeams();
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
than I have object Teams in variable $team, BUT when I flush I still have error:
Catchable Fatal Error: Object of class Artel\ProfileBundle\Entity\Teams could not be converted to string
I solved, but problem it was in persist entity and in config fos_elastic
I still have Proxies Teams entity but in action
$user = $em->getRepository('ArtelProfileBundle:Users')->getCompanyByEmail($request->get('email'));
$id = $user[0]->getTeams()->getId();
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
if (!empty($user)) {
$elance_import = new SourceElanceProfileImport();
$hepler = $this->container->get('artel.profile.additional_function');
$pass = $hepler->generatePassword();
$elance_import
->setEmail($email)
->setSecurityHash(sha1($pass))
->setElanceUrl($url);
$em->persist($elance_import);
$elance_import->setTeamId($team);
$em->flush();
and flush fine entity create and have id Teams (Proxies object Teams)
and teamId this is relationship with entity Teams and in fos_elastica need write like this:
sourceelanceprofileimport:
mappings:
id:
type: integer
elance_url:
type: string
full_name:
type: string
status:
type: string
created:
type: date
approved_at:
type: date
imported_at:
type: date
team_id:
type: "nested"
properties:
id: ~
persistence:
# the driver can be orm, mongodb or propel
# listener and finder are not supported by
# propel and should be removed
driver: orm
model: Artel\ProfileBundle\Entity\SourceElanceProfileImport
provider: ~
listener: ~
finder: ~
and I have entity in my DB and Elastic DB
First, you will have proxies every time an entity is not completely populated. Proxies are used to return partial entities.
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/partial-objects.html
Looking your code I noticed this:
$team = $em->getRepository('ArtelProfileBundle:Teams')->findOneById($id);
But you can do the same in this way:
$team = $em->find('ArtelProfileBundle:Teams', $id); // EntityManager::find has 2 arguments: entity and id.
Underneath you do this:
$user = $em->getRepository('ArtelProfileBundle:Users')->getCompanyByEmail($request->get('email'));
Did you implement some custom code on getCompanyByEmail? If not, you can use Doctrine's methods:
$user = $em->getRepository('ArtelProfileBundle:Users')->findOneByEmail($request->get('email'));
Those are magic methods: findOneBy or findBy. One returns a single object or NULL, the other one returns an array with results.
You can also do this:
$user = $em->getRepository('ArtelProfileBundle:Users')->findOneBy(['email' => $request->get('email')]); // you can add more key/value filters on the array
Now just to be sure, did you implement APC as bytecode cache? Because when I make changes on my entities I usually need to flush APC (restarting the webserver) to force the cache to regenerate it. If misterious errors occur maybe APC should be restarted.
Now, regarding the flush error, did you implement a __toString method on any entity? Probably it's not returning a string.

How to return or display data in JSON format using FOSRestBundle

I am working in a Restful API using Symfony2 and FOSRestBundle. I have read view layer docs but is not clear to me how to handle output for API. What I want to achieve is simple: display or return or output the result as valid JSON. This is what I have at controller:
<?php
/**
* RestAPI: Company.
*/
namespace PDI\PDOneBundle\Controller\Rest;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Request\ParamFetcherInterface;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\Get;
class CompanyRestController extends FOSRestController
{
/**
* Gets all companies.
*
* #return array
*
* #ApiDoc(
* resource = true,
* https = true,
* description = "Gets all companies",
* statusCodes = {
* 200 = "Returned when successful",
* 400 = "Returned when errors"
* }
* )
* #Get("/api/v1/companies")
*
*/
public function getCompaniesAction()
{
$response = array();
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('PDOneBundle:Company')->findAll();
if ($entities) {
foreach ($entities as $entity) {
$response['companies'][] = [
'id' => $entity->getId(),
'createdAt' => $entity->getCreatedAt(),
'updatedAt' => $entity->getUpdatedAt(),
'name' => $entity->getName(),
'logo_url' => $entity->getLogoUrl(),
'division' => $entity->getDivision(),
'inactive' => $entity->getInactive(),
];
}
$response['status'] = 'ok';
} else {
$response['status'] = 'error';
}
return $response;
}
}
If I try this URL: /app_dev.php/api/v1/companies.json I got 404 error:
{"code":404,"message":"No route found for \"GET\/api\/v1\/companies.json\""}
If I try this URL: https://reptool.dev/app_dev.php/api/v1/companies error turns on:
Unable to find template "". 500 Internal Server Error -
InvalidArgumentException 3 linked Exceptions: Twig_Error_Loader »
InvalidArgumentException » InvalidArgumentException »
I've also check FOSRestBundleByExample but didn't get much help.
What I am missing here? How do I achieve what I need? Any advice?
FOSRest Config
I forgot to add the FOSRestBundle at config.yml:
#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
I feel your pain. I had troubles getting started as well. One important place to start is the config. Here's what I use in my implementation.
fos_rest:
param_fetcher_listener: 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
format_listener:
rules:
- { path: ^/, priorities: [ json, html ], fallback_format: ~, prefer_extension: true }
media_type:
version_regex: '/(v|version)=(?P<version>[0-9\.]+)/'
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
body_listener: true
In the format_listener if you want JSON to be the default response, make sure it's set first in priorities. Otherwise your header will need to include Accept: application/json every time. This may be why you're getting a twig error as it's trying to use twig to render an HTML output.
Also, make sure you have a serializer like http://jmsyst.com/bundles/JMSSerializerBundle installed and included in your AppKernal.
In your controller I found it easiest to extend the FOSRestController like you did, but also return a view object instead of creating the array yourself. The serializer will handle all of that for you.
/**
* RestAPI: Company.
*/
namespace PDI\PDOneBundle\Controller\Rest;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Request\ParamFetcherInterface;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\Get;
class CompanyRestController extends FOSRestController
{
/**
* Gets all companies.
*
* #return array
*
* #ApiDoc(
* resource = true,
* https = true,
* description = "Gets all companies",
* statusCodes = {
* 200 = "Returned when successful",
* 400 = "Returned when errors"
* }
* )
* #Get("/api/v1/companies")
*
*/
public function getCompaniesAction()
{
$response = array();
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('PDOneBundle:Company')->findAll();
if(!$entities)
{
return $this->view(null, 400);
}
return $this->view($entities, 200);
}
}
I hope this helps a little.

Categories