I am building a project management tool for my team in Symfony 3. I am using ramsey/uuid-doctrine for the IDs in the system.
So far, this hasn't been a problem with One-to-Many or Many-to-One associations, but when I try to persist a One-to-One association, Doctrine is not converting the associated entity to its UUID, and instead leaving a null value in the SQL.
In this example, I have a WikiPage which can have multiple WikiPageVersions. The WikiPage has a One-to-Many association with WikiPageVersion (the versions property: for all the versions of the page), but also a One-to-One (Unidirectional) association with WikiPageVersion (the currentVersion property: for the, well, current version).
The WikiPage also has a Many-to-One associations with Project (to track which project the wiki page is for) and that property is populated correctly.
The WikiPage Entity
/**
* #ORM\Table(name="wiki_page")
* #ORM\Entity(repositoryClass="AppBundle\Repository\Project\WikiPageRepository")
*/
class WikiPage
{
/**
* #var Uuid
* #ORM\Id
* #ORM\Column(type="uuid")
*/
protected $id;
/**
* #var Project
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Project\Project", inversedBy="wikiPages")
* #ORM\JoinColumn(name="project_id", referencedColumnName="id")
*/
protected $project;
/**
* #var string
* #ORM\Column(name="title", type="text")
* #Assert\NotBlank()
*/
protected $title;
/**
* #var HiveWikiPageVersion
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Project\WikiPageVersion", fetch="EAGER")
*/
protected $currentVersion;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Project\WikiPageVersion", mappedBy="wikiPage", cascade={"persist", "remove"})
*/
protected $versions;
// -- Class Methods
}
The WikiPageVersion Entity
/**
* #ORM\Table(name="wiki_page_version")
* #ORM\Entity(repositoryClass="AppBundle\Repository\Project\WikiPageVersionRepository")
*/
class WikiPageVersion
{
/**
* #var Uuid
* #ORM\Id
* #ORM\Column(type="uuid")
*/
protected $id;
/**
* #var WikiPage
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Project\WikiPage", inversedBy="versions")
* #ORM\JoinColumn(name="wiki_page_id", referencedColumnName="id")
* #Assert\NotBlank()
*/
protected $wikiPage;
/**
* #var string
* #ORM\Column(name="content", type="text")
* #Assert\NotBlank()
*/
protected $content;
/**
* #var string
* #ORM\Column(name="version_comment", type="string", length=255)
* #Assert\NotNull()
*/
protected $versionComment;
/**
* #var HiveWikiPageVersion
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Project\WikiPageVersion")
* #ORM\JoinColumn(name="previous_version", referencedColumnName="id")
* #Assert\Type(type="Odev\Hive\Model\Entity\Project\WikiPageVersion")
*/
protected $previousVersion;
/**
* #var \DateTimeInterface
* #ORM\Column(name="created", type="datetime")
* #Assert\NotBlank()
*/
protected $created;
/**
* #var User
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* #ORM\JoinColumn(name="created_by", referencedColumnName="id")
* #Assert\NotBlank()
*/
protected $createdBy;
}
// -- Methods
Troubleshooting So Far
I can confirm that before persisting the WikiPage, that the WikiPageVersion has been associated.
I can duplicate this same behaviour with my Project and WikiPageVersion entites (Project has a One-to-One association with WikiPage for the wiki homepage and WikiPageVersion has a One-to-One association with itself for the previous version). Only the One-to-One associations are not converting the UUID.
I have the same problem when trying to persist from Symfony Controller or from when loading Doctrine fixtures.
I have tried to trace down where the conversion occurs using xdebug, but I am not that versed in using a debugger and after 20 minutes of stepping through the debugger, I can't find where the conversion for that field happens. I'm either skipping past the loop it happens in or just missing it. After wasting an hour skipping through runs trying to find the problem, I had to give up.
Here is the error I get from Doctrine when I try to perist the WikiPage:
[Doctrine\DBAL\Exception\NotNullConstraintViolationException]
An exception occurred while executing 'INSERT INTO wiki_page (id, project_home, title, project_id, current_version_id) VALUES (?, ?, ?, ?, ?)' with params ["ddc1f51a-f5d9-489f-89bb-cd79f3393af0", 1
, "Technical Reviews Wiki", "5138b185-b10b-48ac-a102-bdea1139c911", null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'current_version_id' cannot be null
and the exception trace (this exception is from saving during fixtures loading):
Exception trace:
() at /home/vagrant/hive/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php:112
Doctrine\DBAL\Driver\AbstractMySQLDriver->convertException() at /home/vagrant/hive/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php:128
Doctrine\DBAL\DBALException::driverExceptionDuringQuery() at /home/vagrant/hive/vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php:177
Doctrine\DBAL\Statement->execute() at /home/vagrant/hive/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php:281
Doctrine\ORM\Persisters\Entity\BasicEntityPersister->executeInserts() at /home/vagrant/hive/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1014
Doctrine\ORM\UnitOfWork->executeInserts() at /home/vagrant/hive/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:378
Doctrine\ORM\UnitOfWork->commit() at /home/vagrant/hive/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:356
Doctrine\ORM\EntityManager->flush() at /home/vagrant/hive/src/Odev/Hive/Infrastructure/AppBundle/DataFixtures/ORM/LoadProjectData.php:89
Odev\Hive\Infrastructure\AppBundle\DataFixtures\ORM\LoadProjectData->load() at /home/vagrant/hive/vendor/doctrine/data-fixtures/lib/Doctrine/Common/DataFixtures/Executor/AbstractExecutor.php:121
Doctrine\Common\DataFixtures\Executor\AbstractExecutor->load() at /home/vagrant/hive/vendor/doctrine/data-fixtures/lib/Doctrine/Common/DataFixtures/Executor/ORMExecutor.php:88
Doctrine\Common\DataFixtures\Executor\ORMExecutor->Doctrine\Common\DataFixtures\Executor\{closure}() at n/a:n/a
call_user_func() at /home/vagrant/hive/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:233
Doctrine\ORM\EntityManager->transactional() at /dev/shm/app/cache/dev/appDevDebugProjectContainer.php:5645
DoctrineORMEntityManager_00000000015ce1f5000000002a2c79364ae5d79093a662a969d1540330e84087->transactional() at /home/vagrant/hive/vendor/doctrine/data-fixtures/lib/Doctrine/Common/DataFixtures/Executor/ORMExecutor.php:90
Doctrine\Common\DataFixtures\Executor\ORMExecutor->execute() at /home/vagrant/hive/vendor/doctrine/doctrine-fixtures-bundle/Command/LoadDataFixturesDoctrineCommand.php:118
Doctrine\Bundle\FixturesBundle\Command\LoadDataFixturesDoctrineCommand->execute() at /home/vagrant/hive/vendor/symfony/symfony/src/Symfony/Component/Console/Command/Command.php:262
Symfony\Component\Console\Command\Command->run() at /home/vagrant/hive/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:848
Symfony\Component\Console\Application->doRunCommand() at /home/vagrant/hive/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:189
Symfony\Component\Console\Application->doRun() at /home/vagrant/hive/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Console/Application.php:80
Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /home/vagrant/hive/vendor/symfony/symfony/src/Symfony/Component/Console/Application.php:120
Symfony\Component\Console\Application->run() at /home/vagrant/hive/bin/console:29
At this point I am lost - if there is any suggestions of where I should be looking or if anyone else has run into this problem, I would love and guidance that could be provided.
Update
As requested by matteo:
Here is the load method of the fixture that creates the records and throws the error.
/**
* {#inheritDoc}
* #param ObjectManager $manager
*/
public function load(ObjectManager $manager)
{
// Create ODEV Project Section
$section = new ProjectSection(
Uuid::uuid4(),
'ODEV',
'ODEV specific projects'
);
$manager->persist($section);
$this->addReference('section-odev', $section);
// Create Technical Review Project -> public
$project = new Project(
Uuid::uuid4(),
'techreview',
$section,
'Technical Reviews',
'Technical Reviews for Work Requests',
false,
false,
$this->getReference('user-system'),
$this->getReference('user-system'),
'',
$this->getReference('user-system')
);
// VarDumper::dump($project->getWikiHome()->getId());
// VarDumper::dump($project->getCreatedBy()->getId());
$manager->persist($project);
$this->addReference('project-tech-review', $project);
$manager->flush();
}
The two VarDumper::dump() commands were to confirm that the associations were getting created.
The actual WikiPage gets generated in the Project's constructor and the WikiPageVersion is generated in the WikiPages's constructor.
Here is Project's constructor:
public function __construct(
string $id,
string $identifier,
ProjectSection $section,
string $name,
string $description,
bool $private,
bool $sensitive,
User $owner,
User $contact,
string $homepage,
User $createdBy
) {
$this->id = Uuid::fromString($id);
$this->identifier = $identifier;
$this->projectSection = $section;
$this->name = $name;
$this->description = $description;
$this->private = $private;
$this->sensitive = $sensitive;
$this->owner = $owner;
$this->contact = $contact;
$this->homepage = $homepage;
$this->createdBy = $createdBy;
$this->created = new \DateTimeImmutable();
$this->updatedBy = $createdBy;
$this->updated = new \DateTimeImmutable();
$this->archived = false;
$this->workRequest = null;
// set up collections
$this->teamMembers = new ArrayCollection();
$this->issues = new ArrayCollection();
$this->folders = new ArrayCollection($this->defaultFolders());
$this->wikiHome = $this->defaultWikiPage();
$this->wikiPages = new ArrayCollection([$this->wikiHome]);
$this->labels = new ArrayCollection($this->defaultLabels());
$this->milestones = new ArrayCollection($this->defaultMilestones());
}
protected function defaultWikiPage(): WikiPage
{
return new WikiPage(Uuid::uuid4(), $this, $this->name.' Wiki', '', $this->createdBy);
}
And the constructor of WikiPage:
public function __construct(string $id, Project $project, string $title, string $content, User $createdBy)
{
$this->id = Uuid::fromString($id);
$this->project = $project;
$this->title = $title;
$this->content = $content;
$this->created = new \DateTimeImmutable();
$this->createdBy = $createdBy;
$this->currentVersion = $this->createFirstVersion($content, $createdBy);
$this->versions = new ArrayCollection([$this->currentVersion]);
}
protected function createFirstVersion(string $content, User $createdBy)
{
return new WikiPageVersion(Uuid::uuid4(), $this, $content, $createdBy, 'Page Created');
}
Hope that helps.
When WikiPage Entity tries to INSERT it is trying to insert all its properties. Do a check for version and if === null unset the key index. then when the
INSERT fires the parm array is only 4 keys.
Related
I try this code: Symfony: ManyToMany table extra columns
This new field is because the user have a diferent role for diferent events.
In the fist time i can add add the new fields on table user.evento, but now i cant. And i dont find the problem.
I get this fail:
An exception occurred while executing 'INSERT INTO user_evento (user_id, evento_id) VALUES (?, ?)' with params [3, 18]: SQLSTATE[HY000]: General error: 1364 Field 'tipoinvitado' doesn't have a default value
why not detecting the other 2 fields ?
capture of my sql update with the new fields
my entity UserHasEvento:
/**
* #ORM\Entity
* #ORM\Table(name="user_evento")
*/
class UserHasEvento
{
/**
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Evento", cascade={"persist"}, fetch="LAZY")
* #ORM\JoinColumn(name="evento_id", referencedColumnName="id")
*/
private $eventos;
/**
* #ORM\ManyToOne(targetEntity="User", cascade={"persist","remove"}, fetch="LAZY" )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id",nullable=true)
*/
private $users;
/**
* #ORM\Column(name="tipoinvitado", type="string", length=255, nullable=true)
*/
private $tipoinvitado;
/**
* #var \DateTime|null
*
* #ORM\Column(name="fechainscripcion", type="datetime", nullable=true)
*/
private $fechainscripcion;
public function setTipoinvitado(string $tipoinvitado): self
{
$this->tipoinvitado = $tipoinvitado;
return $this;
}
public function getTipoinvitado(): string
{
return $this->tipoinvitado;
}
public function getFechainscripcion()
{
return $this->fechainscripcion;
}
public function setFechainscripcion($fechainscripcion): self
{
$this->fechainscripcion = $fechainscripcion;
return $this;
}
public function __construct()
{
$this->createdAt= new \DateTime('now');
}
}
My controller update.
public function asignarUsuario(Request $request, Evento $evento, Userhasevento $userhasevento){
$user_repo = $this->getDoctrine()->getRepository(User::class);
$users = $user_repo->findAll();
$evento = $this->getDoctrine()->getRepository(Evento::class)->findOneById($evento);
$form = $this->createForm(AsignarEventoFormType::class, $evento);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$users = $evento->GetUsers();
foreach($users as $user){
$user->addEvento($evento);
$evento->addUser($user);
$userhasevento->SetTipoinvitado('normal');
$userhasevento->setFechainscripcion(new \DateTime('now'));
$em = $this->getDoctrine()->getManager();
$em->persist($evento);
$em->persist($userhasevento);
$em->flush();
}
return $this->redirect($this->generateUrl('evento_detalle', ['id' => $evento->getId()]));
}
return $this->render('evento/AsignarEvento.html.twig',[
'form' => $form->createView()
]);
}
Is your field $tipoinvitado disappear?
You might have changed your relation and the null boolean of $tipoinvitado at some point. You made your migration after changing any relation?
This can happen when you change your relations and had already data stored in you DB in a field that disappear because of you relation change.
I might be able to help you, don't hesitate giving more info
You have the answer in the error, the $tipoinvitado field has the annotation nullable=false which is a not null constraint (prevent storing null value), so it should absolutely have a value.
So you either give it a default value, give a value or change it to nullable=true.
I have several classes using a Taggable trait to set up a tag system common to several doctrine entities (Project, Note, ...).
The relationship between these entities and these tags is a ManyToMany relationship that I can not make multi-directional.
My problem: When I delete a Project entity, it is removed from the project table, but the relationships in the project_tag table between this project and the tags are not deleted. Then, if I create a new Project entity, an exception is thrown.
An exception exists while executing 'INSERT INTO project_tag (project_id, tag_id) VALUES (?,?)' With params [2, 4]:
SQLSTATE [23000]: Integrity constraint violation: 19 UNIQUE constraint failed: project_tag.project_id, project_tag.tag_id
Entities :
Tag
/**
* Tag
*
* #ORM\Table(name="tag")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TagRepository")
*/
class Tag
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* #ORM\Column(name="last_use_at", type="datetime", nullable=false)
* #var \DateTime
*/
private $lastUseAt;
public function __construct()
{
$this->lastUseAt = new \DateTime();
}
public function __toString()
{
return $this->name;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Tag
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #return \DateTime
*/
public function getLastUseAt(): \DateTime
{
return $this->lastUseAt;
}
/**
* #param \DateTime $lastUseAt
*/
public function setLastUseAt(\DateTime $lastUseAt)
{
$this->lastUseAt = $lastUseAt;
}
}
Taggable
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag", cascade={"persist"})
*/
protected $tags;
/**
* Add tag
*
* #param Tag $tag
*
* #return $this
*/
public function addTag(Tag $tag)
{
$tag->setLastUseAt(new \DateTime());
$this->tags[] = $tag;
return $this;
}
/**
* Remove tag
*
* #param Tag $tag
*/
public function removeTag(Tag $tag)
{
$this->tags->removeElement($tag);
}
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
}
Project
/**
* Project
*
* #ORM\Table(name="project")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProjectRepository")
*/
class Project
{
use Taggable;
}
Note
class Note
{
use Taggable;
}
Is this the only solution or is my annotation incomplete / incorrect?
I tried with JoinColumns, JoinTable and onDelete = "cascade" but nothing works.
In the meantime, I dodged the problem with this instruction placed before the suppresion.
$project->getTags()->clear();
Full code of the action in the controller :
/**
* #Route("/project/{id}/delete", name="project_delete")
*/
public function deleteAction($id) {
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->find($id);
if(!$project) {
return $this->redirectToRoute('index');
}
$project->getTags()->clear();
$em->remove($project);
$em->flush();
return $this->redirectToRoute('index');
}
I think I found a better solution: you can set the PRAGMA within Doctrine configuration. Like:
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_sqlite'
#server_version: '5.7'
#charset: utf8mb4
#default_table_options:
#charset: utf8mb4
#collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
options:
'PRAGMA foreign_keys': 'ON'
I just tried it on my Symfony 4 application, re-created the database and tested using DB Browser for SQLite and it works as I expected.
Hope this helps
I managed to fix the problem. Here's my solution working for SQLite conections.
Create an eventListener listening on the kernel.request event :
namespace AppBundle\EventListener;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
/**
* #var Registry
*/
private $doctrine;
public function __construct(Registry $doctrine)
{
$this->doctrine = $doctrine;
}
public function onKernelRequest(GetResponseEvent $event)
{
$this->doctrine->getConnection()->exec('PRAGMA foreign_keys = ON');
}
}
Service declaration
app.event_listener.request_listener:
class: AppBundle\EventListener\RequestListener
arguments:
- '#doctrine'
tags:
- { name: kernel.event_listener, event: kernel.request }
I think the problem is that you have your trait Taggable set as the owning side of the ManyToMany relationship but your are deleting the inverse side and expecting something to happen as a result. Doctrine will only check the owning side of the relationship in order to persist any changes. See here for docs on this.
You can solve by making the Taggable the inverse side of each of your relationships, or by manually telling doctrine to delete the owning side.
The first solution will probably not work for you since you won't (easily) specify multiple inverse sides. (Are you sure a trait is the right way to go for this??)
The second solution is easy. In your entities like Project for your deleteTag($tag) function, call a delete function on the owning side (e.g., deleteProject($project). You will have to create if one does not exist.
class Project
{
use Taggable;
public function deleteTag($tag)
{
$this->tags->removeElement($tag);
// persist on the owning side
$tag->deleteProject($this);
}
}
EDIT:
After seeing full code, it looks like you are deleting correctly. Now you need to tell doctrine to carry that through. See this post for full details, but basically you can change your trait to this:
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(
* targetEntity="AppBundle\Entity\Tag",
* cascade={"persist"},
* onDelete="CASCADE"
* )
*/
protected $tags;
// ...
}
I've setup Doctrine and Symfony-forms independent of the Symfony Framework (as I don't need most of it).
The issue I'm having is, when trying to persist a new "Audit" which has an "Type" doctrine seems to want to persist the owning side of the relationship (Type).
For example as Audit may have a type of Vehicle Service.
// -- Model/Audit.php --
/**
* #var \Model\Type
*
* #ORM\ManyToOne(targetEntity="Model\Audit\Type", inversedBy="audits")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=true)
*/
private $type;
/**
* Set type
*
* #param \Model\Type $type
* #return Audit
*/
public function setType(\Model\Type $type)
{
$this->type = $type;
return $this;
}
And then in the inverse side:
/**
* #ORM\OneToMany(targetEntity="Model\Audit", mappedBy="type")
* #var type */
private $audits;
public function __construct() {
$this->audits = new \Doctrine\Common\Collections\ArrayCollection();
}
Persistance code looks as follows:
$data = $form->getData();
$entityManager->persist($data);
$entityManager->flush();
And finally the form class is:
class AuditType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name')
->add('type', 'entity', array(
'class' => "Model\Type"
));
}
All looks (to me at least) exactly the same as in all the documentations both Doctrine and Symfony sides but I'm getting this error:
A new entity was found through the relationship 'Model\Audit#type'
that was not configured to cascade persist operations for entity:
Vehicle Service. To solve this issue: Either explicitly call
EntityManager#persist() on this unknown entity or configure cascade
persist this association in the mapping for example
#ManyToOne(..,cascade={"persist"})."
Which is really frustrating as I don't want to persist the Type side, I just want to put (in most basic terms) the id of 3 into the type_id column. Yet Doctrine seems to think I want to create a new "Type" which I certainly do not. They already exist.
Using $entityManager->merge($audit); works in part, it allows the inital Audit and its FK's to be saved. However it caused any embedded forms to become ignored.
I think you need set
/**
* #ORM\OneToMany(targetEntity="Model\Audit", mappedBy="type")
* #var type
*/
private $audits;
public function __construct() {
$this->audits = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #return ArrayCollection
*/
public function getAudits()
{
return $this->audits;
}
/**
* #param Audit $audit
*/
public function addAudits(Audit $audit)
{
$this->audits->add($audit);
$audit->setTyoe($this);
}
and in Type Audit.model
// -- Model/Audit.php --
/**
* #var \Model\Type
*
* #ORM\ManyToOne(targetEntity="Model\Audit\Type", inversedBy="audits")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=true)
*/
private $type;
/**
* Set type
*
* #param \Model\Type $type
* #return Audit
*/
public function setType(\Model\Type $type)
{
$this->type = $type;
}
I'm working on some kind of "complex" form in my project where entities are persisted on each steps since individual forms are split on them. Then I've a first step (lets call it step1) where I persist a entity and also store it on the session, see code below:
$productoSolicitudEntity = new Entity\ProductoSolicitud();
$productoSolicitudForm = $this->createForm(new Form\ProductoSolicitudForm(), $productoSolicitudEntity);
$productoSolicitudForm->handleRequest($request);
if ($productoSolicitudForm->isValid()) {
$productoSolicitudRequest = $request->get('productoSolicitud');
try {
$producto = $em->getRepository("AppBundle:Producto")->find($productoSolicitudRequest['producto']['nombre']);
$productoSolicitudEntity->setProducto($producto);
$condicionProducto = $em->getRepository("AppBundle:CondicionProducto")->find($productoSolicitudRequest['condicion_producto']);
$productoSolicitudEntity->setCondicionProducto($condicionProducto);
$finalidadProducto = $em->getRepository("AppBundle:FinalidadProducto")->find($productoSolicitudRequest['finalidad_producto']);
$productoSolicitudEntity->setFinalidadProducto($finalidadProducto);
$procedenciaProducto = $em->getRepository("AppBundle:ProcedenciaProducto")->find($productoSolicitudRequest['procedencia_producto']);
$productoSolicitudEntity->setProcedenciaProducto($procedenciaProducto);
$productoSolicitudEntity->setLote($productoSolicitudRequest['lote']);
$solicitudUsuario = $em->getRepository("AppBundle:SolicitudUsuario")->find($session->get('solicitudUsuarioEntity')->getId());
$productoSolicitudEntity->setSolicitudUsuario($solicitudUsuario);
$em->persist($productoSolicitudEntity);
$em->flush();
$session->set('productoSolicitudEntity', $productoSolicitudEntity);
$response['success'] = true;
} catch (Exception $ex) {
$status = 400;
$response['error'] = $ex->getMessage();
}
} else {
$status = 400;
$response['error'] = $this->get('translator')->trans('formularioNoValido');
$response['formError'] = $this->getFormErrors($productoSolicitudForm);
}
Then in the four step (lets call it step4) I need to attach that entity to a new one since they are related and this is the code involve:
$productoSolicitud = $session->get('productoSolicitudEntity');
if (! $productoSolicitud) {
$status = 400;
$response['error'] = 'No se encontrĂ³ la solicitud';
}
$distribuidorEntity = new Entity\FabricanteDistribuidor();
$distribuidorForm = $this->createForm(new Form\DistribuidorForm(), $distribuidorEntity);
$distribuidorForm->handleRequest($request);
if ($distribuidorForm->isValid()) {
$em->persist($distribuidorEntity);
$em->flush();
$session->set('distribuidorEntity', $distribuidorEntity);
$distribuidorProductoSolicitudEntity = new Entity\DistribuidorProductoSolicitud();
$distribuidorProductoSolicitudEntity->setProductoSolicitud($productoSolicitud);
$distribuidorProductoSolicitudEntity->setFabricanteDistribuidor($distribuidorEntity);
$em->persist($distribuidorProductoSolicitudEntity);
$em->flush();
$session->set('distribuidorEntity', $distribuidorEntity);
}
But I'm getting this error:
A new entity was found through the relationship 'AppBundle\Entity\DistribuidorProductoSolicitud#producto_solicitud' that was not configured to cascade persist operations for entity:
AppBundle\Entity\ProductoSolicitud#000000000a1f3e9d00007f88c54033f8. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example
#ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'AppBundle\Entity\ProductoSolicitud#__toString()' to get a clue.
Since the conflictive entity seems to be DistribuidorProductoSolicitud then I made this change on it:
/**
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\ProductoSolicitud", cascade={"persist"})
* #ORM\JoinColumn(name="producto_solicitud_id", referencedColumnName="id")
*/
protected $producto_solicitud;
But does not solve the issue, any help? What's is wrong? What I'm missing here? I should add a method __toString() at ProductoSolicitud entity but what this should return?
This are the entities involved on the issue:
class DistribuidorProductoSolicitud
{
use IdentifierAutogeneratedEntityTrait;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\FabricanteDistribuidor")
* #ORM\JoinColumn(name="fabricante_distribuidor_id", referencedColumnName="id")
*/
protected $fabricante_distribuidor;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\ProductoSolicitud", cascade={"persist"})
* #ORM\JoinColumn(name="producto_solicitud_id", referencedColumnName="id")
*/
protected $producto_solicitud;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Pais", inversedBy="distribuidorProductoSolicitudPais", cascade={"persist"})
* #ORM\JoinTable(name="nomencladores.pais_distribuidor_producto_solicitud", schema="nomencladores",
* joinColumns={#ORM\JoinColumn(name="distribuidor_producto_solicitud_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="pais_id", referencedColumnName="id")}
* )
*/
protected $paisesDistribuidorProductoSolicitudPais;
}
class ProductoSolicitud
{
use IdentifierAutogeneratedEntityTrait;
/**
* #var \Producto
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Producto")
* #ORM\JoinColumn(name="producto_id", referencedColumnName="id")
*/
protected $producto;
/**
* #var \SolicitudUsuario
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\SolicitudUsuario", cascade={"persist"})
* #ORM\JoinColumn(name="solicitud_usuario_id", referencedColumnName="id")
*/
protected $solicitud_usuario;
/**
* #var \CondicionProducto
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\CondicionProducto")
* #ORM\JoinColumn(name="condicion_producto_id", referencedColumnName="id")
*/
protected $condicion_producto;
/**
* #var \FinalidadProducto
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\FinalidadProducto")
* #ORM\JoinColumn(name="finalidad_producto_id", referencedColumnName="id")
*/
protected $finalidad_producto;
/**
* #ORM\Column(name="lote", type="integer", nullable=false)
*/
protected $lote;
/**
* #var \ProcedenciaProducto
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\ProcedenciaProducto")
* #ORM\JoinColumn(name="procedencia_producto_id", referencedColumnName="id")
*/
protected $procedencia_producto;
}
Where the cascade={"persist"} should go in order to fix it?
I've found this post but it's no helpful.
Saving (or serializing) a Doctrine entity to the session is problematic (here's a relevant SO question/answer on the matter) - since it loses the private properties that are needed to detect a hydrated Doctrine object that can be recognized in the system.
Since those private properties of a hydrated Doctrine object are missing, it perceives these unhydrated objects as entirely new (and the other associated objects.)
Your best solution is to only store the Object identifier in the session and retrieve them later with the find() helper function.
To store:
$this->get('session')->set('objectId', $object->getId());
To fetch later:
$objectId = $this->get('session')->get('objectId');
$object = $this->getDoctrine()->getRepository('AcmeBundle:Entity')->find($objectId);
Try to add cascade={"persist"} to both sides of your ManyToOne (in ProductoSolicitud and DistribuidorProductoSolicitud).
If this ManyToOne is unidirectional, try to change it to a OneToMany bidirectional with cascade persist on both sides.
In class ProductoSolicitud:
/**
* #OneToMany(targetEntity="AppBundle\Entity\DistribuidorProductoSolicitud", mappedBy="producto_solicitud", cascade={"persist"})
#var \Doctrine\Common\Collections\ArrayCollection
**/
private $distribuidor_producto_solicidudes;
In class DistribuidorProductoSolicidud:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\ProductoSolicitud", inversedBy="distribuidor_producto_solicidudes", cascade={"persist"})
* #ORM\JoinColumn(name="producto_solicitud_id", referencedColumnName="id")
#var \AppBundle\Entity\ProductoSolicidud
*/
protected $producto_solicitud;
I would like to ask, if anybody obtained such error before. Since I am stuck for 2 hours fixing bugs with doctrine already. So any kind of of help would be appreciate.
To the point. I am fighting with doctrine Entity Manager I can't make it work.
I created entity and doctrine class to work with, but I am getting an error all the time:
Fatal error: Uncaught exception 'Doctrine\ORM\Mapping\MappingException' with message 'Class "MSP\Model\Entity\Category" is not a valid entity or mapped super class.' in /home/dariss/www/dom/php/MenuSiteProject/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/MappingException.php:216 Stack trace: #0 /home/dariss/www/dom/php/MenuSiteProject/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php(87): Doctrine\ORM\Mapping\MappingException::classIsNotAValidEntityOrMappedSuperClass('MSP\Model\Entit...') #1 /home/dariss/www/dom/php/MenuSiteProject/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php(113): Doctrine\ORM\Mapping\Driver\AnnotationDriver->loadMetadataForClass('MSP\Model\Entit...', Object(Doctrine\ORM\Mapping\ClassMetadata)) #2 /home/dariss/www/dom/php/MenuSiteProject/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php(318): Doctrine\ORM\Mapping\ClassMetadataFactory->doLoadMetadata(Object(Doctrine\ORM\Mapping\ClassMetadata), NULL, false, Array) in /home/dariss/www/dom/php/MenuSiteProject/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/MappingException.php on line 216
My entity class.
namespace MSP\Model\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Category
*
* #ORM\Entity
* #ORM\Table(name="category")
*/
class Category {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="int", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="category_id_seq", allocationSize=1, initialValue=1)
*/
private $id;
/**
* #var String $name
*
* #ORM\Column(name="name", type"string", length=50, nullable=true)
*/
private $name;
/**
* #var integer $parent
*
* #ORM\Column(name="parent", type="int", nullable=true)
*/
private $parent;
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #param $name
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* #return String
*/
public function getName()
{
return $this->name;
}
/**
* #param int $parent
* #return Category
*/
public function setParent($parent)
{
$this->parent = $parent;
return $this;
}
/**
* #return int
*/
public function getParent()
{
return $this->parent;
}
Doctrine class:
namespace MSP\Helper;
use Doctrine\Common\ClassLoader,
Doctrine\ORM\Configuration,
Doctrine\ORM\EntityManager,
Doctrine\Common\Cache\ArrayCache,
Doctrine\DBAL\Logging\EchoSQLLogger;
class Doctrine{
public $em = null;
public function __construct()
{
require_once __DIR__.'/../../../vendor/doctrine/common/lib/Doctrine/Common/ClassLoader.php';
$doctrineClassLoader = new ClassLoader('Doctrine', '/');
$doctrineClassLoader->register();
$entitiesClassLoader = new ClassLoader('MSP\Model\Entity', '/../Model/Entity');
$entitiesClassLoader->register();
$proxiesClassLoader = new ClassLoader('Proxies', '/../Proxies/');
$proxiesClassLoader->register();
// Set up caches
$config = new Configuration;
$cache = new ArrayCache;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver(array('/../Model/Entity'), true);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setQueryCacheImpl($cache);
// Proxy configuration
$config->setProxyDir('/proxies');
$config->setProxyNamespace('Proxies');
// Set up logger
$logger = new EchoSQLLogger;
//$config->setSQLLogger($logger);
$config->setAutoGenerateProxyClasses( TRUE );
$iniParser = new \MSP\Helper\IniParser();
$configuration = $iniParser->getConfig();
// Database connection information
$connectionOptions = array(
'driver' => $configuration['driver'],
'user' => $configuration['username'],
'password' => $configuration['password'],
'dbname' => $configuration['dbname'],
'host' => $configuration['host']
);
// Create EntityManager
$this->em = EntityManager::create($connectionOptions, $config);
}
}
Test file:
$doctrine = new MSP\Helper\Doctrine();
$doctrine->em->find('MSP\Model\Entity\Category', 1);
You need to drop the #ORM prefixes when you annotate the entities.
But I followed the example in the Symfony 2 documentation? Yep. For Symfony 2 you need to use #ORM. But your test cases uses "pure" doctrine which means no #ORM.
If you really need to run your stuff using pure doctrine then consider using yaml notation.
Why does S2 use #ORM? It's a long sad story but basically it introduced the #ORM prefix so other annotations would not clash with Doctrine.
Can you tweak the Doctrine configuration to allow the user of #ORM? Yep. But I forget how. You can search for it.
Bottom line: Consider just using the Symfony 2 Doctrine service. It's easier.
You set an absolute path /../Model/Entity.
You need to set ./../Model/Entity