Symfony2 Doctrine, setup a page with many to many self referencing relationship - symfony

I have tried following the instructions on Doctrines manuals:
http://docs.doctrine-project.org/en/latest/reference/association-mapping.html#many-to-many-self-referencing
I'm trying to setup a pages entity, which has many subPages and I would also like to be able to access a pages parentPage.
Following the instructions above I am told by symfony2 that I need to setup a "use" due to a semantic error.
Could anybody instruct me on what to do to allow me to do this as I am quite stuck.
Sample code is:
namespace Pages\Bundle\PageBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Page
*
* #ORM\Table(name="Page")
* #ORM\Entity(repositoryClass="Pages\Bundle\PageBundle\Entity\PageRepository")
*/
class Page
{
/**
* Constructor
*/
public function __construct()
{
$this->subPages = new \Doctrine\Common\Collections\ArrayCollection();
$this->parentPages = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ManyToMany(targetEntity="Page", mappedBy="subPages")
**/
private $parentPages;
/**
* #ManyToMany(targetEntity="Page", inversedBy="parentPages")
* #JoinTable(name="sub_pages",
* joinColumns={#JoinColumn(name="parent_page_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="sub_page_id", referencedColumnName="id")}
* )
**/
private $subPages;
...
(other variables follow but are content / metas)
Error response when running is:
[Semantical Error] The annotation "#ManyToMany" in property
Pages\Bundle\PageBundle\Entity\Page::$parentPages was never imported. Did you maybe forget to add a "use" statement for this annotation?

Try using #ORM\ManyToMany instead of just #ManyToMany
(and also #ORM\JoinTable)

Related

Symfony Accessing to parent entity inside entity class

In Symfony 5, I've created 2 entities related with a ManyToOne relation : Project is the parent, Serie is the child.
Project entity :
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\SerieRepository")
*/
class Serie
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Project", inversedBy="series")
* #ORM\JoinColumn(nullable=false)
*/
private $project;
[...]
}
Serie entity :
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProjectRepository")
*/
class Project
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Serie", mappedBy="Project", orphanRemoval=true)
*/
private $series;
[...]
}
I didn't write down here, but you also have all the getter and setter for each class.
I need to access to the Project entity in the Serie entity. For example : accessing to the name property of Project entity by adding a getProjectName method in Serie class.
public function getProjectName()
{
return $this->project->getName();
}
But this is not working as the Project entity is not loaded (only the id). How can I get this value, without adding a repository in the entity class or passing any argument to the getProjectName method ? (maybe a Doctrine annotation...).
In doctrine entities in relations are lazy-loaded, that means, when you have not accessed anything on $this->project (or the referenced project), it will just be of type Project^ (notice the ^) and it will have an attribute called __initialized__ (or similar) with the value false (check by dump($this->project);). This means, that the entity is NOT loaded, yet.
Lazy-loading means, it will be loaded if it's actually needed (thus reducing database accesses), and before that, a proxy object will take the place of the entity. It'll register all calls done to it, load the entity if necessary and forward all calls to it.
So, to load a lazy-loaded entity, you just call one of its methods. So $this->project->getName() should already work nicely. (verify by calling dump($this->project); afterwards).
If it doesn't, something else is missing/wrong/dysfunctional.
Ok, thank you Jakumi. You are right, on that way, it's working fine.
To complete your explanation, if you want to get the child elements, like :
$series = $project->getSeries();
You will have an empty table (a foreach loop won't get any item). This is because $series is a Doctrine Collection. You need to use :
$series = $project->getSeries()->getValues();
to have a fully completed array.
I spend 2 hours on the topic, I hope this will help somebody else.

Doctrine Entity Repository how to add 'andWhere' to all 'find*' functions?

For legacy reasons I have table that is used for many purposes. Only subset of rows is relevant to Entity I'm writing. Criteria is simple, just check 'project' field for specific value.
Instead of reimplementing find, findAll, findByName, findByID, findBy.... Just notify doctrine to append single condition to them all. Is that possible without reimplementing each and every find* ?
Or maybe it can be done on lover level still?
UPDATE:
Reworked question, to specify what kind of solution would be acceptable.
An available easy-to-use solution is to create a Repository with your custom find function.
Then, if all your entities has a specific Repository, make them (Repository) extending from yours (which contains the custom find method), otherwise (you doesn't have a Repository per entity), assign the repository to all your entities with the repositoryClass option of the #ORM\Entity annotation like :
#ORM\Entity(repositoryClass="YourMainRepository")
Otherwise, if you doesn't want put any repository in your entities, override the whole default repository and customise the find method.
I already used the last option because of a specific need, also I invite you to see the following question :
Abstract repository and #Entity annotation inheritance
Look at the solution wich contains a gist of all required steps for override the default doctrine repository.
See Custom Repository classes
Entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Phrase
*
* #ORM\Table(name="User")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User
{
/**
* #var int
*
* #ORM\Column(name="id", type="bigint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
.............................................................
.............................................................
.............................................................
Your Repository:
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
/** For example **/
public function getByName($name)
{
$qb = $this->createQueryBuilder('u')
->where('u.name = :name')->setParameter('name', $name)
->andWhere('u.lastname LIKE :name')->setParameter('lastname', '%'.$name.'%');
$query = $qb->getQuery();
return $query->getResult();
}
}
In Your Controller:
/**
* #Route("/", name="index")
*/
public function indexAction(Request $request)
{
$userRepository = $this->getDoctrine()->getRepository('AppBundle:User');
$userName = $userRepository->getByName($name);
..................................................................................
..................................................................................

How update database with doctrine in Symfony2

I make an Entity with Entity generator, but now when I try command:
php app/console doctrine:schema:update --force
It throw following:
No Metadata Classes to process.
So, what I make wrong?
PS. I am also try write it self and use following command:
php app/console doctrine:generate:entities AppBundle/Entity/DataPage
And I saw this error:
[Doctrine\ORM\Mapping] \MappingException] Class "AppBundle\Entity\DataPage" is not a valid entity or mapped super class.
So there is code:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* DataPage
*/
class DataPage
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $sisalto;
/**
* #var string
*/
private $nimi;
}
EDIT
So, I try make this for exampe code from there
And I saw that same error:
[Doctrine\ORM\Mapping \MappingException]
Class "AppBundle\Entity\Product" is not a valid entity or mapped superclass.
/**
* DataPage
*/
class DataPage
[...]
should be
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="data_page")
*/
[...]
This is only the first step, than you have to annotate every single field of the entity that you want to appear onto db.
Please take a look here
I'm not sure if this helps anyone else, but I was getting the following error for the symfony Doctrine Product example code:
[Doctrine\ORM\Mapping\MappingException]
Class "AppBundle\Entity\Product" is not a valid entity or mapped super class.
Then I took a second look at my comments section, and it was a missing asterisk like the following code sample [The below is WRONG]:
/*
* #ORM\Entity
* #ORM\Table(name="product")
*/
class Product
Notice on line 1 there should be an extra '*' at the very end. I definitely did not think this would matter at all, but apparently it does. Also don't use tabs in front of your annotations. The Doctrine group just fixed that bug too.

Doctrine arrayCollections and relationship

I'm quite new with Doctrine, so I hope someone can help me or redirect me to the good documentation page.
I'm building an app with two entity (I reduce for explanations) :
- Tender
- File
For each tender, we can have one or more files. So I made the following objects.
Tender:
<?php
namespace TenderBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="tender")
*/
class Tender
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $tender_id;
/**
* #ORM\Column(type="array")
* #ORM\ManyToOne(targetEntity="File", inversedBy="tenders")
* #ORM\JoinColumn(name="tender_files", referencedColumnName="file_id")
*/
private $tender_files;
}
File:
<?php
namespace TenderBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity
* #ORM\Table(name="file")
*/
class File
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $file_id;
/**
* #ORM\OneToMany(targetEntity="Tender", mappedBy="tender_files", cascade={"persist", "remove"})
*/
private $file_tender;
}
First question: is it the right way to do this?
(of course, i've created the methods to get and set attributes, but they're basic).
When I persist each of my File object i'm trying to add then to my Tender instance. But to do this, I need to make $tender_files public and do this:
$tender->tender_files[]
This is not a viable solution for me because I need all my fields are private and I want to recover my object when I try to call this:
$this->getDoctrine()->getManager()->getRepository('TenderBundle:Tender')->find($id)->getTenderFiles()->getFileName();
So, I'm explaining and asking to find the right way to do what I want. I hope what i need is clear and i'm here to answers questions or show more code if needed.
Thanks!
Like Richard has mentioned, you're missing getters and setters which are declared to be public. They'll have access to your private variables. The quick way to do this with symfony:
php app/console doctrine:generate:entities
It'll generate something like this:
public function addTenderFile(\TenderBundle\Entity\File $file)
{
$this->tender_files[] = $file;
return $this;
}
/**
* Remove
*/
public function removeTenderFile(\TenderBundle\Entity\File $file)
{
$this->tender_files->removeElement($file);
}
/**
* Get
*/
public function getTenderFiles()
{
return $this->tender_files;
}
It's good practice if you're a beginner to see how your code lines up with the auto generator. Once you understand what's going on, just let the generator do the grunt work.
You should have a setter and getter in your File entity similar to this:
public function setTender(\Your\Namespace\Tender $tender)
{
$this->tender = $tender;
return $this;
}
public function setTender()
{
return $this->tender;
}
So when you instance (or create) File, you can go like so:
$file = new File(); // or file fetched from DB, etc.
// here set $file properties or whatever
$tender->setFile($file);
$entityManager->persist($tender);
$entityManager->flush();
Then your tender will be properly associated with your file.
Similarly from the File end, you should be able to do:
$file->addTender($tender);
$entityManager->persist($file);
$entityManager->flush();
And your tender will be added to your File->tenders collection.
For more information the documentation is very useful and has more or less everything you need to get started.
Also, save yourself manually creating getters and setters by using generate:doctrine:entity
This is incorrect:
/**
* #ORM\Column(type="array")
* #ORM\ManyToOne(targetEntity="File", inversedBy="tenders")
* #ORM\JoinColumn(name="tender_files", referencedColumnName="file_id")
*/
private $tender_files;
You can't persist an array to your database. A database row is one entity and it's corresponding attributes. If a tender can have many files, then this relationship should be:
* #ORM\OneToMany
Likewise for the File entity. If many files can have one Tender then it's relationship should be:
* #ORM\ManyToOne
For relationship mapping using Doctrine, it's helpful to read left-to-right with the entity YOU'RE CURRENTLY IN being on the left, and the entity you're setting as a variable being on the right.
If you're in Tender reading left-to-right Tender may have "OneToMany" files. And File(s) may have ManyToOne Tender. Doctrine Association Mapping

How do you extend an entity in Symfony2 like you used to be able to in Symfony 1?

In older versions of Symfony you used to be able to build new objects within a data object by extending a model class with an extended subclass.
For example, I had a questionnaire model that had a results table. That results table had a Result.php model class that used to set and get the results through Doctrine. I then used the ResultPeer.php model subclass to add a new function to the Result object that took the result and depending on a fixed set of thresholds calculated a score and corresponding colour.
In the new Symfony2 version using Doctrine2 I am struggling to work out the best way to do this. When creating an entity I can only find in the documentation the ability to add objects based on the data structure relationships.
I looked at the entity repositories, but that does not appear to extend or add functionality to an original object. It seems to bring back data objects based on queries that are more complex than the standard query functions.
I also looked at services, which I can use to collect the object and then using the object create a new array that includes this object and the newly created data, but this just does not seem right or follow the philosophy that Symfony is all about.
Does anyone know how functions can be added to an existing data object. I found it really useful in the older version of Symfony, but cannot seem to find the alternative in the new version of Symfony2.
Extending an entity is the way to go. In the Doctrine2 world, they talk about inheritance mapping. Here a code example. It defines a BaseEntity then it is extendsed to create a BaseAuditableEntity and finally there is a User entity extending BaseAuditableEntity. The trick is to use the #Orm\MappedSuperclass annotation. This inheritance scheme will create a single table even if there is three entities in my relationships graph. This will then merge all properties into a single table. The table created will contains every property mapped through the relations, i.e. properties from BaseAuditableEntity and from User. Here the code examples:
Acme\WebsiteBundle\Entity\BaseEntity.php
namespace Acme\WebsiteBundle\Entity;
use Doctrine\ORM\Mapping as Orm;
/**
* #Orm\MappedSuperclass
*/
class BaseEntity {
}
Acme\WebsiteBundle\Entity\BaseAuditableEntity.php
namespace Acme\WebsiteBundle\Entity;
use Doctrine\ORM\Mapping as Orm;
/**
* #Orm\MappedSuperclass
*/
class BaseAuditableEntity extends BaseEntity {
private $createdBy;
/**
* #Orm\Column(type="datetime", name="created_at")
*/
private $createdAt;
/**
* #Orm\ManyToOne(targetEntity="User")
* #Orm\JoinColumn(name="updated_by", referencedColumnName="id")
*/
private $updatedBy;
/**
* #Orm\Column(type="datetime", name="updated_at")
*/
private $updatedAt;
// Setters and getters here
}
Acme\WebsiteBundle\Entity\User.php
namespace Acme\WebsiteBundle\Entity;
use Acme\WebsiteBundle\Entity\BaseAuditableEntity;
use Doctrine\ORM\Mapping as Orm;
/**
* #Orm\Entity(repositoryClass="Acme\WebsiteBundle\Entity\Repository\UserRepository")
* #Orm\Table(name="acme_user")
*/
class User extends BaseAuditableEntity implements AdvancedUserInterface, \Serializable
{
/**
* #Orm\Id
* #Orm\Column(type="integer")
* #Orm\GeneratedValue
*/
private $id;
/**
* #Orm\Column(type="string", name="first_name")
*/
private $firstName;
/**
* #Orm\Column(type="string", name="last_name")
*/
private $lastName;
/**
* #Orm\Column(type="string", unique="true")
*/
private $email;
// Other properties
// Constructor
// Setters and getters
}
Here a link to the official inheritance mapping documentation of Doctrine 2.1: here
Hope this helps, don't hesitate to comment if you need more information.
Regards,
Matt

Resources