Custom annotation was never imported - doctrine-orm

I created a doctrine annotation
namespace Fondative\GenBundle\Front\Annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* #Annotation
* #Target("PROPERTY")
*/
class ReferenceAnnotation extends Annotation {
}
use Fondative\GenBundle\Front\Annotation\ReferenceAnnotation ;
/**
* A Driver.
*
*
*
* #ORM\Entity
*
*/
class Driver {
/*
* #Reference
*/
private $name;
I get this exception
[Semantical Error] The annotation "#Reference" in property
Fondative\TestBundle\Entity\Driver::$name was never imported.

A very common mistake ;)
The package Doctrine/Annotation read annotations from comments via ReflectionProperty::getDocComment().
Problem example:
class MyClassA
{
/**
* Foo
*/
private $foo;
/*
* Bar
*/
private $bar;
}
$ref = new \ReflectionClass('MyClassA');
print sprintf(
"Comment of MyClassA::foo -> \n%s\n\n",
$ref->getProperty('foo')->getDocComment()
);
print sprintf(
"Comment of MyClassA::bar -> \n%s\n\n",
$ref->getProperty('bar')->getDocComment()
);
The property foo have a doc comments, but property bar not have comment, because exist typos in declare comment (one special char *).
In PHP doc comments must be started from two chars *!
Fix this typo, and all works ;)

class Driver {
/*
* #ReferenceAnnotation
*/
private $name;
or rename the annotation class to Reference and
use Fondative\GenBundle\Front\Annotation\Reference as Reference ;
/*
* #Reference
*/
private $name;

Related

Doctrine prevent error on OneToOne association where record doesn't exist in database

Problem
I'm trying to create a OneToOne association in a Laravel app using Doctrine. When trying to access the association I'm getting this error.
Entity of type 'Status' for IDs clientId(1) was not found
Versions:
Doctrine: 2.7.5
Laravel: 7.30.4
Code:
Client Class
<?php
namespace App\Client;
use App\Person\Person;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Client
* #package App\Client
*
* #ORM\Entity(repositoryClass="ClientRepository")
* #ORM\Table(name="client")
*/
class Client extends Person
{
/**
* #var Status
*
* #ORM\OneToOne(targetEntity="Status", mappedBy="client")
* #ORM\JoinColumn(name="id", referencedColumnName="client_id", nullable=true)
*/
protected $status;
/**
* #return Status
*/
public function getStatus()
{
return $this->status;
}
}
Status Class:
<?php
namespace App\Client\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Status
* #package App\Client\Entity
*
* #ORM\Entity(readOnly=true)
* #ORM\Table(name="status_view")
*/
class Status
{
/**
* #var int
*
* #ORM\Id()
* #ORM\Column(name="client_id", type="integer")
*/
protected $clientId;
/**
* #var \App\Client\Client
*
* #ORM\OneToOne(targetEntity="App\Client\Client", inversedBy="staus")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id", nullable=true)
*/
protected $client;
/**
* #var string
*
* #ORM\Column(name="status", type="string")
*/
protected $status;
/**
* #return string
*/
public function getStatus()
{
return $this->status;
}
}
Calling Code
$client->getStatus()->getStatus()
What I've tried/Answers I've looked at
Entity of type 'AppBundle\Entity\User' for IDs id(155) was not found - I'm not using a Doctrine filter, nor DQL.
https://stackoverflow.com/a/49416542/9530790 - This works, with a few tweaks, by swallowing up the exception, but it feels more like a hack when the docs say nullable should work.
https://stackoverflow.com/a/21887344/9530790 - This states nullable should work but it doesn't.
https://stackoverflow.com/a/15744449/9530790 - Same question different ans. States that Doctrine doesn't support zero-to-one associations, but nullable I believe should be what solves that, but for my problem it's not working. Also there's no link to the docs stating where Zero to one is not supported.
I believe that adding fetch="EAGER" should fix the null issue as elsewhere in our app that works, but when I add that I get an different Doctrine error spl_object_hash() expects parameter 1 to be object, null given, which again has to do with the association not existing.
"Well why aren't you experiencing the above error with your other associations". Great question! After a deep underwater excursion into the Doctrine code, I believe the reason is because those associations are nested and for some reason (I'm not sure why), when nested, the spl_object_hash function, in class UnitOfWork is not called.
Additional Notes:
This is what the object looks like when calling $client->getStatus(), before it errors on the next ->getStatus() call.
DoctrineProxies\__CG__\App\Client\Entity\Status {#2011
+__isInitialized__: false
#clientId: 4
#client: null
#status: null
…2
}
You can see it's a Client Proxy object that's created not a 'true' object, this is why it errors (with Entity of type 'Status' for IDs clientId(1) was not found) when not using fetch="EAGER", since eager loads a true object. See here
This code below in the Proxy object is the what causes the above error. Which is why I can't do a try catch in the parent ('true' Client class), since it errors before calling the parent.
/**
* {#inheritDoc}
*/
public function getStatus()
{
$this->__initializer__ && $this->__initializer__->__invoke($this, 'getStatus', []);
return parent::getStatus();
}
Question:
Why is nullable=true not working as expected, and what should/can I do to make it work?

Filtering a Collection using Criteria targeting an Embeddable object

Let's say you have a class Title, and the title is translated in multiple languages using TitleTranslation classes.
To indicate which language the title is translated in, each translation has a Locale value object.
For readability, I am attempting to provide the Title class with a getTitle(Locale $locale) method, returning the correct translation.
The easy way to do this would be to loop over all translations, and check each translation's locale.
However, I would like to accomplish this using Criteria, so only a single TitleTranslation will be fetched and hydrated.
To illustrate the case, a simplified version of the classes I'm working with:
Title:
/** #ORM\Entity #ORM\Table */
class Title
{
/**
* #ORM\OneToMany(targetEntity="TitleTranslation", mappedBy="element")
*/
private $translations;
}
TitleTranslation:
/** #ORM\Entity #ORM\Table */
class TitleTranslation
{
/**
* #ORM\ManyToOne(targetEntity="Title", inversedBy="translations")
*/
private $title;
/**
* #ORM\Column(type="string")
*/
private $translation;
/**
* #ORM\Embedded(class="Locale")
*/
private $locale;
public function getTranslation() : string
{
return $this->translation;
}
}
Locale:
/** #Embeddable */
class Locale
{
/** #ORM\Column(type="string")
private $locale;
public function __toString()
{
return $this->locale;
}
}
I have made the following attempts, all of which are unsuccessful:
public function getTitle(Locale $locale)
{
$localeCriteria = Criteria::create()->where(Criteria::expr()->eq('locale', $locale));
/** #var TitleTranslation | bool $translation */
$translation = $this->translations->matching($translationCriteria)->first();
return $translation ? $translation->getTranslation() : null;
}
This approach fails with an ORMException "Unrecognized field: locale", which seems normal, as Embeddables should be queried as "locale.locale" (field in containing class.field in VO).
However, using this notation:
$localeCriteria = Criteria::create()->where(Criteria::expr()->eq('locale.locale', $locale));
Fails with Undefined property: TitleTranslation::$locale.locale
Am I missing something, or is this approach simply not possible?

Doctrine Annotation in #var

Base on the documentation, #var will be skipped during processing, however, an exception has been thrown in my custom annotation.
[Type Error] Attribute "element" of #DisplayProperty declared on property UmEnquiryRequestProposalView::$eventStartDate expects a(n) string|array, but got string.
DisplayProperty
/**
* #Annotation
* #Target("PROPERTY")
*/
class DisplayProperty {
/**
* #var string|array
*/
public $element = '';
}
Also, I have tried to add #var into ignore list
AnnotationReader::addGlobalIgnoredName('var');
But it doesn't work. How can I make it works by keeping #var?

Table already exist

I am using doctrine 2 in zend framework 2. Below is my entity file. The problem is, when I tried to validate schema using,
./vendor/bin/doctrine-module orm:validate-schema
command.
I am getting error,
[Doctrine\DBAL\Schema\SchemaException]
The table with name 'database.opportunitycriteria' already exists.
What should I do?
namespace Administration\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* OpportunityCriteria
*
* #ORM\Table(name="OpportunityCriteria")
* #ORM\Entity
*/
class Criteria
{
/**
* #var integer
* #ORM\Id
* #ORM\Column(name="criteria_id", type="integer", nullable=false)
*/
private $criteria_id;
/**
* #var string
*
* #ORM\Column(name="description", type="string", nullable=false)
*/
private $description;
}
and appropriate getter and setter methods..
I finally figured it out. OP's use case may be different, but in my case, this was because of a misconfigured bidirectional many-to-many relationship.
I had the following entities:
class Cuisine {
/**
* #ManyToMany(targetEntity="Dish")
* #ORM\JoinTable(name="CuisineDish", ...)
*/
protected $dishes;
}
class Dish {
/**
* #ORM\ManyToMany(targetEntity="Cuisine")
* #ORM\JoinTable(name="CuisineDish", ...)
*/
protected $cuisines;
}
What was missing was the inversedBy and mappedBy properties of the #ManyToMany annotations. These are only required when the association is bi-directional.
So now the correctly mapped entities look like:
class Cuisine {
/**
* #ManyToMany(targetEntity="Dish", inversedBy="cuisines")
* #ORM\JoinTable(name="CuisineDish", )
*/
protected $dishes;
}
class Dish {
/**
* #ORM\ManyToMany(targetEntity="Cuisine", mappedBy="dishes")
* #ORM\JoinTable(name="CuisineDish", ...)
*/
protected $cuisines;
}
And orm:validate-schema does not exit with an exception any more.
The exception message is just misleading, as the database is not altered by this operation. Furthermore, this issue is only spotted when validating the sync with the database, not when validating the mapping only (--skip-sync), where it should.
I just reported this bug.
it can cause this error message if you want to use a table name which is already used by one of the installed bundles.

Doctrine 2 Can't Seem to Remove Many to Many Relationships

I have the following setup "Many Users can have Many Projects (Collaborators)"
/**
* #Entity #HasLifeCycleCallbacks
* #Table(name="projects")
*/
class Project implements \Zend_Acl_Resource_Interface {
/**
* #ManyToMany(targetEntity="User", mappedBy="projects")
* #OrderBy({"displayName" = "ASC", "username" = "ASC"})
*/
protected $collaborators;
..
}
/**
* #Entity
* #Table(name="users")
*/
class User implements \Zend_Acl_Role_Interface {
/**
* #ManyToMany(targetEntity="Project", inversedBy="collaborators")
*/
protected $projects;
...
}
I tried to remove a collaborator using the following
$user = Application_DAO_User::findById($this->_getParam('userid'));
$proj = Application_DAO_Project::getProjectById($this->_getParam('id'));
Application_DAO_Project::removeCollaborator($proj, $user); // <---
// Application_DAO_User
public static function findById($id) {
return self::getStaticEm()->find('Application\Models\User', $id);
}
// Application_DAO_Project
public static function getProjectById($id) {
return self::getStaticEm()->find('Application\Models\Project', $id);
}
public static function removeCollaborator(Project $proj, User $collaborator) { // <---
$proj->getCollaborators()->remove($collaborator);
$collaborator->getProjects()->remove($proj);
self::getStaticEm()->flush();
}
And there isn't any errors but the database stays the same ...
This may be well over due but was just experiencing the same problem myself... According to the doctrine 2 documents, the function ArrayCollection->remove($i) is for removing by array index.
What you are after is:
getCollaborators()->removeElement($collaborator);
I went round in circles trying to figure this out until I realised that for this to work:
getCollaborators()->removeElement($collaborator);
$collaborator would have to be the actual object from the collaborators ArrayCollection. That is, if you pass in a new Collaborator object with the same parameters it won't remove it. That's because ArrayCollection uses array_search to look for the object you want to remove.
Hope that saves someone else a few hours...