Doctrine – ArrayCollection->add() throws an exception
- Čamo
- Member | 798
Hello,
Can you help me please find out where is the problem? ArrayCollection->add()
throws me an exception: “Call to a member function add() on array”.
I have created entity like this:
<?php
namespace App\Model\Entity;
use Doctrine\ORM\Mapping as ORM;
use Kdyby;
use App;
/**
* @ORM\Entity(repositoryClass="App\Model\Repository\ArticleRepository")
* @ORM\Table(options={"collate"="utf8_slovak_ci"})
*/
class Article extends Kdyby\Doctrine\Entities\BaseEntity
{
use Kdyby\Doctrine\Entities\Attributes\Identifier;
public function __construct()
{
$this->tags = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @ORM\manyToMany(targetEntity="Tag", inversedBy="articles")
* @ORM\joinTable(
* name="article_tag",
* joinColumns={
* @ORM\joinColumn(name="article_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* @ORM\joinColumn(name="tag_id", referencedColumnName="id")
* }
* )
*/
protected $tags;
...
but if I try use it this way:
$tags = $tagRepository->findBy( [ 'id' => [ 9, 8, 7 ] ] );
$user = $userRepository->find( 1 );
$article = new Model\Entity\Article();
$date = date( 'j.n.Y H:i:s' );
$article->title = 'Some title ' . $date;
$article->content = 'Some content ' . $date;
$article->user = $user;
$article->tags->add( $tags );
$this->em->persist( $article );
$this->em->flush();
it throws me an exception: “Call to a member function add() on array”
May be the problem is that I added __constructor later and something is wrong
with cache. But console orm:schema-tool:update writes: “Nothing to
update …”
Thank you.
EDIT:
As I found out correct syntax is
foreach ( $tags as $tag )
{
$article->tags->add( $tag );
}
but it still throws the same exception.
Last edited by Čamo (2015-10-21 21:57)
- Čamo
- Member | 798
Azathoth:
Base entity __get() has this implementation:
...
// protected attribute support
$properties = $this->listObjectProperties();
if (isset($properties[$name = func_get_arg(0)])) {
if ($this->$name instanceof Collection) {
$coll = $this->$name->toArray();
return $coll;
} else {
$val = $this->$name;
return $val;
}
}
...
so it should return array. Also as I found out the implementation of __call enables to use
foreach ( $tags as $tag )
{
$article->addTag( $tag );
}
which works for me. But as I try to solve it I created new Test entity with
tags collection and it works also with $test->tags->add( $tag
). So I am confused.
EDIT: Test entity works cause it has getTags() method. That was
my mistake.
Last edited by Čamo (2015-10-21 22:48)
- Filip Procházka
- Moderator | 4668
Be aware, that using getters that return and expose raw collections is a bad practice! That's why Kdyby returns only the Array.
If instead of extending BaseEntity
, you use MagicAccessors,
you'll get the same functionality, but instead of returning arrays it returns
readonly collections. Which doesn't solve your problem, I know :) But
BaseEntity
has been deprecated.
If you wanna use magic, then instead of
$article->tags->add($tag);
use
$article->addTag($tag);
You can also disable the magic simply by declaring the field private. Which means you'd have to define the method yourself.
public function addTag(Tag $tag) {
$this->tags->add($tag);
}
Anyway, you should not expose tha raw collection, it's bad.
- Čamo
- Member | 798
“Be aware, that using getters that return and expose raw collections is
a bad practice!”
You are right.
“BaseEntity has been deprecated.”
Do you mean class MyEntity extends BaseEntity at all?
It is little weird. Php use public/protected/private to solve encapsulation. Properties of Doctrine Entities has to be min protected. I don't understand that. Of course I am totally newbie. But that setters and getters make huge objects form every entity. It looks like a haystack :|.
Why Doctrine can't simply check (if I use $prop->add()) type hint and if property is public – which simply means I want to add?
Have a nice day.
Last edited by Čamo (2015-10-23 21:41)
- Azathoth
- Member | 495
There is no reason to have public properties. It breaks encapsulation.
So all properties should be private or protected.
And you also should not use collections (collections as object properties)
outside of entities. Because of encapsulation.
When you use public properties, entity has no control which value it has. Thus,
entity could be in invalid state.
NEVER let your entities be in invalid state.
In setter you can validate (whether it is string, bool, integer, phone number,
email, whatever). In public property, you can not validate.
And do not generate all getters and setters immediately when creating new
class.
Think, whether you need this getter/settier.
It is bad practise to generate all setters.
Of course, some times you need setter for every property. But when you do not
need CRUD, do not use CRUD.
This article can offer you another point of view on entities.
This article is also very useful.
And your entities should be valid and should work even if you wont be using Doctrine.