JoinColumns/Composite keys with php attributes - php

How do you declare joinColumns/composite keys with PHP attributes. Haven't been able to find the right way and it is not documented (https://www.doctrine-project.org/projects/doctrine-orm/en/2.11/reference/attributes-reference.html)
Entities
Comment.php
#[ORM\Entity(repositoryClass: CommentRepository::class)]
class Comment
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\ManyToOne(targetEntity: PullRequest::class, inversedBy: 'comments')]
#[ORM\JoinColumn(name: 'pull_request_id', referencedColumnName: 'id')]
#[ORM\JoinColumn(name: 'repo_id', referencedColumnName: 'repo_id')]
private $pullRequest;
}
PullRequest.php
#[ORM\Entity(repositoryClass: PullRequestRepository::class)]
class PullRequest
{
#[ORM\Id]
#[ORM\Column(type: 'integer', unique: false)]
private $id;
#[ORM\Id]
#[ORM\ManyToOne(targetEntity: Repo::class, inversedBy: 'pullRequests')]
#[ORM\JoinColumn(nullable: false)]
private $repo;
#[ORM\OneToMany(mappedBy: 'pullRequest', targetEntity: Comment::class, orphanRemoval: true)]
private $comments;
}

I ran into the same issue today and managed to find a workaround. It indeed seems like JoinColumns is not available as a PHP8 attribute, at least not at Doctrine ORM 2.11 nor the upcoming 2.12 / 3.0.
However, you can work around this by moving the join columns definitions into an AssociationsOverride attribute at the class level as follows:
#[ORM\Entity(repositoryClass: CommentRepository::class)]
#[ORM\AssociationOverrides([
new ORM\AssociationOverride(
name: 'pullRequest',
joinColumns: [
new ORM\JoinColumn(name: 'pull_request_id', referencedColumnName: 'id'),
new ORM\JoinColumn(name: 'repo_id', referencedColumnName: 'repo_id')
]
)
])]
class Comment
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\ManyToOne(targetEntity: PullRequest::class, inversedBy: 'comments')]
private $pullRequest;
}

According to https://github.com/greg0ire/doctrine-orm/commit/18366db5789b03e1d8a34933fbbff97a768a9cfe the "JoinColumns" attribute is no longer required, multiple "JoinColumn" attributes are sufficient.

Related

Create or update unique method Doctrine

I have the following entity:
class DoctrinePartnerDao extends DoctrineBaseDao
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
public int $id;
#[ORM\Column(type: 'uuid', unique: true, nullable: false)]
public Uuid $guid;
#[ORM\Column(type: 'string', length: 255, nullable: false)]
public string $name;
#[
ORM\ManyToOne(
targetEntity: DoctrineCountryDao::class,
cascade: ['persist']
)
]
public DoctrineCountryDao $country;
#[ORM\ManyToOne(targetEntity: DoctrineImageDao::class)]
#[ORM\JoinColumn(name: 'banner_image_id', nullable: true)]
public ?DoctrineImageDao $bannerImage = null;
#[ORM\ManyToOne(targetEntity: DoctrineImageDao::class)]
#[ORM\JoinColumn(name: 'avatar_image_id', nullable: true)]
public ?DoctrineImageDao $avatarImage = null;
#[ORM\Column(type: 'datetime', nullable: true)]
public ?DateTimeInterface $deletedAt;
And the save method:
public function save(Partner $partner): Partner
{
/** #var DoctrinePartnerDao */
$dao = $this->mapper->toDao($partner);
$countryId = $partner->getCountryId();
$countryReference = $this->getEntityManager()->getReference(
DoctrineCountryDao::class,
$countryId
);
if (!$countryReference) {
throw new InvalidAggregateIdException(
"Country with id '$countryId' does not exist."
);
}
$bannerImageReference = $partner->getBannerImageId()
? $this->getEntityManager()->getReference(
DoctrineImageDao::class,
$partner->getBannerImageId()
)
: null;
$avatarImageReference = $partner->getAvatarImageId()
? $this->getEntityManager()->getReference(
DoctrineImageDao::class,
$partner->getAvatarImageId()
)
: null;
$dao->country = $countryReference;
$dao->bannerImage = $bannerImageReference;
$dao->avatarImage = $avatarImageReference;
try {
$this->getEntityManager()
->persist($dao)
->flush();
} catch (Exception $e) {
dd($e);
}
return $partner;
}
Expected behavior: create the entity in db if it doesn't exist and if it exists update it. I am trying to avoid finding it first.
Before it was a 'merge' method but it seems it is deprecated now.
Getting the following error:
#message: "An exception occurred while executing a query: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'i\x03\xE4\xED\x18\xAE8\x18\xB6%\xD1\x18\xD7D\xE6\xBA' for key 'partner.UNIQ_312B3E162B6FCFB2'"

Should non-nullable entity properties be nullable type in PHP?

Using Symfony 6.1, I have this Entity:
#[ORM\Entity(repositoryClass: InvoiceRepository::class)]
class Invoice implements SearchableEntityInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(type: 'datetime')]
private DateTimeInterface $timeCreated;
#[ORM\Column(type: 'datetime', nullable: true)]
private ?DateTimeInterface $timePaid = null;
// ...
}
The only nullable field in DB is timePaid, the rest is required to be NOT NULL in DB.
My code reflects "nullability" of DB in PHP code, i.e. if the field can be NULL in DB, then its nullable in PHP, but never otherwise.
However when using the make:entity or checking online examples you always see
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id;
#[ORM\Column(type: 'datetime')]
private ?DateTimeInterface $timeCreated;
i.e. nullable PHP properties even when it CANNOT be null ever.
Is it correct to declare ID field and non-nullable fields as non-nullable in PHP? Can there be any issues from this approach?

Symfony error message (An exception occurred while executing a query: SQLSTATE[23000])

I'm trying to create a blog with symfony and I'm new in this field, I can’t solve the error that appears to me and I don’t understand where it comes from:
An exception occurred while executing a query:
SQLSTATE\[23000\]: Integrity constraint violation: 19 NOT NULL constraint failed: articles.users_id
Articles.php
#[ORM\Entity(repositoryClass: ArticlesRepository::class)]
class Articles implements TimestampedInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string', length: 255)]
private $titre;
#[ORM\Column(type: 'string', length: 255)]
#[Groups('article')]
private $slug;
#[ORM\Column(type: 'text')]
private $contenu;
#[ORM\Column(type: 'datetime')]
#[Groups('article')]
private $created_at;
#[ORM\Column(type: 'datetime', nullable: true)]
private $updated_at;
#[ORM\ManyToOne(targetEntity: Users::class, inversedBy: 'articles')]
#[ORM\JoinColumn(nullable: false)]
private $users;
#[ORM\OneToMany(mappedBy: 'articles', targetEntity: Commentaires::class, orphanRemoval: true)]
private $commentaires;
#[ORM\ManyToMany(targetEntity: MotsCles::class, inversedBy: 'articles')]
private $mots_cles;
#[ORM\ManyToMany(targetEntity: Categories::class, inversedBy: 'articles')]
private $categories;
public function __construct()
{
$this->commentaires = new ArrayCollection();
$this->mots_cles = new ArrayCollection();
$this->categories = new ArrayCollection();
}
public function getId() : ?int
{
return $this->id;
}
}
ArticlesCrudController.php
class ArticlesCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Articles::class;
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id');
yield TextField::new('titre');
yield TextEditorField::new('contenu');
yield DateTimeField::new('created_at')->hideOnForm();
yield DateTimeField::new('updated_at')->hideOnForm();
}
}
DashBoardController.php
class DashboardController extends AbstractDashboardController
{ public function __construct(private AdminUrlGenerator $adminUrlGenerator){
}
#[Route('/admin', name: 'admin')]
public function index(): Response
{
$url = $this->adminUrlGenerator->setController(ArticlesCrudController::class)->generateUrl();
return $this->redirect($url);
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('BLOG DE HIBA');
}
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', '');
yield MenuItem::subMenu('Articles', '')->setSubItems([
MenuItem::linkToCrud('Tous les articles' , 'fas fa-book',Articles::class),
MenuItem::linkToCrud('Ajouter' , 'fas fa-plus',Articles::class)->setAction(Crud::PAGE_NEW),
MenuItem::linkToCrud('Categories' , 'fas fa-tag',Categories::class),
MenuItem::linkToCrud('MotsCles' , 'fas fa-tag',MotsCles::class)
]);
yield MenuItem::subMenu('Utilisateurs', '')->setSubItems([
MenuItem::linkToCrud('Utilisateurs' , 'fas fa-user',Users::class),
]);
}
}
Please I need Help, any suggestion would be great!!!
An exception occurred while executing a query:
SQLSTATE\[23000\]: Integrity constraint violation: 19 NOT NULL constraint failed: articles.users_id

Use a BooleanFilter in a AssociationField in EaseyAdmin

My entity Society has a relation in OneToMany with Client.
Class Society :
/** #var Collection<int, Client> */
#[ORM\OneToMany(mappedBy: 'society', targetEntity: Client::class, cascade: ['persist', 'remove'])]
#[Assert\Valid]
private Collection $clients;
class Client:
#[ORM\ManyToOne(targetEntity: Society::class, cascade: ['persist', 'remove'], inversedBy: 'clients')]
#[Assert\Valid]
private ?Society $society = null;
In Client's class, I've got a boolean property isSigned. and I would like to put a BooleanFilter on it. If I use EntityFilter, I can only filter by the name of Client and If I try to do BooleanFilter('client.IsSigned') I've got an error.
Any idea to add a booleanFilter on my Association ?

Add attribute to the association class ManyToMany

I use Symfony and Doctrine for mapping database , I have 2 entities "Product" and "Operation" with ManyToMany relation so I get an other table in DataBase called "Products-Operations"
Now I like to add an attribute for this table , how can I do it?
Produit.orm.yml
manyToMany:
operations:
targetEntity: Operation
mappedBy: produits
Operation.orm.yml
manyToMany:
produits:
targetEntity: Produit
inversedBy: operations
joinTable:
name: produits_operations
joinColumns:
operation_id:
referencedColumnName: id
inverseJoinColumns:
produit_id:
referencedColumnName: id
I work with annotation to create table in data base I think same orm.yml and hope this solution is work...
//inverse side
class Product
{
/**
* #ORM\OneToMany(targetEntity="ProductOperation", mappedBy="product", cascade={"persist", "remove"})
*/
private $productOperations;
}
//owing side
class ProductOperation
{
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="productOperations")
*/
private $product;
/**
* #ORM\ManyToOne(targetEntity="Operation", inversedBy="productOperations")
*/
private $operation;
}
//inverse side
class Operation
{
/**
* #ORM\OneToMany(targetEntity="ProductOperation", mappedBy="operation", cascade={"persist", "remove"})
*/
private $productOperations;
}

Categories