I am trying to embed collection of Tag forms to Service form, according to this tutorial. Tag and Service entities have many-to-many relationship.
Form is rendering correctly. But when I submit form, I get
Could not determine access type for property "tagList"
error. I don't understand why new Tag object is not added to the Service class by calling the addTag() method.
ServiceType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, array(
'label' => 'Title'
))
;
$builder->add('tagList', CollectionType::class, array(
'entry_type' => TagType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
)));
}
Service class
{
....
/**
* #ORM\ManyToMany(targetEntity="Tag", mappedBy="serviceList",cascade={"persist"})
*/
private $tagList;
/**
* #return ArrayCollection
*/
public function getTagList()
{
return $this->tagList;
}
/**
* #param Tag $tag
* #return Service
*/
public function addTag(Tag $tag)
{
if ($this->tagList->contains($tag) == false) {
$this->tagList->add($tag);
$tag->addService($this);
}
}
/**
* #param Tag $tag
* #return Service
*/
public function removeTag(Tag $tag)
{
if ($this->tagList->contains($tag)) {
$this->tagList->removeElement($tag);
$tag->removeService($this);
}
return $this;
}
}
Tag class
{
/**
* #ORM\ManyToMany(targetEntity="Service", inversedBy="tagList")
* #ORM\JoinTable(name="tags_services")
*/
private $serviceList;
/**
* #param Service $service
* #return Tag
*/
public function addService(Service $service)
{
if ($this->serviceList->contains($service) == false) {
$this->serviceList->add($service);
$service->addTag($this);
}
return $this;
}
/**
* #param Service $service
* #return Tag
*/
public function removeService(Service $service)
{
if ($this->serviceList->contains($service)) {
$this->serviceList->removeElement($service);
$service->removeTag($this);
}
return $this;
}
}
ServiceController
public function newAction(Request $request)
{
$service = new Service();
$form = $this->createForm('AppBundle\Form\ServiceType', $service);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($service);
$em->flush();
return $this->redirectToRoute('service_show', array('id' => $service->getId()));
}
return $this->render('AppBundle:Service:new.html.twig', array(
'service' => $service,
'form' => $form->createView(),
));
}
Could you please try to implement code from this URL?
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#owning-and-inverse-side-on-a-manytomany-association
First, please try to change mapped/inverse sides, and remove $service->addTag($this); from Tag::addService method.
Short version:
I just ran into this problem and solved it by adding a setter for the affected property:
Could not determine access type for property "tagList"
public function setTagList(Array $tagList)
{
$this->tagList = $tagList;
}
Long version:
The error message is signaling that Symfony is trying to modify the object's state, but cannot figure out how to actually make the change due to the way its class is set up.
Taking a look at Symfony's internals, we can see that Symfony gives you 5 chances to give it access and picks the best one in this order from top to bottom:
A setter method named setProperty() with one argument:
This is the first thing Symfony checks for and is the most explicit way to achieve this. As far as I'm aware this is the best practice:
class Entity {
protected $tagList;
//...
public function getTagList()
{
return $this->tagList;
}
//...
}
A combined getter and setter in one method with one argument:
It's important to realize that this method will also be accessed by Symfony in order to get the object's state. Since those method calls don't include an argument, the argument in this method must be optional.
class Entity {
protected $tagList;
//...
public function tagList($tags = null)
{
if($reps){
$this->tagList = $tags;
} else {
return $this->tagList;
}
}
//...
}
The affected property being declared as public:
class Entity {
public $tagList;
//... other properties here
}
A __set magic method:
This will affect all properties rather than just the one you intended.
class Entity {
public $tagList;
//...
public function __set($name, $value){
$this->$name = $value;
}
//...
}
A __call magic method (in some cases):
I wasn't able to confirm this, but the internal code suggests this is possible when magic is enabled on PropertyAccessor's construction.
Only using one of the above strategies is required.
Maybe the problem is that Symfony can't access that property?
If you look at where that exception is thrown (writeProperty method in the PropertyAccessor class) it says it can be thrown:
If the property does not exist or is not public.
In the tutorial you mentioned it has property $tags, and method addTag. I'm just guessing here, but maybe there's a convention where it tries to call a method names add($singularForm) and this is failing for you because the property is tagList and the method is addTag.
I'm not 100% sure, but you could try debugging by setting a stop point in that Symfony method to see why it's being thrown.
Maybe you forgot in the __construct() of Service class and Tag class to initialize $tagList and $serviceList like this ?
$this->tagList = new ArrayCollection();
$this->serviceList = new ArrayCollection();
This seems like an error with your constructor. Try this :
public function __construct()
{
$this-> tagList = new \Doctrine\Common\Collections\ArrayCollection();
}
It's a long shot, but looking at your annotations I think the problem might be related to your manyToMany relationship. Try to change the owning side and inverse side (Swap the relationship) unless you specifically need to update from both ends (In that case I think the only solution is to add the objects manually or use oneToMany relationships).
Changes made only to the inverse side of an association are ignored.
Make sure to update both sides of a bidirectional association (or at
least the owning side, from Doctrine’s point of view)
This is a problem related to Doctrine I have suffered before, see:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/unitofwork-associations.html
Based on Symfony 3.3.10
I actually faced this problem many and many times, finally once i discovered where this problem was coming from, depending on the name you give to your entity property it can happen that the adder and the remover for your collection property aren't exactly what you are expecting.
Example: Your entity properity name is "foo" and you would expect the adder to be called "addFoo" and remover "removeFoo", but then all of a sudden the "Could not determine access type for property" appear.
So you start going into fear searching for w/e problems in your code, instead you just have to look this file inside Symfony core files:
vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
Inside this file there's a method called findAdderAndRemover.
Go there with your debugger and you will eventually find out that symfony searches for weird name for your adder/remover, they may actually end with "um" or "on" or "us" depending on the language (human language) you used to name them. Since i'm Italian this happen quite often.
Watch out for that, since the fix may be as simple as changing the name used for your add/remove method inside your entity to make them match with what Symfony core is looking for.
This happens to me when i use bin/console doctrine:generate:entities to create the methods automatically for me
If you are using symfony, and use EntityRepository instead of CollectionType, make sure you use the 'multiple' => true, on your form build, otherwise the input will be for one entity and not for many, therefore it will call the setTagList instead of using the methods addTagList and removeTagList.
I wonder whether it's possible to have relations between entities and value objects or if a third entity is mandatory as relation target. The purpose could be to flag different kind of entities with a common data structure that has it's own business logic. Any idea ?
Update :
Let's say I have a business object to model SCAP CPE namings :
<?php
namespace Scap\Cpe\Naming;
/**
* Cpe22 represents the naming convention in CPE Naming version 2.2
* Accepted values are only CPE URIs
*/
class Cpe22
{
protected $cpe;
public function __construct($cpe)
{
if (! preg_match('/[c][pP][eE]:\/[AHOaho]?(:[A-Za-z0-9\._\-~%]*){0,6}/', $cpe)) {
throw new InvalidNamingException();
}
$this->cpe = $cpe;
}
}
If I want to flag different kind of entities with this VO in a one-to-many way, I can think of 2 different ways :
Entities are directly related to VOs so that the relation identifiers could be the entities references and the VOs themselves (= key composed from referenced identifier and representative VO fields)
Entities are related to third entity types, that embed the VOs so that the relation identifiers are those third entities identifiers
So I wonder if only the second option is available or if the first can somehow be implemented.
You should use a custom mapping type for things like this. For example, it could look something like this:
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform
class Cpe22Type extends Type
{
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return $platform->getVarcharTypeDeclarationSQL(array('length' => 250));
}
public function convertToPHPVale($value, AbstractPlatform $platform)
{
return new Cpe22($value);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return (string) $value;
}
public function getName()
{
return 'cpe22';
}
public function requiresSQLCommentHint(AbstractPlatform $platform)
{
return true;
}
}
Obviously, you have to add a __toString() method to your Cpe22 class for this to work.
After registering the type (how you do this depends on your framework, in symfony for example you do it in config.yml) you can simply map your field as type cpe22:
/**
* #ORM\Column(name="my_cpe", type="cpe22")
*/
public $myCpe;
I would like to use Doctrine’s preflush features to automatically set the value of form elements based on the values of other elements. The preflush statements in my ZF2 entity might look like this:
/**
* set eventEndDate = eventStartDate for single-day events on pre flush.
*
* #ORM\PreFlush
* #return void
*/
public function onPreFlush(PreFlushEventArgs $args)
{
$currentEventType = $this->getEventType();
if ($currentEventType=='meeting') {
$this->eventEndDate = $this->getEventStartDate();
}
}
My challenge is that I don’t have a getEventType() getter because eventType is a discriminator column in my inheritance mapping. How can a preflush function in an entity evaluate a discriminator value from within the entity?
You can use instanceof php operator to check object's class. Like that:
if ($this instanceof MeetingEntityClass) {
//...
}
In my zf2 project, I have doctrine 2 entities that reference the user entity as created by as the following:
/**
* #ORM\ManyToOne(targetEntity="User")
* #ORM\JoinColumn(name="created_by", referencedColumnName="id")
**/
protected $createdBy;
and I want to set this reference in the PrePersist, how can I do that?
I tried the following (I don't know if it is right):
/** #ORM\PrePersist */
public function prePersist() {
if ($this->createdBy === null) {
$session = new \Zend\Authentication\Storage\Session;
$userId = $session->read();
if ($userId !== null) {
$this->createdBy = $userId;
} else {
throw new \Exception("Invalid User");
}
}
}
but the main problem is that the $userId is an integer, and createdBy must hold the reference of the user not the user ID.
is there a better way to do that? if no, how can I get the reference instead of the user ID?
Instead of directly accessing your session storage, you might configuring a Zend\Authentication\AuthenticationService to handle your authenticated identity.
Then you could set your Namespace\For\Entity\User as your AuthenticationService identity and inject the authentication service via setter injection (see this post about hooking into Doctrine lifecycle events).
Then you should be able to do this:
/** #ORM\PrePersist */
public function prePersist() {
if (empty($this->createdBy)) {
$this->setCreatedBy($this->getAuthenticationService()->getIdentity());
}
}
...or you could add a $loggedInUser property to your entity, and inject the logged in User directly, instead of creating a dependency on the AuthenticationService (or session storage). This is probably the better way, because it simplifies your tests:
/** #ORM\PrePersist */
public function prePersist() {
if (empty($this->createdBy)) {
$this->setCreatedBy($this->getLoggedInUser());
}
}
Note that I got rid of the type-checking in your prePersist method by using setters, because then you can handle that via type-hinting in your setters like this:
public function setAuthenticationService(\Zend\Authentication\AuthenticationService $authenticationService){/** do stuff */};
public function setLoggedInUser(\Namespace\For\Entity\User $user){/** do stuff */};
public function setCreatedBy(\Namespace\For\Entity\User $user){/** do stuff */};
For example, we have an external DB of Countries and Cities. We need to be able to read that external DB with the following conditions:
We can't alter or modify in any way the World DB. We can't add FK for example.
When using the external DB, we want to keep an internal reference, for example for our entity "User" we want to keep a reference such as User->city
We want to have an internal entity CustomCities where users can create their own cities.
What would be the best approach to do this?
We have tried several options but all of them break in one way or another. One recommendation was to use a #Table with an external reference readOnly but that didn't work.
The closest solution we have found for this is to use an in-between class that represents a City object, but doesn't really hold data, and then via native queries, we populate that fake object. Then using internal logic we determine if the requested item such as User->getCity() came from the City DB or came from the CityCustomDB...
Any ideas on how to approach this?
I've taken a guess at the possible schema, have you tried using Class Table Inheritance so that the country essentially becomes your interface.
interface CountryInterface
{
public function getName();
}
So your entities might look like this
/**
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="type", type="string")
* #DiscriminatorMap({
* "internal" = "InternalCountry"
* ,"external" = "ExternalCountryAlias"
* })
*/
abstract class AbstractCountry implements CountryInterface
{
protected $id;
}
class InternalCountry extends AbstractCountry
{
protected $name;
public function getName()
{
return $this->name;
}
}
The ExternalCountryAlias works like a proxy to ExternalCountry, but I named it Alias so not to confuse it with Data Mapper Proxies.
class ExternalCountryAlias extends AbstractCountry
{
/**
* #OneToOne(targetEntity="ExternalCountry")
* #JoinColumn(name="external_country_id"
* ,referencedColumnName="id")
*/
protected $externalCountry;
public function getName()
{
return $this->externalCountry->getName();
}
}
ExternalCountry doesn't have to extend from the base class.
class ExternalCountry
{
protected $name;
public function getName()
{
return $this->name;
}
}
So when you get a country you are referencing the base class. So lets say country.id = 1 is and internal country and country.id = 2 is an external country.
// returns an instance of InternalCountry
$entityManager->find('AbstractCountry', 1);
// returns an instance of ExternalCountryAlias
$entityManager->find('AbstractCountry', 2);
And because they both implement CountryInterface you don't have to worry where they came from, you still access the name by calling getName();