Symfony is awesome, that's for sure. It includes a lot of tools that you may use to improve the development times of your project, automatizing very important stuff like the generation of forms from tables that exist already, so you don't need to write them as doctrine entities from scratch. Using the mentioned tools may lead of course to some exceptions to appear in your project if you don't fully understand what the Symfony commands did.
In the case of "Object of class Proxies\__CG__\App\Entity could not be converted to string", it's quite probably that you generated the doctrine entities from a database automatically, and therefore, the entities have not the magic __toString
method. In this article, I'll explain to you how to easily prevent this exception from appearing.
Context
In order to replicate this error just like the use that you are having right now, we will use a very basic example. We do have 2 tables with a ManyToOne relationship:
When the user registers a new person in the database, it's necessary to provide the state in which the user lives, quite simple. This information, converted to doctrine entities, look respectively like this, for the Person.php
entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Person
*
* @ORM\Table(name="person")
* @ORM\Entity
*/
class Person
{
/**
* @var int
*
* @ORM\Column(name="id", type="bigint", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string|null
*
* @ORM\Column(name="first_name", type="string", length=255, nullable=true, options={"default"="NULL"})
*/
private $firstName;
/**
* @var string|null
*
* @ORM\Column(name="last_name", type="string", length=255, nullable=true, options={"default"="NULL"})
*/
private $lastName;
/**
* @var \States
*
* @ORM\ManyToOne(targetEntity="States")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="states_id", referencedColumnName="id")
* })
*/
private $state;
public function getId(): ?string
{
return $this->id;
}
public function getFirstName(): ?string
{
return $this->firstName;
}
public function setLastName(?string $firstName): self
{
$this->firstName = $firstName;
return $this;
}
public function getLastName(): ?string
{
return $this->lastName;
}
public function setLastName(?string $lastName): self
{
$this->lastName = $lastName;
return $this;
}
public function getState(): ?State
{
return $this->state;
}
public function setState(?State $state): self
{
$this->state = $state;
return $this;
}
}
And the State.php
entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* State
*
* @ORM\Table(name="state")
* @ORM\Entity
*/
class State
{
/**
* @var int
*
* @ORM\Column(name="id", type="bigint", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255, nullable=false)
*/
private $name;
public function getId(): ?string
{
return $this->id;
}
public function getName(): ?string
{
return $this->nombre;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
}
In our case, they were automatically created using doctrine:mapping:import
(using reverse engineering). Then, we created the CRUD through generate:doctrine:crud
. This would generate the views where the user should be able to see all the items in the database and the form to create and edit the Person entities:
<?php
namespace App\Controller;
use App\Entity\Person;
use App\Form\PersonType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/person")
*/
class PersonController extends AbstractController
{
/**
* @Route("/", name="person_index", methods={"GET"})
*/
public function index(): Response
{
// .. //
}
/**
* @Route("/new", name="person_new", methods={"GET","POST"})
*/
public function new(Request $request): Response
{
$persona = new person();
$form = $this->createForm(PersonType::class, $persona);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($persona);
$entityManager->flush();
return $this->redirectToRoute('person_index');
}
return $this->render('person/new.html.twig', [
'persona' => $persona,
'form' => $form->createView(),
]);
}
/**
* @Route("/{id}", name="person_show", methods={"GET"})
*/
public function show(person $persona): Response
{
// .. //
}
/**
* @Route("/{id}/edit", name="person_edit", methods={"GET","POST"})
*/
public function edit(Request $request, person $persona): Response
{
// .. //
}
/**
* @Route("/{id}", name="person_delete", methods={"DELETE"})
*/
public function delete(Request $request, person $persona): Response
{
// .. //
}
}
Now, if the user visits either the edit or new route, the exception will appear.
Cause and solution
The only reason why this error appears is because there's no magic __toString
method in the State
entity:
<?php
class MyEntity
{
public function __toString() {
return $this->somePropertyOrPlainString;
}
}
Even though we are in the Person form. Basically, because when the form is rendered, Symfony doesn't know what should be displayed on the select field causing the error, so in this case, as the select (state_id) should allow the user to select one of the states registered in the database, when the entities in the list are printed as a string, we should return the name property of the entity returning its value in the magic method:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* State
*
* @ORM\Table(name="state")
* @ORM\Entity
*/
class State
{
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255, nullable=false)
*/
private $nombre;
// Register Magic Method to Print the name of the State e.g California
public function __toString() {
return $this->name;
}
}
Allowing the form to render a list of states without any exception:
Happy coding ❤️!