I need to set follower, following (myFriends) hasFriend logic to my project. column "odobera" means "following" to example nick(id user) odobera (following to this user). User (25) is following user(37).
Requests table:
User entity:
/**
* #ORM\OneToMany(targetEntity="TB\RequestsBundle\Entity\Requests", mappedBy="odobera")
*/
protected $followers;
/**
* #ORM\OneToMany(targetEntity="TB\RequestsBundle\Entity\Requests", mappedBy="nick")
*/
protected $myFriends;
public function __construct()
{
parent::__construct();
$this->followers = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get myFriends
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getMyFriends()
{
return $this->myFriends;
}
/**
*
* #param \TB\UserBundle\Entity\User $user
* #return bool
*/
public function hasFriend(User $user)
{
return $this->myFriends->contains($user);
}
class Requests
{
/**
* #ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User", inversedBy="myFriends")
* #ORM\JoinColumn(name="nick", referencedColumnName="id")
*/
protected $nick;
/**
* #ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User",inversedBy="followers")
* #ORM\JoinColumn(name="odobera", referencedColumnName="id")
*/
protected $odobera;
In controller:
$myFollowers=$user->getMyFriends();
returns:
What is good: returns 1 record. I actually follow just one person as you can see here the id of record is 24395
DB requests table:
I don't know if is good that getMyFriends function returns response in that "format". Please look at it carefully.
Then I have select followers from query and in loop:
{% for follower in followers %}
and i print data like this (works greate) {{ follower.nick }}
or if i want some fields from user entity {{ follower.nick.rank }}
{% endfor %}
But the problem is here:
{% if (app.user.hasFriend(follower.nick)) %}
That returns false, why? I follow this user as I checked in controller with dump :P Few lines over.
The problem seems to be that you are comparing two different type variable.
When you do this: {% if (app.user.hasFriend(follower.nick)) %} the following function is called:
/**
*
* #param \TB\UserBundle\Entity\User $user
* #return bool
*/
public function hasFriend(User $user)
{
return $this->myFriends->contains($user);
}
This function is called, taking a User type $user variable and you then use the contains() function on $this->myFriends.
$this->myFriends is an ArrayCollection of Requests (so different type than User) and from the doctrine documentation about contains():
The comparison of two elements is strict, that means not only the
value but also the type must match.
http://www.doctrine-project.org/api/common/2.1/class-Doctrine.Common.Collections.ArrayCollection.html
Related
I have a product entity and product image entity. I want to use soft delete on product entity only and make a delete on product image entity.
The soft delete works fine. When I delete the product, the deleted_at column is set to current time.
So I would like to delete product image when the deleted_at column is updated.
I was wondering if I can do it directly in entity class? and how?
Product entity where I try to make the collection delation in setDeletedAt function.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProductRepository")
* #ORM\Table(name="product")
*/
class Product
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\ProductImage", mappedBy="product", orphanRemoval=true, cascade={"persist"})
*/
private $productImages;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $deleted_at;
public function __construct()
{
$this->productImages = new ArrayCollection();
}
public function setDeletedAt(?\DateTimeInterface $deleted_at): self
{
// Here I try to remove images when deleted_at column is updated
$productImage = $this->getProductImages();
$this->removeProductImage($productImage);
$this->deleted_at = $deleted_at;
return $this;
}
/**
* #return Collection|ProductImage[]
*/
public function getProductImages(): Collection
{
return $this->productImages;
}
public function addProductImage(ProductImage $productImage): self
{
if (!$this->productImages->contains($productImage)) {
$this->productImages[] = $productImage;
$productImage->setProduct($this);
}
return $this;
}
public function removeProductImage(ProductImage $productImage): self
{
if ($this->productImages->contains($productImage)) {
$this->productImages->removeElement($productImage);
// set the owning side to null (unless already changed)
if ($productImage->getProduct() === $this) {
$productImage->setProduct(null);
}
}
return $this;
}
}
But when I make the soft delete, setDeletedAt() is called and the following error is returned:
Argument 1 passed to App\Entity\Product::removeProductImage() must be an instance of App\Entity\ProductImage, instance of Doctrine\ORM\PersistentCollection given, called in ...
Thanks for your help!
---- UPDATE ----
Solution provided by John works fine:
foreach ($this->getProductImages() as $pi) {
$this->removeProductImage($pi);
}
Thanks!
pretty self-explaining error:
at this point:
$productImage = $this->getProductImages();
$this->removeProductImage($productImage);
you are passing a collection instead a single ProductImage object.
to delete them all, just do:
foreach ($this->getProductImages() as $pi) {
$this->removeProductImage($pi);
}
I have a problem with ManyToMany association. I cant't figure out how to fetch data from database.
I have 2 entities - Teacher and Subject.
Class Subject
/**
*
* #ORM\ManyToMany(targetEntity="Teacher", mappedBy="subjects")
*/
private $teachers;
public function __construct() {
$this->teachers = new ArrayCollection();
}
/**
* Add teacher
*
* #param \AppBundle\Entity\Teacher $teacher
*
* #return Subject
*/
public function addTeacher(\AppBundle\Entity\Teacher $teacher)
{
$this->teachers[] = $teacher;
return $this;
}
/**
* Remove teacher
*
* #param \AppBundle\Entity\Teacher $teacher
*/
public function removeTeacher(\AppBundle\Entity\Teacher $teacher)
{
$this->teachers->removeElement($teacher);
}
/**
* Get teachers
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTeachers()
{
return $this->teachers;
}
And entity Teacher
Class Teacher
/**
*
* #ORM\ManyToMany(targetEntity="Subject", inversedBy="teachers")
*/
private $subjects;
public function __construct()
{
$this->subjects = new ArrayCollection();
}
/**
* Add subject
*
* #param \AppBundle\Entity\Subject $subject
*
* #return Teacher
*/
public function addSubject(\AppBundle\Entity\Subject $subject)
{
$this->subjects[] = $subject;
return $this;
}
/**
* Remove subject
*
* #param \AppBundle\Entity\Subject $subject
*/
public function removeSubject(\AppBundle\Entity\Subject $subject)
{
$this->subjects->removeElement($subject);
}
/**
* Get subjects
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getSubjects()
{
return $this->subjects;
}
All i want to do is fetch all information from Teacher + Subjects associated with them.
How should looks like my controller to do this?
I try do this by
$teacher = $this->getDoctrine()->getRepository(Teacher::class)->findAll();
return $this->render('admin/adminDashboard.html.twig', [
'teachers' => $teacher]);
But i still got errors
First, my advice is to change your $teacher variable to $teachers as
findAll() method returns an array so you have to iterate through it. In your controller:
foreach($teachers as $teacher) { // Here you have $teacher and you can do $teacher->getSubjects() }
In your twig template:
{% for teacher in teachers %}
{{ dump(teacher) }}
{% endfor %}
If that doesnt work, let us know what kind of errors do you get.
This is all assuming that you have entries inside your database, or from wherever you're fetching your entities.
if {% for t in teachers %}{{dump(t.subjects.count)}}{% endfor %} worked, it means the data is there.
If you mean {{teacher.subjects}} throws error: An exception has been thrown during the rendering of a template ("Catchable Fatal Error: Object of class Doctrine\ORM\PersistentCollection could not be converted to string. Well in case you have never seen this error before, take a close read:
Object of class Doctrine\ORM\PersistentCollection refers to subjects, which as a collection that it is, cannot be just thrown into an html tag like a string.
Depends on how you want to present your data but the very basics would be:
{% for t in teachers %}
<tr>
{% for s in t.subjects %}
<td>s.getName</td>
{% endfor %}
</tr>
{% endfor %}
assuming subject entity has a property name and a getter for it. Hope you get the gist.
let me know
I am stuck at this case, I reproduced it in an example from symfony documentation, here it how it looks:
FIXTURES
/**
* #ORM\Entity
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category", fetch="EAGER")
*/
private $products;
public function __construct()
{
$this->products = new ArrayCollection();
}
public function products(): Collection
{
return $this->products;
}
public function id()
{
return $this->id;
}
}
and related Product class
/**
* #ORM\Entity
*/
class Product
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
public function __construct($category)
{
$this->category = $category;
}
public function id()
{
return $this->id;
}
public function category()
{
return $this->category;
}
}
TEST
Now I have this snippet of test code where I want to fetch Category and be able to get its Products:
$cat = new Category();
$prod = new Product($cat);
$this->entityManager->persist($prod);
$this->entityManager->persist($cat);
$this->entityManager->flush();
$crepo = $this->getEntityManager()->getRepository(Category::class);
$c = $crepo->findAll()[0];
var_dump(get_class($c->products()), $c->products()->count())
What I am getting is products of class PersistentCollection which is expected, but the count is 0 while there should be 1 product.
I can see that in the database I have proper category and product records with proper foreign key set.
WORKAROUND
I am debugging PersistentCollection for products and can see that its flag is set to initialized = true. With this I am able to force this to work by calling
$c->products()->setInitialized(false);
$c->products()->initialize();
But afaik this is not how it should work, should it ?
I managed to found an answer. It basically works but not when run in the same process. If I split the script in two - first one persists, second retrieves the data then the products collection will contain products related to category.
This is because when it is done in single process doctrine does not know that the category in question has products, and since it retrieves the same object it just saved and that was created few lines above, the entity manager won't populate the collection using database but will use the one from the category object. And the category object does not have products in products collection, since there is no call like $this->products()->add($category) neither in Product constructor or anywhere else. Only forcing to reinitialize the collection works since then it really retrieves products from database
I have 2 entities Submission and Documents. 1 Submission can have Multiple documents.
Submission Entity:
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Document", mappedBy="submission",cascade={"persist", "remove" })
* #ORM\JoinColumn(name="id", referencedColumnName="submission_id")
*/
protected $document;
/**
* #return mixed
*/
public function getDocument()
{
return $this->document->toArray();
}
public function setDocument(Document $document)
{
$this->document[] = $document;
return $this;
}
Document Entity:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Submission", inversedBy="document")
* #ORM\JoinColumn(name="submission_id", referencedColumnName="id",onDelete="cascade", nullable=true)
*/
protected $submission;
public function getSubmission()
{
return $this->submission;
}
/**
* #param mixed $submission
*/
public function setSubmission($submission)
{
$this->submission = $submission;
}
After receiving files dropzonejs - I'm saving them into Document object, and then, i'm try to save this object into Submission, and persist.
$document = new Document();
$em = $this->getDoctrine()->getManager();
$media = $request->files->get('file');
foreach($media as $req){
$document->setFile($req);
$document->setPath($req->getPathName());
$document->setName($req->getClientOriginalName());
$em->persist($document);
}
$submission->setSubmissionStatus(true);
foreach($document as $item){
$submission->setDocument($item);
}
$submission->setUser($user);
$em = $this->getDoctrine()->getManager();
$em->persist($submission);
$em->flush();
Problem is that all the time, i'm receiving error that submission_title is not set, but that's not true, because i have set this field before. I haven't got idea, what is wrong.
I think you'll get some mileage out of following the tutorial over at http://symfony.com/doc/current/doctrine/associations.html, if you haven't already.
I can see that your getters / setters aren't optimal for associating more than one Document with your Submission.
As they write in the Symfony docs, where they want to associate one category with many products, they have the following code:
// src/AppBundle/Entity/Category.php
// ...
use Doctrine\Common\Collections\ArrayCollection;
class Category
{
// ...
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
private $products;
public function __construct()
{
$this->products = new ArrayCollection();
}
}
From the docs:
The code in the constructor is important. Rather than being
instantiated as a traditional array, the $products property must be of
a type that implements Doctrine's Collection interface. In this case,
an ArrayCollection object is used. This object looks and acts almost
exactly like an array, but has some added flexibility. If this makes
you uncomfortable, don't worry. Just imagine that it's an array and
you'll be in good shape.
So, you'll want to be sure the constructor for your Document entity has something like $this->submissions = new ArrayCollection();. I've changed the property to a plural name, because I think it's more semantically correct. But you can keep your $submission property name, if you like.
Next is to add a addSubmission, removeSubmission, and a getSubmissions method.
Then, your class might end up looking like this:
<?php
// src/AppBundle/Entity/Submission.php
namespace AppBundle\Entity
use Doctrine\Common\Collections\ArrayCollection;
class Submission
{
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Document", mappedBy="submission",cascade={"persist", "remove" })
* #ORM\JoinColumn(name="id", referencedColumnName="submission_id")
*
* #var ArrayCollection()
*/
protected $documents;
...
/**
* Instantiates the Submission Entity
*
* #return void
*/
public function __construct()
{
$this->documents = new ArrayCollection();
}
/**
* Returns all documents on the Submission
*
* #return mixed
*/
public function getDocuments()
{
return $this->documents;
}
/**
* Add document to this Submission
*
* #param Document $document The object to add to the $documents collection.
*
* #return Submission
*/
public function setDocument(Document $document)
{
$this->documents[] = $document;
return $this;
}
/**
* Remove a document from this Submission
*
* #param Document $document The object to remove from the $documents collection.
*
* #return Submission
*/
public function removeDocument(Document $document)
{
$this->documents->removeElement($document);
return $this;
}
}
I have a problem of recognition constrains() method in Symfony2. I have a relationship between the groups & Roles entities: So a group must have a mandatory role and the role may or may not have one or more groups. So in my addRoles function ( Groups $grp) I have checked each time if the group has a role so we joust if not assigning a role. But when inserting,
I encounter a problem:
PHP Fatal error: Call to undefined method
MemberShipManagement\GroupsBundle\Entity\Roles::contains() in
/var/www/Project_Console/src/MemberShipManagement/GroupsBundle/Entity/Roles.php
on line 118,
Class Groups:
/**
* #var Roles $role
*
* #ORM\ManyToOne(targetEntity="Roles", inversedBy="groups")
* #ORM\JoinColumn(name="role_id", referencedColumnName="id", nullable=false)
*
* #Assert\Valid()
*/
protected $role;
Class Roles:
/**
* #var ArrayCollection $groups
*
* #ORM\OneToMany(targetEntity="Groups", mappedBy="role", cascade={"remove"} )
*#Assert\Valid()
*/
protected $groups;
/**
* Add group
* #param Groups $grp
*/
public function addRoles(Groups $grp) {
// $grp->setRole($this);
if (!$this->groups->contains($grp)) {
$this->groups->add($grp);
}
return $this;
}
/**
* Remove groups
* #param Groups $groups
*/
public function removeRoles(Groups $groups)
{
if ($this->groups->contains($groups)) {
$this->groups->removeElement($groups);
}
return $this;
}
public function __construct()
{
$this->groups = new ArrayCollection();
}
thank you :)
Maybe you could try to check if the data exists before calling the contains method like,
public function addRoles(Groups $grp) {
// $grp->setRole($this);
if ( !isEmpty($this->groups) ){
if (!$this->groups->contains($grp)) {
$this->groups->add($grp);
}
}
return $this;
}
since the error you get seems to be derived from the fact that $groups are not interpreted as ArrayCollection defined in doctrine.(if so, you could've called contains method defined there)
http://www.doctrine-project.org/api/common/2.4/class-Doctrine.Common.Collections.ArrayCollection.html
As long as other examples using contains method are found like this(https://groups.google.com/forum/#!topic/doctrine-user/i6IhBPHALkk) your approach looks correct.
Otherwise you could try to change $role to private instead of protected?