Why is my PersistentCollection empty? - php

I'm using the Symfony 3 Framework with Doctrine and MongoDB.
I've two documents that are in an OneToMany relationship.
/**
* Class Waypoint
* #package AppBundle\Document
* #MongoDB\Document(collection="waypoints", repositoryClass="AppBundle\Repository\WaypointRepository")
*/
class Waypoint
{
/**
* #var int
*
* #MongoDB\Id(strategy="auto")
*/
private $id;
/**
* #var ArrayCollection
* #MongoDB\ReferenceMany(targetDocument="Comment", cascade={"delete"})
*/
private $comments;
}
**
* Class Comment
* #package AppBundle\Document
* #MongoDB\Document(collection="comments", repositoryClass="AppBundle\Repository\CommentRepository")
*/
class Comment
{
/**
* #var int
*
* #MongoDB\Id(strategy="auto")
*/
private $id;
/**
* #var Waypoint
*
* #MongoDB\ReferenceOne(targetDocument="Waypoint", inversedBy="comments")
* #Assert\NotBlank()
*/
private $waypoint;
}
Now I'm getting a part of my Waypoint entries from an repository query and want to display them with twig.
/**
* WaypointRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class WaypointRepository extends DocumentRepository
{
public function getWaypointsForCruiseByPage(Cruise $cruise, $page)
{
$displayLimit = 10;
$amountToSkip = 0;
if ($page > 1)
{
$amountToSkip = ($page -1) * $displayLimit;
}
$qb = $this->createQueryBuilder()
->select()
->field('cruise')->equals($cruise)
->field('isAutoWaypoint')->equals(false)
->sort('date', -1)
->skip($amountToSkip)
->limit($displayLimit)
;
$qb
->addOr($qb->expr()->field('hasImage')->equals(true))
->addOr($qb->expr()->field('hasAudio')->equals(true))
->addOr($qb->expr()->field('description')->notEqual(''))
;
return $qb->getQuery()->toArray();
}
}
Now, I'm trying to do {{ waypoint.comments.count }} or {{ waypoint.comments|length }} will always be 0, even if I'm having datasets in my MongoDB collection.
If I'm getting the comments over the CommentRepository by the ID of the Waypoint I'm getting the expected results.
// returns the expected results
public function getAllCommentsForWaypoint(Waypoint $waypoint)
{
return $this->createQueryBuilder()
->select()
->field('waypoint')->equals($waypoint)
->getQuery()->toArray()
;
}
The mapping is fine as far as I can tell, no flaws or errors to find.
Why is the PersistentCollection empty, event though informations are there in the collection?

I'm not sure how are you creating documents, but this is my best shot:
Waypoint::$comments is not mapped as an inverse side thus ODM expects list of references to be available in the waypoint.comments field in the database. Most probably it's not there (i.e. you're not explicitly adding new Comment to the collection in the Waypoint) and that's why you're seeing empty collection when querying for waypoints, but have results when querying for comments. Given you have inversedBy="comments" in the Comment mapping I think you forgot to set the Waypoint::$comments as the inverse side:
/**
* #var ArrayCollection
* #MongoDB\ReferenceMany(targetDocument="Comment", mappedBy="waypoint")
*/
private $comments;

Related

Symfony Doctrine2 manyToMany relationship not removed - Specific to SQLite

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;
// ...
}

It's possible to persist entities stored on session in Symfony2+Doctrine2?

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;

Filtering on many-to-many association with Doctrine2

I have an Account entity which has a collection of Section entities. Each Section entity has a collection of Element entities (OneToMany association). My problem is that instead of fetching all elements belonging to a section, I want to fetch all elements that belong to a section and are associated with a specific account. Below is my database model.
Thus, when I fetch an account, I want to be able to loop through its associated sections (this part is no problem), and for each section, I want to loop through its elements that are associated with the fetched account. Right now I have the following code.
$repository = $this->objectManager->getRepository('MyModule\Entity\Account');
$account = $repository->find(1);
foreach ($account->getSections() as $section) {
foreach ($section->getElements() as $element) {
echo $element->getName() . PHP_EOL;
}
}
The problem is that it fetches all elements belonging to a given section, regardless of which account they are associated with. The generated SQL for fetching a section's elements is as follows.
SELECT t0.id AS id1, t0.name AS name2, t0.section_id AS section_id3
FROM mydb.element t0
WHERE t0.section_id = ?
What I need it to do is something like the below (could be any other approach). It is important that the filtering is done with SQL.
SELECT e.id, e.name, e.section_id
FROM element AS e
INNER JOIN account_element AS ae ON (ae.element_id = e.id)
WHERE ae.account_id = ?
AND e.section_id = ?
I do know that I can write a method getElementsBySection($accountId) or similar in a custom repository and use DQL. If I can do that and somehow override the getElements() method on the Section entity, then that would be perfect. I would just very much prefer if there would be a way to do this through association mappings or at least by using existing getter methods. Ideally, when using an account object, I would like to be able to loop like in the code snippet above so that the "account constraint" is abstracted when using the object. That is, the user of the object does not need to call getElementsByAccount() or similar on a Section object, because it seems less intuitive.
I looked into the Criteria object, but as far as I remember, it cannot be used for filtering on associations.
So, what is the best way to accomplish this? Is it possible without "manually" assembling the Section entity with elements through the use of DQL queries? My current (and shortened) source code can be seen below. Thanks a lot in advance!
/**
* #ORM\Entity
*/
class Account
{
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="MyModule\Entity\Section")
* #ORM\JoinTable(name="account_section",
* joinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="section_id", referencedColumnName="id")}
* )
*/
protected $sections;
public function __construct()
{
$this->sections = new ArrayCollection();
}
// Getters and setters
}
/**
* #ORM\Entity
*/
class Section
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="MyModule\Entity\Element", mappedBy="section")
*/
protected $elements;
public function __construct()
{
$this->elements = new ArrayCollection();
}
// Getters and setters
}
/**
* #ORM\Entity
*/
class Element
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var Section
* #ORM\ManyToOne(targetEntity="MyModule\Entity\Section", inversedBy="elements")
* #ORM\JoinColumn(name="section_id", referencedColumnName="id")
*/
protected $section;
/**
* #var \MyModule\Entity\Account
* #ORM\ManyToMany(targetEntity="MyModule\Entity\Account")
* #ORM\JoinTable(name="account_element",
* joinColumns={#ORM\JoinColumn(name="element_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")}
* )
*/
protected $account;
// Getters and setters
}
If I understand correctly, you want to be able to retrieve all Elements of all Sections of an Account, but only if those Elements are associated with that Account, and this from a getter in Account.
First off: An entity should never know of repositories. This breaks a design principle that helps you swap out the persistence layer. That's why you cannot simple access a repository from within an entity.
Getters only
If you only want to use getters in the entities, you can solve this by adding to following 2 methods:
class Section
{
/**
* #param Account $accout
* #return Element[]
*/
public function getElementsByAccount(Account $accout)
{
$elements = array();
foreach ($this->getElements() as $element) {
if ($element->getAccount() === $account) {
$elements[] = $element->getAccount();
}
}
return $elements;
}
}
class Account
{
/**
* #return Element[]
*/
public function getMyElements()
{
$elements = array()
foreach ($this->getSections() as $section) {
foreach ($section->getElementsByAccount($this) as $element) {
$elements[] = $element;
}
}
return $elements;
}
}
Repository
The solution above is likely to perform several queries, the exact amount depending on how many Sections and Elements are associated to the Account.
You're likely to get a performance boost when you do use a Repository method, so you can optimize the query/queries used to retrieve what you want.
An example:
class ElementRepository extends EntityRepository
{
/**
* #param Account $account [description]
* #return Element[]
*/
public function findElementsByAccount(Account $account)
{
$dql = <<< 'EOQ'
SELECT e FROM Element e
JOIN e.section s
JOIN s.accounts a
WHERE e.account = ?1 AND a.id = ?2
EOQ;
$q = $this->getEntityManager()->createQuery($dql);
$q->setParameters(array(
1 => $account->getId(),
2 => $account->getId()
));
return $q->getResult();
}
}
PS: For this query to work, you'll need to define the ManyToMany association between Section and Account as a bidirectional one.
Proxy method
A hybrid solution would be to add a proxy method to Account, that forwards the call to the repository you pass to it.
class Account
{
/**
* #param ElementRepository $repository
* #return Element[]
*/
public function getMyElements(ElementRepository $repository)
{
return $repository->findElementsByAccount($this);
}
}
This way the entity still doesn't know of repositories, but you allow one to be passed to it.
When implementing this, don't have ElementRepository extend EntityRepository, but inject the EntityRepository upon creation. This way you can still swap out the persistence layer without altering your entities.

zend framework 2 - ServiceManager error while saving data in database

I am trying to create a saveAction in zend2 framework using doctrine.
in my PromotionsController i have this action:
public function saveLinkAction() {
$view = new ViewModel();
$salonId = (int) $this->params()->fromPost('salon_id', null);
$addLink = $this->getServiceLocator()->get('Promotions\Model\Link');
$linkData['salon_id'] = $salonId;
$linkData['link'] = '/link/example';
$addLink->setData($linkData);
return $view;
}
This is just for learning how to write data in database.
$addLink = $this->getServiceLocator()->get('Promotions\Model\Link');
This line of code is showing an error and i don't know what is the cause?
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Promotions\Model\Link
I have created a Link.php in Model directory.
<?php
namespace Link\Model;
use Application\Model\Entity;
use Zend\Form\Annotation;
/**
* #Entity
* #Table(name="promo_link")
*/
class Link extends Entity {
/********** PROPERTIES **********/
/**
* #Id #GeneratedValue(strategy="AUTO") #Column(name="id", type="integer")
* #var int
*
* #Annotation\Exclude()
*/
protected $id;
/**
* #Column(name="salon", type="integer")
* #var int
*
* #Annotation\Options({"label":"Salon"})
* #Annotation\Validator({"name": "Digits"})
*/
protected $salon;
/**
* #Column(name="link", type="string")
* #var string
*/
protected $link;
/**
* #Column(name="start_date", type="string")
* #var string
*/
protected $start_date;
/**
* #Column(name="end_date", type="string")
* #var string
*/
protected $end_date;
}
?>
The error tells you where the problem is:
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Promotions\Model\Link
Meaning: The ServiceManager doesn't know what Promotions\Model\Link is supposed to be. This key either doesn't exist in your SMConfig or while creating the instance to be returned some error occurs.
TL/DR - Check your ServiceManager Configuration regarding the key Promotions\Model\Link
In order to save your data in your database, you will need the entitymanager.
$link = new Link();
$link->setSalonId($salonId);
$link->setLink('/link/example');
$em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$em->persist($link);
$em->flush();
You can start with the above code.
However, preferably you would create a repository and a servicelayer. The service should have access to the entityManager and hold your logic. Your controller should have access to this service.

Symfony2 Relationship returns null, should return object

I have run into a problem that defies all my attempts to unproblem it. Its probably simple, I have just totally exhausted myself on this one :)
Basically I want to give the user the ability to add modules (facebook id, a bio, text inputs) and assets (image logo, pdf, et al) to a page object.
I have set up a onetomany relationship for Module and Asset to Page.
Module works as expected, however Asset will not work at all: when PageController calls getAsset() from its entity, it is null. There is no error until I try to iterate over the Assets.
Also, in PageController there are the following namespace declarations:
use Linkme\SiteBundle\Entity\Module;
use Linkme\SiteBundle\Entity\Asset;
If I remove the Module declaration I get an error, but if I remove the Asset line, nothing changes. Therefore I believe the relationship is not created.
If I run
app/console doctrine:schema:create --dump-sql
then amongst others I get the following line:
ALTER TABLE Asset ADD CONSTRAINT FK_C36E75589D3B65E3 FOREIGN KEY (pageId) REFERENCES Page(id);
which makes me think the schema is correct. It as least understands Asset is related to Page
Im starting to feel I have a typo or I am totally missing something equally as obvious - any assistance on troubleshooting or other suggestions would be much appreciated!
app/console --version
Symfony version 2.0.1 - app/dev/debug
Page.php
/*
* #ORM\OneToMany(targetEntity="Asset", mappedBy="pageId", cascade={"persist", "remove"})
* #ORM\OrderBy({"type" = "ASC"})
* #ORM\OrderBy({"id" = "ASC"})
*
* #var ArrayCollection $assets
*/
protected $assets;
/**
* #ORM\OneToMany(targetEntity="Module", mappedBy="pageId", cascade={"persist", "remove"})
* #ORM\OrderBy({"type" = "ASC"})
* #ORM\OrderBy({"id" = "ASC"})
*
* #var ArrayCollection $modules
*/
protected $modules;
/**
* Set assets
* #param Asset $assets
*/
public function setAssets(Asset $assets = null)
{
$this->assets = $assets;
}
/**
* Get assets
*
* #return Asset
*/
public function getAssets()
{
echo '<br />Asset is '.gettype($this->assets); // outut: Asset is NULL
return $this->assets;
}
/**
* Set modules
* #param Module $modules
*/
public function setModules(Module $modules = null)
{
$this->modules = $modules;
}
/**
* Get modules
* #return Module
*/
public function getModules()
{
echo '<br />Module is '.gettype($this->assets); // output: Module is object
return $this->modules;
}
Asset.php
/**
* #var integer $pageId
*
* #ORM\ManyToOne(targetEntity="Page", inversedBy="assets")
* #ORM\JoinColumn(name="pageId", referencedColumnName="id")
*/
protected $pageId;
Module.php
/**
* #var integer $pageId
*
* #ORM\ManyToOne(targetEntity="Page", inversedBy="modules")
* #ORM\JoinColumn(name="pageId", referencedColumnName="id")
*/
protected $pageId;
PageController.php
use Linkme\SiteBundle\Entity\Module;
use Linkme\SiteBundle\Entity\Asset;
/**
* Add modules and assets to a page
*
* #Route("/{id}/wizardmodule", name="page_wizardmodule")
* #Template()
*/
public function wizardmoduleAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$page = $em->getRepository('LinkmeSiteBundle:Page')->find($id);
$modules = $page->getModules();
$assets = $page->getAssets();
depmod
[symfony]
git=http://github.com/symfony/symfony.git
version=v2.0.1
[twig]
git=http://github.com/fabpot/Twig.git
version=v1.1.2
[monolog]
git=http://github.com/Seldaek/monolog.git
version=1.0.1
[doctrine-common]
git=http://github.com/doctrine/common.git
version=2.1.1
[doctrine-dbal]
git=http://github.com/doctrine/dbal.git
version=2.1.1
[doctrine]
git=http://github.com/doctrine/doctrine2.git
version=2.1.1
[swiftmailer]
git=http://github.com/swiftmailer/swiftmailer.git
version=v4.1.1
[assetic]
git=http://github.com/kriswallsmith/assetic.git
version=v1.0.1
[twig-extensions]
git=http://github.com/fabpot/Twig-extensions.git
[metadata]
git=http://github.com/schmittjoh/metadata.git
version=1.0.0
[SensioFrameworkExtraBundle]
git=http://github.com/sensio/SensioFrameworkExtraBundle.git
target=/bundles/Sensio/Bundle/FrameworkExtraBundle
version=v2.0.1
[JMSSecurityExtraBundle]
git=http://github.com/schmittjoh/JMSSecurityExtraBundle.git
target=/bundles/JMS/SecurityExtraBundle
version=v2.0.1
[SensioDistributionBundle]
git=http://github.com/sensio/SensioDistributionBundle.git
target=/bundles/Sensio/Bundle/DistributionBundle
version=v2.0.1
[SensioGeneratorBundle]
git=http://github.com/sensio/SensioGeneratorBundle.git
target=/bundles/Sensio/Bundle/GeneratorBundle
version=v2.0.1
[AsseticBundle]
git=http://github.com/symfony/AsseticBundle.git
target=/bundles/Symfony/Bundle/AsseticBundle
version=v1.0.0
I got it! and as I predicted, it was an incredibly annoying PEBKAC....
The relationship was not being created because the annotations were not being read, because I was missing a * on the annotations comment box..... ddddoooohhhhh!
Page.php
Before:
/* <========================== there is only one * here. It needs to be two: **
* #ORM\OneToMany(targetEntity="Asset", mappedBy="pageId", cascade={"persist", "remove"})
* #ORM\OrderBy({"type" = "ASC"})
* #ORM\OrderBy({"id" = "ASC"})
*
* #var ArrayCollection $assets
*/
protected $assets;
After:
/** <========================== like this.
* #ORM\OneToMany(targetEntity="Asset", mappedBy="pageId", cascade={"persist", "remove"})
* #ORM\OrderBy({"type" = "ASC"})
* #ORM\OrderBy({"id" = "ASC"})
*
* #var ArrayCollection $assets
*/
protected $assets;
I'd just like to say a big thanks to everyone who helped out with this problem.
Check this:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-many-bidirectional
Do you correctly initialize collections into your page constructor?
This is because you don't initialize your $assets collection in your Page constructor.
public function __construct(){
$this->assets = new ArrayCollection();
}
Then I think you haven't run the doctrine:generate:entities command, it simplifies your life a little, creating get, set and add methods for every mapped field. In your case it would create a
public function addModules(Module $modules)
{
$this->modules[] = $modules;
}
Note that actually you simply assign $modules to $this->modules, this is wrong, it's an array not a scalar.
And to add the reference to the page referred by every module you'll have to add another instruction:
public function addModules(Module $modules)
{
$this->modules[] = $modules;
$modules->setPage($this);
}
I've done this in my code and it works, let me know if it works for you too, bye
Linuxatico

Categories