I'm working with Doctrine 2 as an ORM for Slim 3 but I keep getting stuck in the object mapping section when I try to implement a bidirectional relationship
/**
* Class Resource
* #package App
* #ORM\Entity
* #ORM\Table(name="users", uniqueConstraints={#ORM\UniqueConstraint(name="user_id", columns={"user_id"})}))
*/
class User
{
/**
* #ORM\ManyToOne(targetEntity="UserRoles", inversedBy="users")
* #ORM\JoinColumn(name="role_id", referencedColumnName="user_role_id")
*/
protected $user_role;
}
/**
* Class Resource
* #package App
* #ORM\Entity
* #ORM\Table(name="user_roles", uniqueConstraints={#ORM\UniqueConstraint(name="user_role_id", columns={"user_role_id"})}))
*/
class UserRoles
{
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="user_role")
*/
protected $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
}
I get an exception when I try php vendor/bin/doctrine orm:schema-tool:update --force
The output is:
[Doctrine\Common\Annotations\AnnotationException][Semantical Error] The annotation "#OneToMany" in property App\Entity\UserRoles::$users was never imported. Did you maybe forget to add a "use" statement for this annotation?
Doctrine classes like
Column
Entity
JoinColumn
ManyToMany
ManyToOne
OneToMany
OneToOne
Full list available on Github
are part of the Doctrine\ORM\Mapping namespace.
You should import this namespace with ORM as an alias. Then you should add #ORM in front of these classes as annotation to make them work.
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\ManyToOne(...)
* #ORM\JoinColumn(...)
*/
If you just want to use every single of those classes you have to import each separately.
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\JoinColumn;
/**
* #ManyToOne(...)
* #JoinColumn(...)
*/
Related
How can I define a Doctrine property in a parent class and override the association in a class which extends the parent class? When using annotation, this was implemented by using AssociationOverride, however, I don't think they are available when using PHP 8 attributes
Why I want to:
I have a class AbstractTenantEntity whose purpose is to restrict access to data to a given Tenant (i.e. account, owner, etc) that owns the data, and any entity which extends this class will have tenant_id inserted into the database when created and all other requests will add the tenant_id to the WHERE clause. Tenant typically does not have collections of the various entities which extend AbstractTenantEntity, but a few do. When using annotations, I handled it by applying Doctrine's AssociationOverride annotation to the extended classes which should have a collection in Tenant, but I don't know how to accomplish this when using PHP 8 attributes?
My attempt described below was unsuccessful as I incorrectly thought that the annotation class would magically work with attributes if modified appropriately, but now I see other code must be able to apply the appropriate logic based on the attributes. As such, I abandoned this approach and just made the properties protected and duplicated them in the concrete class.
My attempt:
Tenant entity
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[Entity()]
class Tenant
{
#[Id, Column(type: "integer")]
#[GeneratedValue]
private ?int $id = null;
#[OneToMany(targetEntity: Asset::class, mappedBy: 'tenant')]
private array|Collection|ArrayCollection $assets;
// Other properties and typical getters and setters
}
AbstractTenantEntity entity
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\JoinColumn;
abstract class AbstractTenantEntity implements TenantInterface
{
/**
* inversedBy performed in child where required
*/
#[ManyToOne(targetEntity: Tenant::class)]
#[JoinColumn(nullable: false)]
protected ?Tenant $tenant = null;
// Typical getters and setters
}
This is the part which has me stuck. When using annotation, my code would be as follows:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
* #ORM\AssociationOverrides({
* #ORM\AssociationOverride(name="tenant", inversedBy="assets")
* })
*/
class Asset extends AbstractTenantEntity
{
// Various properties and typical getters and setters
}
But AssociationOverrides hasn't been modified to work with attributes, so based on the official class, I created my own class similar to the others which Doctrine has updated:
namespace App\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\ORM\Mapping\Annotation;
/**
* This annotation is used to override association mapping of property for an entity relationship.
*
* #Annotation
* #NamedArgumentConstructor()
* #Target("ANNOTATION")
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class AssociationOverride implements Annotation
{
/**
* The name of the relationship property whose mapping is being overridden.
*
* #var string
*/
public $name;
/**
* The join column that is being mapped to the persistent attribute.
*
* #var array<\Doctrine\ORM\Mapping\JoinColumn>
*/
public $joinColumns;
/**
* The join table that maps the relationship.
*
* #var \Doctrine\ORM\Mapping\JoinTable
*/
public $joinTable;
/**
* The name of the association-field on the inverse-side.
*
* #var string
*/
public $inversedBy;
/**
* The fetching strategy to use for the association.
*
* #var string
* #Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
*/
public $fetch;
public function __construct(
?string $name = null,
?array $joinColumns = null,
?string $joinTable = null,
?string $inversedBy = null,
?string $fetch = null
) {
$this->name = $name;
$this->joinColumns = $joinColumns;
$this->joinTable = $joinTable;
$this->inversedBy = $inversedBy;
$this->fetch = $fetch;
//$this->debug('__construct',);
}
private function debug(string $message, string $file='test.json', ?int $options = null)
{
$content = file_exists($file)?json_decode(file_get_contents($file), true):[];
$content[] = ['message'=>$message, 'object_vars'=>get_object_vars($this), 'debug_backtrace'=>debug_backtrace($options)];
file_put_contents($file, json_encode($content, JSON_PRETTY_PRINT));
}
}
When validating the mapping, Doctrine complains that target-entity does not contain the required inversedBy. I've spent some time going through the Doctrine source code but have not made much progress.
Does my current approach have merit and if so please fill in the gaps. If not, however, how would you recommend meeting this need?
It has been resolved by this pr: https://github.com/doctrine/orm/pull/9241
ps: PHP 8.1 is required
#[AttributeOverrides([
new AttributeOverride(
name: "id",
column: new Column(name: "guest_id", type: "integer", length: 140)
),
new AttributeOverride(
name: "name",
column: new Column(name: "guest_name", nullable: false, unique: true, length: 240)
)]
)]
Override Field Association Mappings In Subclasses
Sometimes there is a need to persist entities but override all or part of the mapping metadata. Sometimes also the mapping to override comes from entities using traits where the traits have mapping metadata. This tutorial explains how to override mapping metadata, i.e. attributes and associations metadata in particular. The example here shows the overriding of a class that uses a trait but is similar when extending a base class as shown at the end of this tutorial.
Suppose we have a class ExampleEntityWithOverride. This class uses trait ExampleTrait:
<?php
/**
* #Entity
*
* #AttributeOverrides({
* #AttributeOverride(name="foo",
* column=#Column(
* name = "foo_overridden",
* type = "integer",
* length = 140,
* nullable = false,
* unique = false
* )
* )
* })
*
* #AssociationOverrides({
* #AssociationOverride(name="bar",
* joinColumns=#JoinColumn(
* name="example_entity_overridden_bar_id", referencedColumnName="id"
* )
* )
* })
*/
class ExampleEntityWithOverride
{
use ExampleTrait;
}
/**
* #Entity
*/
class Bar
{
/** #Id #Column(type="string") */
private $id;
}
The docblock is showing metadata override of the attribute and association type. It basically changes the names of the columns mapped for a property foo and for the association bar which relates to Bar class shown above. Here is the trait which has mapping metadata that is overridden by the annotation above:
<?php
/**
* Trait class
*/
trait ExampleTrait
{
/** #Id #Column(type="string") */
private $id;
/**
* #Column(name="trait_foo", type="integer", length=100, nullable=true, unique=true)
*/
protected $foo;
/**
* #OneToOne(targetEntity="Bar", cascade={"persist", "merge"})
* #JoinColumn(name="example_trait_bar_id", referencedColumnName="id")
*/
protected $bar;
}
The case for just extending a class would be just the same but:
<?php
class ExampleEntityWithOverride extends BaseEntityWithSomeMapping
{
// ...
}
Overriding is also supported via XML and YAML (examples).
i use doctrine ORM for generate a association one-to-many bidirectional, but when i execute the orm:validation-scheme command, this shows me the mesaje below:
"C:\xampp\htdocs\Gestor\vendor\bin>doctrine-module orm:validate-schema
[Mapping] FAIL - The entity-class 'Empleados\Entity\TipoDocumento' mapping is i
nvalid:
* The association Empleados\Entity\TipoDocumento#empleados refers to the owning
side field Empleados\Entity\Empleado#tipodocumento which does not exist.
[Database] FAIL - The database schema is not in sync with the current mapping fi
le."
The code:
Empleado class (many to one side)
<?php
namespace Empleados\Entity;
use Doctrine\Common\Collections\ArrayCollection as Collection;
use Empresas\Entity\Empresa;
use Empleados\Entity\TipoDocumento;
use Doctrine\ORM\Mapping as ORM;
use Documentos\Entity\Documentacion_Empleado;
/**
* #ORM\Entity
* #ORM\Table(name="empleado")
*
*/
class Empleado
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string",length=30,nullable=false,unique=true)
*/
private $nro_documento;
/*
* #ORM\ManyToOne(targetEntity="Empleados\Entity\TipoDocumento",inversedBy="empleados")
* #ORM\JoinColumn(name="tipodocumento_id", referencedColumnName="id")
*/
private $tipodocumento;
//...
}
The TipoDocumento class (one to many side):
<?php
// yes, the class are in the same namespace "Empleados"
namespace Empleados\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Empleados\Entity\Empleado;
/**
* #ORM\Entity
* #ORM\Table(name="tipo_documento")
*/
class TipoDocumento
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Empleados\Entity\Empleado", mappedBy="tipodocumento"))
*/
private $empleados;
//.....
public function __construct()
{
$this->empleados = new ArrayCollection();
}
}
I'm based on the Doctrine documentation example in http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
In class TipoDocumento, private $empleados; should be private $empleado;.
Edit
Sorry that's right, I was looking at the wrong place in the documentation.
The Many-to-one has the plural there. It also contains something like:
public function __construct() {
$this->empleados = new ArrayCollection();
}
I can't tell if your class contains this function.
jmarkmurphy thanks for your help.
The problem was in the camelcase of the "TipoDocumento" class, for some reason Doctrine does not like the camelcase ... What I did was rename the class to Tipo_documento and with that change everything started to work fine.
Is it possible to override #ManyToOne(targetEntity)?
I read this Doctrine documentation page, but it doesn't mention how to override targetEntity.
Here's my code:
namespace AppBundle\Model\Order\Entity;
use AppBundle\Model\Base\Entity\Identifier;
use AppBundle\Model\Base\Entity\Product;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\AttributeOverrides;
use Doctrine\ORM\Mapping\AttributeOverride;
/**
* Class OrderItem
*
*
* #ORM\Entity
* #ORM\Table(name="sylius_order_item")
* #ORM\AssociationOverrides({
* #ORM\AssociationOverride(
* name="variant",
* joinColumns=#ORM\JoinColumn(
* name="variant", referencedColumnName="id", nullable=true
* )
* )
* })
*/
class OrderItem extends \Sylius\Component\Core\Model\OrderItem
{
/**
* #var
* #ORM\ManyToOne(targetEntity="AppBundle\Model\Base\Entity\Product")
*/
protected $product;
/**
* #return mixed
*/
public function getProduct()
{
return $this->product;
}
/**
* #param mixed $product
*/
public function setProduct($product)
{
$this->product = $product;
}
}
I was able to to override the definition for the "variant" column and set this column to null, but I can't figure it out how to change the targetEntity.
As described in the doc, you cannot change the type of your association:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#association-override
BUT, you can define a targetEntity as an interface (that's the default sylius conf it seems),
targetEntity="AppBundle\Entity\ProductInterface"
Extends the original one in your Interface file
namespace AppBundle\Entity;
use Sylius\Component\Core\Model\ProductInterface as BaseProductInterface;
interface ProductInterface extends BaseProductInterface {}
And add the mapping in your configuration
doctrine:
orm:
resolve_target_entities:
AppBundle\Entity\ProductInterface: AppBundle\Entity\Product
It's described here: http://symfony.com/doc/current/doctrine/resolve_target_entity.html
Hope it helps
I didn't find a way to override the targetEntity value of an association when using annotations, but it is possible when using PHP mapping (php or staticphp in Symfony).
https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/php-mapping.html
We can create a function:
function updateAssociationTargetEntity(ClassMetadata $metadata, $association, $targetEntity)
{
if (!isset($metadata->associationMappings[$association])) {
throw new \LogicException("Association $association not defined on $metadata->name");
}
$metadata->associationMappings[$association]['targetEntity'] = $targetEntity;
}
and use it like this:
updateAssociationTargetEntity($metadata, 'product', AppBundle\Model\Base\Entity\Product::class);
That way, when Doctrine loads the metadata for the class AppBundle\Model\Order\Entity\OrderItem, it first loads the parent (\Sylius\Component\Core\Model\OrderItem) metadata, and then it loads the PHP mapping for the child class where we override the association mapping that was set when loading the parent class metadata.
For my project, I'm trying to use the inheritance feature of Doctrine. I need to represent medias (through different tables : one table for uploaded documents, one for linked videos, ... and so on).
But, the videos can vary from provider to provider (such as Youtube, Dailymotion, you name it). So, I was thinking of doing another inheritance, proper to the Video table, through a SINGLE_TABLE inheritance.
But, when I declare my entities, it seems that if I add the SINGLE_TABLE inheritance annotation on the AbstractVideo entity, which extends the AbstractMedia Entity, the Video table is never created (nor detected). Here is a snippet of these two entities :
<?php
namespace Acme\Demo\Entity;
use Datetime;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="Media")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
*/
abstract class AbstractMedia
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// some other fields
}
/**
* #ORM\Entity
* #ORM\Table(name="Video")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="provider", type="string")
* #ORM\DiscriminatorMap({})
*/
abstract class AbstractVideo extends AbstractMedia
{
/** #ORM\Column(type="string") */
private $name;
// some other fields
}
I already tried to have a mapped entity to a Foo entity, extending the AbstractVideo, but then when I try to persist something, it says that it is not a valid entity.
Any ideas, or should I really avoid such deep inheritance ? Thanks
Not really sure if this is exactly what you need, but this is from a production code I use.
We inherit the file, with other entities, and those are also inherited.
The important part is to add the inheriting(extending) entities to disciminator map.
/**
* File
*
* #ORM\Table(name = "file")
* #ORM\Entity(repositoryClass="Living\ApiBundle\Entity\File\FileRepository")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string", length=64)
* #ORM\DiscriminatorMap({
* "file" = "Something\Entity\File\File",
* "image" = "Something\Entity\Image\Image",
* "specialImage" = "Something\Entity\Image\SpecialImage",
* })
*/
class File implements FileEntityInterface
.....
/**
* ImageFile
*
* #ORM\Table(name="image")
* #ORM\Entity(repositoryClass="Living\ApiBundle\Entity\Image\ImageRepository")
*/
class Image extends File implements ImageEntityInterface
As #OCramius said in a comment to my question, this is not supported by Doctrine ORM. So to do what I wanted to do, I will store a value object in the data property of my object, storing the property of "child classes" instead of having deep different kind of inheritance.
<?php
class Video extends AbstractMedia
{
// returns the value object youtube, dailymotion, ... etc
public function getData();
}
class Youtube
{
//public function ...
}
class Dailymotion
{
// public funciton ...
}
Im working with Symfony 2.4 im getting this exception when i came to run the code
doctrine:generate:entities
Class "Entity\ClientClass" is not a valid entity or mapped super class
How to resolve it.
Here is the code in Entity
namespace category\CategoryBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* category
*/
class category
{
/**
* #var integer
*/
private $id;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
We can't help you without details :
- check the namespace of the Entity/ClientClass and the file path according to doctrine configuration, by default : src/BundleName/Entity
does your class have the annotation #ORM\Entity or #ORM\MappedSuperClass ?