I'm under SF2.0.15 with Doctrine2 and I have two entities.
-Expedition
- Step
To explain, one expedition can have several steps and one step can belong to several expeditions. In addition, one expedition belongs to his founder (named "owner" and stored in the User entity). So, I have chosen to make a ManyToMany joining between Expedition and Steps tables. In your opinion, is it a good choice or a wrong choice ?
I want to create a method which select all the steps which belong to one expedition (I have the expedition's Id which is contained in $id_exp). So, I have read lots of topics in the Internet but it always fail and I want to know why...
The entity Expedition.php
/**
* #ORM\ManyToOne(targetEntity="Easymuth\UserBundle\Entity\User")
* #ORM\JoinColumn(nullable=false)
*/
private $owner;
/**
* #ORM\ManyToMany(targetEntity="Easymuth\ExpeditionBundle\Entity\Step", cascade={"persist"})
*/
private $steps;
/**
* Add steps
*
* #param Easymuth\ExpeditionBundle\Entity\Step $steps
*/
public function addStep(\Easymuth\ExpeditionBundle\Entity\Step $step)
{
$this->steps[] = $step;
}
public function __construct()
{
$this->steps = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get steps
*
* #return Doctrine\Common\Collections\Collection
*/
public function getSteps()
{
return $this->steps;
}
ExpeditionRepository.php :
namespace Easymuth\ExpeditionBundle\Entity;
use Doctrine\ORM\EntityRepository;
class ExpeditionRepository extends EntityRepository
{
public function getStepsFromExpedition($id_exp) {
$qb = $this->createQueryBuilder('e')
->leftJoin('e.steps', 's')
->addSelect('s')
->where('e.id = :id')
->setParameter('id', $id_exp);
return $qb->getQuery()->getResult();
}
}
And finally in my controller, I have :
namespace Easymuth\ExpeditionBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Easymuth\ExpeditionBundle\Entity\Expedition;
class MainController extends Controller
{
public function stepsAction($id_exp) {
$expedition = $this->getDoctrine()
->getEntityManager()
->getRepository('EasymuthExpeditionBundle:Expedition')
->getStepsFromExpedition($id_exp);
print_r($expedition->getSteps()); // it displays a very long contents........
//return $this->render('EasymuthExpeditionBundle:Main:steps.html.twig'));
}
}
The displayed error on the print_r (or var_dump) is :
Fatal error: Call to a member function getSteps() on a non-object in /Applications/MAMP/htdocs/easymuth/src/Easymuth/ExpeditionBundle/Controller/MainController.php
Thank you very much for your help !
It is a good choice, you have to use ManyToMany associations for this design, good point !
But be careful, if you want to add information in your association (like order for example, can be useful for step in expedition), you have to create a new entity.
Check here for more info.
Then, the problem is in your controller. (You don't need additional function in your repository)
If you want to get all the steps from one expedition, just do in your controller :
//find your expedition
$expedition = $this->getDoctrine()
->getEntityManager()
->getRepository('EasymuthExpeditionBundle:Expedition')
->find($id_exp);
//then get the steps from this expedition
$steps = $expedition->getSteps();
You have to be sure that the expedition with $id_exp does exist or it will throw an error when you want to use your $expedition variable (because it is set at null)
You can check existence this way :
if(!$expedition) {
//do some stuff like throwing exception
}
Related
EDIT
Forgot to include the package i am using: https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html
I am trying to figure out how i can pass extra data to the factory to use afer persist in some custom code.
I have a factory that generates a University, and after that is inserted in the database i need to then use the Course factory to assign courses which i want to do in afterPersist.
I have tried to add my own method to set the array of courses on the object:
/**
* #param array $courses
* #return $this
*/
public function setCourses(array $courses) : self
{
$this->courses = $courses;
return $this;
}
And then call it like this:
foreach($this->universities as $university){
UniversityFactory::new()->setCourses($university['courses'])->create([
'name'=>$university['name'],
'slug'=>$university['slug']
]);
}
However, i end up with an empty array in the afterPersists (this->courses is empty):
return $this->afterPersist(function(University $university){
foreach($this->courses as $type => $courseName){
CourseFactory::new()->create([
'name'=>$courseName,
'type'=>$type,
'uni'=>$university
]);
}
});
The array of courses does get assigned, but when running create() it gets emptied.
Edit 2
Dont forget to:
remove the code of $this->afterPersist
use Course entity class namespace in the University class file
update what you pass to setCourses:
foreach($this->universities as $university){
UniversityFactory::new()->setCourses(
array_map(function ($type,$courseName){
return (new Course())->setType($type)->setName($courseName);
}, array_keys($university['courses']), array_values($university['courses']))
)->create([
'name'=>$university['name'],
'slug'=>$university['slug']
]);
}
Edit 1
You can actualy handle persisting courses with their uni relation without using afterPersist hook. you can just set cascade={"persist"} as in below and assign uni of each course to $this in the university setCourses Method
/**
* #param Course[] $courses
* #return $this
*/
public function setCourses(array $courses) : self
{
$this->courses = [];
foreach($courses as $course){
if (!$this->courses->contains($course)) {
$this->courses[] = $course;
$course->setUni($this);
}
}
return $this;
}
Original Ansewar
Based on the fact that the $courses property is set but not persised
Its most likely you forgot to add cascade={"persist"} in the doctrine relation annotation
/**
* #ORM\OneToMany(... cascade={"persist"})
*/
private Collection|array $courses;
More about Transitive persistence / Cascade Operations
I'm training myself on Symfony and struggling with a problem with bidirectional association (very basic) because by dumping my entity in a twig template I verify that data is correct but the association is always null.
My problem is like this one but the solution is not shared.
I read the documentation here and it seems I follow the right steps.
My db contain a Parent table and a Children table related by children.parent_id as foreign key, both table are popolated and I use DOCTRINE:GENERATE:ENTITIES and DOCTRINE:GENERATE:CRUD.
In Parents class I have:
function __construct() {
$this->lastUpd = new \DateTime();
$this->children = new ArrayCollection();
}
/*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Children", mappedBy="parent_id", cascade={"persist"})
*/
private $children;
public function setChildren(ArrayCollection $children) {
return $this->children = $children;
}
public function getChildren() {
return $this->children;
}
In Children class I have:
/**
* #var \AppBundle\Entity\Parents
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Parents", inversedBy="children")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="parent_id", referencedColumnName="parent_id")
* })
*/
private $parent_id;
/**
* Set parent_id
* #param \AppBundle\Entity\Parents $parent_id
* #return Parents
*/
public function setParentID(\AppBundle\Entity\Parents $parent_id= null) {
$this->parent_id = $parent_id;
return $this;
}
/**
* Get parent_id
* #return \AppBundle\Entity\Parents
*/
public function getParentID() {
return $this->parent_id;
}
As additional info looking at Simfony profiler (of parents list page) -> Doctrine -> Entities Mapping I found (with no errors) AppBundle\Entity\Parents and AppBundle\Entity\Type (a working unidirectional OneToMany association).
I am sorry to post a so basic error and I bet the solution is simple but I can't see it.
note: Im assuming that youre not creating an ArrayCollection of children and adding them en'mass.
you dont have any addChild method (which you need to call).
this is easy with an ArrayCollection.
public function addChild(Children $child) {
$this->children->add($child);
}
you could also do with a removeChild as well.
public function removeChild(Children $child) {
$this->children->removeElement($child);
}
then when in your controller.
$child = new Children();
$parent->addChild($child);
then when you persist the parent object, the children will follow due to the cascade persist. I would also add cascade={"remove"} as well, so when you delete the parent, the children will go to.
Let say I have a Company for which I manage Employees, Cars, Contracts, Buildings, Sites, Products, etc. As you can guess, these are quite independant things, so no inheritance is possible.
For each of these elements (i.e. Entities), I want to be able to attach one or several Documents (click on a button, form opens, select one/several Document or upload a new one).
Linking Document to one kind of entity is not a problem, my problem is that there are many kinds of entities. How should I manage that? I have 2 ideas which have their own problems...:
Create a ManyToMany relationship between Document and Employee, another between Document and Car, etc.
Problem: I have to duplicate the Controller code to attach Document, duplicate the forms, etc.
Create a single join table containing the Document's ID, the related entity's ID and the related entity's class name.
Problem: it doesn't look really clean to me, I didn't really dig in this way but I feel I'll have a lot of "entity mapping" problems.
Any suggestion?
[EDIT]
In fact I have to do the same for Event as well: I need to link some Events to some Employees and/or to some Cars, etc. And in my real case, I have more than 10 Entities to be linked to Event and/or Document, which means duplicating more tha 20 times the code if I go with the solution 1!
Assuming you're using Doctrine ORM, i think you're searching for the Mapped Superclasses inheritance.
The docs are better than words :
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#mapped-superclasses
So finally I managed to solve my problem, following #Rpg600 idea about Mapped Superclasses.
This is probably not the best and cleanest solution ever, I'm not really proud of it but it does the job and it is still better than my first ideas.
I create a BaseEntity which is my a mapped superclass (Employee, Car, etc. Entities have to extend this Class):
/**
* BaseEntity
* #ORM\MappedSuperclass
*/
class BaseEntity {
/**
* #ORM\OneToOne(targetEntity="MyProject\MediaBundle\Entity\Folder")
*/
private $folder;
/**
* Set folder
* #param \Webobs\MediaBundle\Entity\Folder $folder
* #return BaseEntity
*/
public function setFolder(\Webobs\MediaBundle\Entity\Folder $folder = null){
$this->folder = $folder;
return $this;
}
/**
* Get folder
* #return \Webobs\MediaBundle\Entity\Folder
*/
public function getFolder(){
return $this->folder;
}
}
As it is not possible to have a Many-to-Many relationship in a superclass, I use a Folder which will contain one or several Document. This is the dirty part of the solution ; the folder table basically contain only one field which is the id...
class Folder
{
private $id;
/**
* Note : Proprietary side
* #ORM\ManyToMany(targetEntity="MyProject\MediaBundle\Entity\Document", inversedBy="folders", cascade={"persist"})
* #ORM\JoinTable(name="document_in_folder")
*/
private $documents;
// setters and getters
Then I create a helper class (which is declared as a service) to manage the link between any Entity and the Document:
class DocumentHelper extends Controller
{
protected $container;
/** ************************************************************************
* Constructor
* #param type $container
**************************************************************************/
public function __construct($container = null)
{
$this->container = $container;
}
/** ************************************************************************
* Attach Document(s) to an $entity according to the information given in the
* form.
* #param Entity $entity
* #param string $redirectRouteName Name of the route for the redirection after successfull atachment
* #param string $redirectParameters Parameters for the redirect route
* #return Response
**************************************************************************/
public function attachToEntity($entity, $redirectRouteName, $redirectParameters)
{
$folder = $entity->getFolder();
if($folder == NULL){
$folder = new Folder();
$entity->setFolder($folder);
}
$form = $this->createForm(new FolderType(), $folder);
// ------------- Request Management ------------------------------------
$request = $this->get('request');
if ($request->getMethod() == 'POST') {
$form->bind($request); // Link Request and Form
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($folder);
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl($redirectRouteName, $redirectParameters));
}
}
return $this->render('MyProjectMediaBundle:Folder:addDocument.html.twig', array(
'form' => $form->createView(),
'entity' => $entity,
));
}
Doing that way, I just have to add one small action in each relevant controller, let say EmployeeController.php:
public function addDocumentAction(Employee $employee)
{
$redirectRouteName = 'MyProjectCore_Employee_see';
$redirectParameters = array('employee_id' => $employee->getId());
return $this->get('myprojectmedia.documenthelper')->attachToEntity($employee,$redirectRouteName,$redirectParameters);
}
Same principle for the display, in the helper I have the common function which I call in my already-existing seeAction() and in the TWIG file I import the common "Document list" display.
That's all folks!
I hope this can help :)
I cant find a good documentation about the "ORM expression" i see inside of my entities, so im a bit confuse.
I have this inside my user entity:
/**
* #var ArrayCollection $administered
*
* #ORM\ManyToMany(targetEntity="Done\PunctisBundle\Entity\Brand", inversedBy="admins")
* #ORM\JoinTable(name="user_brand_administered")
**/
protected $administered;
This is working fine, but i need something more simple, i need the var administrated to get all the values of the Brand entity instead of joining the table user_brand_administrated like it does on the code bellow. How can i do this?
Why do you want all the value here ?
With a custom EntityRepository you can use a method to retrieve your object like you want.
For exemple.
class UserRepository extends EntityRepository
{
public function findOneWithRelation($id)
{
$qb = $this->createQueryBuilder('u')
->select('u, b')
->leftJoin('u.administered, b')
->where('u.id = :id')
->setParameter('id', $id)
->getQuery()
->getResult();
}
}
And attach this repository with your user entity:
/**
* #Entity(repositoryClass = "Path\To\UserRepository")
*/
class User
{
// ...
}
Like this you can retrieve your user with all the brands.
Hope it's helpful.
Best regard.
Hi i had fully successfully setted my entity onetoMany and ManyToOne i generated setters and getters and in user entity it created this method:
user entity:
/**
* #ORM\OneToMany(targetEntity="TB\RequestsBundle\Entity\Requests", mappedBy="followeeuser")
*/
protected $followees;
requests entity:
/**
* #ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User", inversedBy="followees")
* #ORM\JoinColumn(name="followee_id", referencedColumnName="id", nullable=false)
*/
protected $followeeuser;
And when i using my own custom queries it works good... but i cant figure out how to use this generated function from symfony:
public function addFollowee(\TB\UserBundle\Entity\User $followee)
{
$this->followees[] = $followee;
}
I dont know what to pass there... i tried first get user object based on id of user from twig... worked good but the error occur:
$user->addFollowee($userRepository->find($target_user_id));
Found entity of type TB\UserBundle\Entity\User on association TB\UserBundle\Entity\User#followees, but expecting TB\RequestsBundle\Entity\Requests
Maybe you should think about what you're trying to before coding it. Grab a pen and a sheet of paper. :)
Tell me if I'm wrong, but here is what I think you're trying to do :
One user can have many "followee".
One "followee" can have one user.
So, a OneToMany relation is ok.
Here is how to write it, from the doc :
Requests.php (btw, you should use Request.php)
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="requests")
**/
private $user;
User.php
/**
* #ORM\OneToMany(targetEntity="Requests", mappedBy="user", cascade={"all"})
**/
private $requests;
public function __construct()
{
$this->requests = new \ArrayCollection();
}
Now you can check if you your relation is ok, and update your schema :
php app/console doctrine:schema:validate
php app/console doctrine:schema:update --force
About getters/setters :
Requests.php
public function getUser()
{
return $this->user;
}
public function setUser(User $user) // Please add a Use statement on top of your document
{
$this->user = $user;
return $this;
}
User.php
public function addRequest(Requests $request)
{
$this->requests->add($request);
return $this;
}
public function removeRequest(Requests $request)
{
$this->requests->removeElement($request);
return $this;
}
// Get requests and set requests (you know how to write those ones)
Now, to set a user to a Request, use
$request->setUser($user);
And to add a Request to a user, use
$user->addRequest($request);