I'm trying to display orders and order items in view like below but the result is an empty collection.
Result from Dump(order.getItems)
Controller:
<?php
namespace App\Controller;
use App\Repository\OrderRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class MyController extends AbstractController
{
#[Route('/all', name: 'app_index')]
public function index(OrderRepository $orderRepository): Response
{
return $this->render('/index.html.twig', [
'orders' => $orderRepository->findAll()
]);
}
}
Twig:
{% for order in orders %}
{{ dump(order.getItems()) }}
{% endfor %}
I have class Order mapped by orderRef in OrderItem class. The mapping is inversed by the "items" collection in Order class.
Order entity:
<?php
namespace App\Entity;
use App\Repository\OrderRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: OrderRepository::class)]
#[ORM\Table(name: '`order`')]
class Order
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\OneToMany(mappedBy: 'orderRef', targetEntity: OrderItem::class, orphanRemoval: true)]
private Collection $items;
public function __construct()
{
$this->items = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
/**
* #return Collection<int, OrderItem>
*/
public function getItems(): Collection
{
return $this->items;
}
}
OrderItem entity:
<?php
namespace App\Entity;
use App\Repository\OrderItemRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: OrderItemRepository::class)]
class OrderItem
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'items')]
#[ORM\JoinColumn(nullable: false)]
private ?Order $orderRef = null;
public function getId(): ?int
{
return $this->id;
}
public function getOrderRef(): ?Order
{
return $this->orderRef;
}
public function setOrderRef(?Order $orderRef): self
{
$this->orderRef = $orderRef;
return $this;
}
}
Instead of a findAll() you can call a new query builder in your repository with an orderBy
public function getSortedOrders()
{
$qb = $this->createQueryBuilder('o')
->orderBy('o.orderRef', 'DESC');
return $qb->getQuery()->getResult();
}```
Related
I'm new to symfony, I work on an e-commerce website. I solved some bugs already trying different solutions but with this one I feel stuck. I'm trying to display my articles by categories but it's not working because it displays an error:
Warning: Trying to access array offset on value of type null
I made a consoles controller in symfony to call the view associated to display only articles with that category, expected to do the same with the games after but it doesn't seem to find the category associated with the articles ?
This is my consolescontroller:
namespace App\Controller;
use App\Repository\ArticlesRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ConsolesController extends AbstractController
{
#[Route('/consoles', name: 'app_consoles')]
public function index(ArticlesRepository $repositoryArticles): Response
{
$consoles = $repositoryArticles->findBy(['category' => 1]);
return $this->render('consoles/index.html.twig', [
'controller_name' => 'ConsolesController',
'consoles' => $consoles,
]);
}
}
My Entity Articles
<?php
namespace App\Entity;
use App\Repository\ArticlesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ArticlesRepository::class)]
class Articles
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 80)]
private ?string $nomArticle = null;
#[ORM\Column(length: 255)]
private ?string $imageArticle = null;
#[ORM\Column]
private ?float $prixArticle = null;
#[ORM\Column(length: 200)]
private ?string $descriptionArticle = null;
#[ORM\ManyToMany(targetEntity: Categories::class, inversedBy: 'articles')]
#[ORM\JoinColumn(nullable: false)]
private Collection $category;
public function __construct()
{
$this->category = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNomArticle(): ?string
{
return $this->nomArticle;
}
public function setNomArticle(string $nomArticle): self
{
$this->nomArticle = $nomArticle;
return $this;
}
public function getImageArticle(): ?string
{
return $this->imageArticle;
}
public function setImageArticle(string $imageArticle): self
{
$this->imageArticle = $imageArticle;
return $this;
}
public function getPrixArticle(): ?float
{
return $this->prixArticle;
}
public function setPrixArticle(float $prixArticle): self
{
$this->prixArticle = $prixArticle;
return $this;
}
public function getDescriptionArticle(): ?string
{
return $this->descriptionArticle;
}
public function setDescriptionArticle(string $descriptionArticle): self
{
$this->descriptionArticle = $descriptionArticle;
return $this;
}
/**
* #return Collection<int, Categories>
*/
public function getCategory(): Collection
{
return $this->category;
}
public function addCategory(Categories $category): self
{
if (!$this->category->contains($category)) {
$this->category->add($category);
}
return $this;
}
public function removeCategory(Categories $category): self
{
$this->category->removeElement($category);
return $this;
}
public function __toString()
{
return $this->nomArticle;
}
}
And my Entity Categories
<?php
namespace App\Entity;
use App\Repository\CategoriesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CategoriesRepository::class)]
class Categories
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 50)]
private ?string $nomCategorie = null;
#[ORM\ManyToMany(targetEntity: Articles::class, mappedBy: 'category')]
private Collection $articles;
public function __construct()
{
$this->articles = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNomCategorie(): ?string
{
return $this->nomCategorie;
}
public function setNomCategorie(string $nomCategorie): self
{
$this->nomCategorie = $nomCategorie;
return $this;
}
/**
* #return Collection<int, Articles>
*/
public function getArticles(): Collection
{
return $this->articles;
}
public function addArticle(Articles $article): self
{
if (!$this->articles->contains($article)) {
$this->articles->add($article);
$article->addCategory($this);
}
return $this;
}
public function removeArticle(Articles $article): self
{
if ($this->articles->removeElement($article)) {
$article->removeCategory($this);
}
return $this;
}
public function __toString()
{
return $this->nomCategorie;
}
}
They're both linked by a "many to many" relationship and they're both supposed to have access to the other but there still something not working.
Thank you in advance for your help.
I am currently using EasyAdmin 4 with Symfony to create a backend admin page to manage users.
I did all the step that was in the documentation but yet it still giving me an error:
"Unable to find the controller related to the "App\Controller\Admin\User" Entity; did you forget to extend "EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController"?"
Here is the code in DashboardController:
<?php
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DashboardController extends AbstractDashboardController
{
#[Route('/admin', name: 'admin')]
public function index(): Response
{
return $this->render('admin/dashboard.html.twig');
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('Symfony Melody')
->renderContentMaximized();
}
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
yield MenuItem::linkToCrud('User', 'fas fa-user', User::class);
}
}
Here is the code in UserCrudController:
<?php
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud;
}
/*
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id'),
TextField::new('title'),
TextEditorField::new('description'),
];
}
*/
}
Here is the code in the User class:
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column]
private array $roles = [];
/**
* #var string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(length: 255)]
private ?string $nom = null;
#[ORM\Column(length: 255)]
private ?string $prenom = null;
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getPrenom(): ?string
{
return $this->prenom;
}
public function setPrenom(string $prenom): self
{
$this->prenom = $prenom;
return $this;
}
}
I tried reading the documentation again but i can't find any answers.
Can someone help please? it would be great, thank you.
You are Missing the use of App\Entity\User
In your DashboardController try to add this line :
use App\Entity\User;
On the top of your class file
Happy coding 🙂
I'm using Symfony + api-platform in their latest versions as of today.
I've got a User Entity and a Team entity which are related through a ManyToMany ORM relation.
A User can have several Teams a Team can have several Users, the Team "owns" the relation.
Once my user is logged in through a JWT Token, I would like the endpoint GET /teams to only send back the Teams in which the identified User is part of.
Here is my User entity :
<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\GetCollection;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ApiResource(operations: [
new Get(),
new GetCollection()
])]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column]
private array $roles = [];
/**
* #var string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
/** #var book[] Available reviews for this book. */
#[ORM\OneToMany(targetEntity: Book::class, mappedBy: 'user', cascade: ['persist', 'remove'])]
public iterable $books;
#[ORM\ManyToMany(targetEntity: Team::class, inversedBy: 'users')]
private Collection $teams;
public function __construct()
{
$this->teams = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
/**
* Méthode getUsername qui permet de retourner le champ qui est utilisé pour l'authentification.
*
* #return string
*/
public function getUsername(): string {
return $this->getUserIdentifier();
}
/**
* #return Collection<int, Team>
*/
public function getTeams(): Collection
{
return $this->teams;
}
public function addTeam(Team $team): self
{
if (!$this->teams->contains($team)) {
$this->teams->add($team);
}
return $this;
}
public function removeTeam(Team $team): self
{
$this->teams->removeElement($team);
return $this;
}
}
Here is my Team Entity :
<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use App\Repository\TeamRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use App\Entity\User;
#[ORM\Entity(repositoryClass: TeamRepository::class)]
#[ApiResource(security: "is_granted('ROLE_USER')")]
class Team
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 100)]
private ?string $name = null;
#[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'teams')]
private Collection $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* #return Collection<int, User>
*/
public function getUsers(): Collection
{
return $this->users;
}
public function addUser(User $user): self
{
if (!$this->users->contains($user)) {
$this->users->add($user);
$user->addTeam($this);
}
return $this;
}
public function removeUser(User $user): self
{
if ($this->users->removeElement($user)) {
$user->removeTeam($this);
}
return $this;
}
}
Here is my CurrentUserExtension Class that filters result based on the current user :
<?php
// api/src/Doctrine/CurrentUserExtension.php
namespace App\Doctrine;
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use App\Entity\Book;
use App\Entity\Team;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;
final class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
{
$this->addWhere($queryBuilder, $resourceClass);
}
public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void
{
$this->addWhere($queryBuilder, $resourceClass);
}
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
{
if ((Book::class !== $resourceClass && Team::class !== $resourceClass) || $this->security->isGranted('ROLE_ADMIN') || null === $user = $this->security->getUser()) {
return;
}
$rootAlias = $queryBuilder->getRootAliases()[0];
if (Team::class == $resourceClass){
$queryBuilder->andWhere(sprintf('%s.users = :current_user', $rootAlias));
//trigger_error($queryBuilder);
}
$queryBuilder->setParameter('current_user', $user->getId());
}
}
Obviously it doesn't work because of the nature of the relation existing between the two tables. I also tried to use the query builder to use leftjoin and join the user_team table. But since the user_team table is not an Entity it failed.
Here is an SQL equivalent of what I would like to get as a result :
select * from team t
left join user_team ut on ut.team_id = t.id
where user_id = :current_user
You should be able to just check if your user is IN or MEMBER OF one of your team $users.
Your query was close, try updating it with:
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
{
if ((Book::class !== $resourceClass && Team::class !== $resourceClass) || $this->security->isGranted('ROLE_ADMIN') || null === $user = $this->security->getUser()) {
return;
}
$rootAlias = $queryBuilder->getRootAliases()[0];
if (Team::class == $resourceClass){
$queryBuilder->andWhere(sprintf(':current_user MEMBER OF %s.users', $rootAlias));
$queryBuilder->setParameter('current_user', $user);
}
}
You could either use MEMBER OF or IN but in a manyToMany it's usually easier to use MEMBER OF.
I am adding fixtures using theofidry/AliceBundle which in turn uses nelmio/alice.
One of my entities is Vendor and another is VendorUser and VendorUser holds the PK of Vendor and thus Vendor must be committed before VendorUser. Even though I don't think it matters, I took steps to ensure they are persisted in the correct order and have verified that Vendor was persisted before VendorUser. But then when flushed, I get a not-null constraint error stating the vendor is not set in vendorUser.
I look into it and see that UnitOfWork::commit() calls UnitOfWork::getCommitOrder() which also shows that VendorUser is scheduled to be committed before Vendor. Going down the rabbit hole, I see that UnitOfWork::getCommitOrder() calls UnitOfWork::getCommitOrderCalculator() which returns a new CommitOrderCalculator, which has the following comment in its source code:
CommitOrderCalculator implements topological sorting, which is an
ordering algorithm for directed graphs (DG) and/or directed acyclic
graphs (DAG) by using a depth-first searching (DFS) to traverse the
graph built in memory. This algorithm have a linear running time
based on nodes (V) and dependency between the nodes (E), resulting in
a computational complexity of O(V + E).
I've tried to figure out how this class works but am not making much progress. Are there any tools to analyze what it is doing? How does Doctrine determine the order to commit records? How can I gleam information about why it is doing it the way it is so I may troubleshoot? I expect that Vendor has some not-null property which isn't yet set, however, for the life of me I can't figure it out.
EDIT It appears to either be a Doctrine bug or probably more likely my schema is not supported. I believe the cause is two entities have references to each other (users have an organization, all entities except for Tenant must belong to a Tenant, and all entities have a createBy and updateBy blameable field) and Doctrine doesn't know which one to do first. There is a correct order, but it is based on business logic and not the database schema, so Doctrine alone can't figure it out. Is there a way to instruct Doctrine the order to commit? Below is a test script which manually creates the entities without using fixtures or blamable. As seen by the output from UnitOfWork's commit order, it will clearly fail as concrete classes are committed before their inherited parents. Any recommendations?
private function testSomething()
{
$manager = $this->managerRegistry->getManager();
$rootUser = $manager->getRepository(TestTenantUser::class)->find(new NilUlid);
$tenant = new TestTenant;
$vendor = new TestVendor;
$tenant->addVendor($vendor);
$tenantUser = new TestTenantUser;
$vendorUser = new TestVendorUser;
$tenant->addUser($tenantUser);
$vendor->addUser($vendorUser);
// Simulating being set by blamable listner.
$tenant->setCreateBy($rootUser)->setUpdateBy($rootUser);
$vendor->setCreateBy($rootUser)->setUpdateBy($rootUser);
$tenantUser->setCreateBy($rootUser)->setUpdateBy($rootUser);
$vendorUser->setCreateBy($rootUser)->setUpdateBy($rootUser);
// Technically only need to persist tenant since others will cascade persist, but just to be double sure.
$manager->persist($tenant);
$manager->persist($vendor);
$manager->persist($tenantUser);
$manager->persist($vendorUser);
printf('%-30s with ID %s has organization %s'.PHP_EOL, get_class($tenantUser), $tenantUser->getId()->toRfc4122(), $tenantUser->getOrganization()->getId()->toRfc4122());
printf('%-30s with ID %s has organization %s'.PHP_EOL, get_class($vendorUser), $vendorUser->getId()->toRfc4122(), $vendorUser->getOrganization()->getId()->toRfc4122());
try {
$manager->flush();
}
catch(\Exception $e) {
exit($e->getMessage());
}
}
class UnitOfWork implements PropertyChangedListener
{
public function commit($entity = null)
{
...
$commitOrder = $this->getCommitOrder();
echo(PHP_EOL.'UnitOfWork::commit() $this->getCommitOrder(): '.PHP_EOL);
foreach($commitOrder as $class){
echo($class->getName().PHP_EOL);
}
...
}
}
App\Entity\Test\TestTenantUser with ID 0182f48a-5778-0cb2-d24d-7840c74773b1 has organization 0182f48a-5778-0cb2-d24d-7840c74773b0
App\Entity\Test\TestVendorUser with ID 0182f48a-5779-dc20-c1d4-9ec596c1ee6d has organization 0182f48a-5778-0cb2-d24d-7840c74773b2
UnitOfWork::commit() $this->getCommitOrder():
App\Entity\Test\TestTenant
App\Entity\Test\TestVendorUser
App\Entity\Test\TestAbstractUser
App\Entity\Test\TestAbstractOrganization
App\Entity\Test\TestTenantUser
App\Entity\Test\TestVendor
An exception occurred while executing a query: SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column "organization_id" of relation "test_abstract_user" violates not-null constraint
DETAIL: Failing row contains (0182f48a-5779-dc20-c1d4-9ec596c1ee6d, null, 00000000-0000-0000-0000-000000000000, 00000000-0000-0000-0000-000000000000, 2022-08-31 08:34:43, 2022-08-31 08:34:43, vendor).
<?php
declare(strict_types=1);
namespace App\Entity\Test;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class TestTenant extends TestAbstractOrganization
{
#[ORM\OneToMany(targetEntity: TestVendor::class, mappedBy: 'tenant', cascade: ['persist', 'remove'])]
private Collection $vendors;
public function __construct()
{
parent::__construct();
$this->vendors = new ArrayCollection();
}
public function addUser(TestAbstractUser $user): self
{
$user->setTenant($this);
return parent::addUser($user);
}
public function getVendors(): Collection
{
return $this->vendors;
}
public function addVendor(TestVendor $vendor): self
{
if (!$this->vendors->contains($vendor)) {
$this->vendors[] = $vendor;
$vendor->setTenant($this);
}
return $this;
}
public function removeVendor(TestVendor $vendor): self
{
if (!$this->vendors->removeElement($vendor)) {
return $this;
}
if ($vendor->getTenant() !== $this) {
return $this;
}
$vendor->setTenant(null);
return $this;
}
}
<?php
declare(strict_types=1);
namespace App\Entity\Test;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\AssociationOverrides([new ORM\AssociationOverride(name: 'tenant', joinTable: new ORM\JoinTable(name: 'tenant'), inversedBy: 'vendors')])]
class TestVendor extends TestAbstractOrganization
{
use TestBelongsToTenantTrait;
public function addUser(TestAbstractUser $user): self
{
$user->setTenant($this->getTenant());
return parent::addUser($user);
}
}
<?php
declare(strict_types=1);
namespace App\Entity\Test;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\InheritanceType(value: 'JOINED')]
#[ORM\DiscriminatorColumn(name: 'discriminator', type: 'string')]
#[ORM\DiscriminatorMap(value: ['tenant' => TestTenant::class, 'vendor' => TestVendor::class])]
abstract class TestAbstractOrganization extends TestAbstractEntity
{
#[ORM\OneToMany(mappedBy: 'organization', targetEntity: TestAbstractUser::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
protected Collection $users;
public function __construct()
{
$this->users = new ArrayCollection();
parent::__construct();
}
public function getUsers(): Collection
{
return $this->users;
}
public function addUser(TestAbstractUser $user): self
{
if (!$this->users->contains($user)) {
$this->users[] = $user;
}
$user->setOrganization($this);
return $this;
}
public function removeUser(TestAbstractUser $user): self
{
if ($this->users->removeElement($user)) {
if ($user->getOrganization() === $this) {
$user->setOrganization(null);
}
}
return $this;
}
}
<?php
declare(strict_types=1);
namespace App\Entity\Test;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class TestTenantUser extends TestAbstractUser
{
use TestBelongsToTenantTrait;
}
<?php
declare(strict_types=1);
namespace App\Entity\Test;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class TestVendorUser extends TestAbstractUser
{
use TestBelongsToTenantTrait;
}
<?php
declare(strict_types=1);
namespace App\Entity\Test;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\InheritanceType(value: 'JOINED')]
#[ORM\DiscriminatorColumn(name: 'discriminator', type: 'string')]
#[ORM\DiscriminatorMap(value: ['tenant' => TestTenantUser::class, 'vendor' => TestVendorUser::class])]
abstract class TestAbstractUser extends TestAbstractEntity
{
#[ORM\ManyToOne(inversedBy: 'users')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
protected ?TestAbstractOrganization $organization=null;
public function getOrganization(): TestAbstractOrganization
{
return $this->organization;
}
public function setOrganization(TestAbstractOrganization $organization): self
{
$this->organization = $organization;
return $this;
}
}
<?php
declare(strict_types=1);
namespace App\Entity\Test;
use Doctrine\ORM\Mapping as ORM;
use DateTime;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Uid\Ulid;
use App\Doctrine\IdGenerator\UlidGenerator;
abstract class TestAbstractEntity
{
#[ORM\Id]
#[ORM\Column(type: 'ulid', unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
protected ?Ulid $id = null;
#[ORM\Column(type: 'datetime')]
//#[Gedmo\Timestampable(on: 'create')]
protected ?DateTime $createAt = null;
#[ORM\ManyToOne(targetEntity: TestAbstractUser::class)]
#[ORM\JoinColumn(nullable: false)]
//#[Gedmo\Blameable(on: 'create')]
protected ?TestAbstractUser $createBy = null;
#[ORM\Column(type: 'datetime')]
//#[Gedmo\Timestampable(on: 'update')]
protected ?DateTime $updateAt = null;
#[ORM\ManyToOne(targetEntity: TestAbstractUser::class)]
#[ORM\JoinColumn(nullable: false)]
//#[Gedmo\Blameable(on: 'update')]
protected ?TestAbstractUser $updateBy = null;
public function __construct()
{
$this->createAt = new DateTime();
$this->updateAt = new DateTime();
}
public function getId(): ?Ulid
{
return $this->id;
}
public function getCreateAt(): ?DateTime
{
return $this->createAt;
}
public function getCreateBy(): ?TestAbstractUser
{
return $this->createBy;
}
// Shouldn't need this? Maybe only by HelpDesk?
public function setCreateAt(DateTime $date): self
{
$this->createAt = $date;
return $this;
}
public function setCreateBy(TestAbstractUser $user): self
{
$this->createBy = $user;
return $this;
}
public function getUpdateAt(): ?DateTime
{
return $this->updateAt;
}
public function getUpdateBy(): ?TestAbstractUser
{
return $this->updateBy;
}
// Shouldn't need this? Maybe only by HelpDesk?
public function setUpdateAt(DateTime $date): self
{
$this->updateAt = $date;
return $this;
}
public function setUpdateBy(TestAbstractUser $user): self
{
$this->updateBy = $user;
return $this;
}
}
<?php
declare(strict_types=1);
namespace App\Entity\Test;
use Doctrine\ORM\Mapping as ORM;
trait TestBelongsToTenantTrait
{
#[ORM\ManyToOne(targetEntity: TestTenant::class)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
protected ?TestTenant $tenant=null;
public function getTenant(): ?TestTenant
{
return $this->tenant;
}
public function setTenant(TestTenant $tenant): self
{
$this->tenant = $tenant;
return $this;
}
}
Here is the error message when displaying: "An exception occurred while executing a query: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'done' cannot be null"
I don't understand why this error occurs when everything seems very normal.
Here's my DefaultController.php file:
<?php
namespace App\Controller;
use App\Entity\Todo;
use App\Form\TodoType;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class DefaultController extends AbstractController {
#[Route('/', name:'index')]
public function index(Request $request, ManagerRegistry $mr) {
$todo = new Todo();
$todoForm = $this->createForm(TodoType::class, $todo);
$todoForm->handleRequest($request);
// dd($todoForm);
if($todoForm->isSubmitted() && $todoForm->isValid() ){
$todo->setCreatedAt(new \DateTime());
$entityManager = $mr->getManager();
$entityManager->persist($todo);
$entityManager->flush();
}
return $this->render('./page1.html.twig',
['form'=>$todoForm->createView()]
);
}
}
Here is the code of my entity class todo:
<?php
namespace App\Entity;
use App\Repository\TodoRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: TodoRepository::class)]
class Todo
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string', length: 255)]
private $content;
#[ORM\Column(type: 'boolean', options:["default" => false])]
private $done;
#[ORM\Column(type: 'datetime' , options:["default" => "CURRENT_TIMESTAMP"])]
private $createdAt;
public function getId(): ?int
{
return $this->id;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getDone(): ?bool
{
return $this->done;
}
public function setDone(bool $done): self
{
$this->done = $done;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
}
Here is my TodoType form:
<?php
namespace App\Form;
use App\Entity\Todo;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TodoType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('content')
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Todo::class,
]);
}
}
I tried another method by injecting the EntityManagerInterface service in my controller, unfortunately. I get the same error message. Need help please.
I think instead of initialising your property withing the ORM you can do it underneath
#[ORM\Column(type: 'boolean')]
private $done = false;
Or you could do it as soon as you submit the form, by any means when you submit the form it sets the done value to false. However this method isn't practical.
if($todoForm->isSubmitted() && $todoForm->isValid() ){
$todo->setCreatedAt(new \DateTime());
$todo->setDone(0); // Sets the value to false as soon as the form is submitted
$entityManager = $mr->getManager();
$entityManager->persist($todo);
$entityManager->flush();