I would like to add a filter on the column 'createdAt' that doesn't come from the class but from a trait.
I tried to use 'createdAt', 'timestampable.createdAt' or 'TimestampableEntity.createdAt' but any of that solutions work.
I don't know if it's possible to access the trait to activate a filter.
Order.php
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use App\Api\Filters\FullTextSearchFilter;
use App\Api\CustomController\Order\PostController;
use App\Entity\Traits\BlameableEntity;
use App\Entity\Traits\TimestampableEntity;
use App\Repository\OrderRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use App\Api\Filters\ArchivedFilter;
/**
* @ORM\Entity(repositoryClass=OrderRepository::class)
* @ORM\Table(name="orders")
* @ORM\EntityListeners({"App\EventListener\OrderListener"})
*
* @ApiResource(
* collectionOperations={
* "get"={
* "normalization_context"={"groups"={"order:collection:get", "timestampable"}},
* },
* "colikadoTransmission"={
* "method"="GET",
* "path"="/orders/transmission/colikado",
* "normalization_context"={"groups"={"order:collection:colikadotransmission", "timestampable"}},
* },
* "post"={
* "controller"=PostController::class,
* "denormalization_context"={"groups"={"order:collection:post", "order:collection:get"}},
* },
* },
* itemOperations={
* "get"={
* "normalization_context"={"groups"={"order:item:get", "timestampable"}},
* "security"="object.getOwner() == user || is_granted('ROLE_ADMIN')",
* },
* "invoice"={
* "method"="GET",
* "path"="/orders/{id}/invoice",
* "controller"=\App\Api\CustomController\Order\InvoiceController::class,
* "security"="object.getOwner() == user || is_granted('ROLE_ADMIN')",
* },
* "patchstatus"={
* "method"="PATCH",
* "path"="/orders/{id}/status",
* "validation_groups"={"Default", "order:item:patchstatus"},
* "normalization_context"={"groups"={"order:item:patchstatus"}},
* "denormalization_context"={"groups"={"order:item:patchstatus"}},
* "security"="object.getOwner() == user || is_granted('ROLE_ADMIN')",
* },
* "updateAdmin"={
* "method"="PATCH",
* "path"="/orders/{id}",
* "validation_groups"={"Default", "order:item:patch:admin"},
* "normalization_context"={"groups"={"order:item:patch:admin"}},
* "denormalization_context"={"groups"={"order:item:patch:admin"}},
* "security"="is_granted('ROLE_ADMIN')",
* "controller"=\App\Api\CustomController\Order\UpdateController::class,
* },
* "ipn"={
* "method"="GET",
* "path"="/orders/ipn/{id}",
* "controller"=\App\Api\CustomController\Order\IpnController::class,
* }
* },
* attributes={"order"={"createdAt": "DESC"}}
* )
* @ApiFilter(OrderFilter::class)
* @ApiFilter(
* SearchFilter::class,
* properties={
* "id"="exact",
* "owner" = "exact",
* "status" = "exact",
* "contactChannel" = "exact",
* "owner.email" = "ipartial",
* "owner.address.firstName" = "ipartial",
* "owner.address.lastName" = "ipartial",
* "owner.address.postalCode" = "ipartial",
* "owner.address.address" = "ipartial",
* "status" = "ipartial",
* "createdAt" = "ipartial"
* }
* )
* @ApiFilter(ArchivedFilter::class, properties={
* "queryParam": "archived",
* "property": "status",
* "archived": Order::ORDER_ARCHIVED_STATUSES
* })
*
*/
class Order
{
use TimestampableEntity;
use BlameableEntity;
/** CONST STATUS */
public const STATUS_IN_PROGRESS = "order.in_progress"; // en cours de saisie
public const STATUS_PENDING = "order.pending"; // mise en attente par une opératrice
public const STATUS_AWAITING_DELAYED_PAYMENT = "order.awaiting_delayed_payment"; // en attente de réception virements ou de chèque
public const STATUS_PAID = "order.paid";
public const STATUS_ADMIN_AUTHORIZED = "order.admin_authorized";
public const STATUS_COMPLETE = "order.complete";
public const STATUS_PAYMENT_ISSUE = "order.payment_issue";
public const STATUS_CANCELED = "order.canceled";
public const ORDER_ARCHIVED_STATUSES = [
self::STATUS_IN_PROGRESS,
self::STATUS_PAID,
self::STATUS_ADMIN_AUTHORIZED,
self::STATUS_COMPLETE,
self::STATUS_PAYMENT_ISSUE,
self::STATUS_CANCELED,
];
public const ORDER_UNMODIFIABLE_STATUSES = [self::STATUS_COMPLETE];
const CHANNEL_SHOW_ROOM = "order.contact_channel.show_room";
const CHANNEL_WEB = "order.contact_channel.web";
const CHANNEL_LETTER = "order.contact_channel.letter";
const CHANNEL_EMAIL = "order.contact_channel.email";
const CHANNEL_PHONE = "order.contact_channel.phone";
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Groups({
* "order:collection:get",
* "user:collection:get",
* "orderitem:collection:get",
* "order:collection:colikadotransmission",
* })
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=User::class, inversedBy="orders")
* @ORM\JoinColumn(nullable=false)
* @Groups({
* "order:collection:post",
* "order:collection:get",
* "cartsproducts:collection:get",
* "order:item:get",
* "orderitem:collection:get",
* "order:collection:colikadotransmission",
* })
*/
private $owner;
/**
* @Groups({
* "order:item:get",
* "order:collection:get",
* "order:collection:post",
* })
* @ORM\OneToOne(targetEntity=Cart::class, inversedBy="order")
* @ORM\JoinColumn(nullable=true)
*/
private $cart;
/**
* @ApiSubresource
* @ORM\OneToMany(targetEntity=Payment::class, mappedBy="order", cascade={"persist"})
*/
private $payments;
/**
* @Groups({
* "order:item:patchstatus",
* "order:item:get",
* "order:item:patch:admin",
* "order:collection:get",
* "cartsproducts:collection:get",
* "orderitem:collection:get",
* "order:collection:colikadotransmission",
* })
* @ORM\Column(type="string", length=255)
*/
private $status = self::STATUS_PENDING;
/**
* @Groups({
* "order:collection:colikadotransmission"
* })
*
* NOT PERSISTED, admin only property to control payment behavior.
*
* @var string
*/
private $paymentMethod = Payment::PAYMENT_CREDIT_CARD;
/**
* NOT PERSISTED.
*
* @var string|null
*/
private $paymentAdditionalInfo;
/**
* @Groups({
* "order:item:get",
* "order:collection:get",
* "order:collection:colikadotransmission",
* })
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $contactChannel = self::CHANNEL_WEB;
/**
* NOT persisted.
*
* @Groups({
* "order:item:get",
* "order:item:patch:admin",
* "cartsproducts:collection:get",
* "order:collection:colikadotransmission"
* })
*
* @var bool
*/
private $archived;
/**
* @Groups({"cartsproducts:collection:get", "order:item:get"})
*
* @ORM\OneToOne(targetEntity=SupportTicket::class, mappedBy="reshippedOrder")
*/
private $supportTicket;
/**
* @Groups({"order:item:get", "order:collection:get", "order:item:patch:admin"})
*
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $invoicePathName;
/**
* @Groups({
* "order:collection:colikadotransmission",
* })
*
* @ApiSubresource()
*
* @ORM\OneToMany(targetEntity=OrderItem::class, mappedBy="parentOrder", cascade={"persist"}, orphanRemoval=true)
*/
private $orderItems;
/**
* Total price as HT (hors taxe) out of tax
* and without discounts.
*
* @Groups({
* "order:collection:colikadotransmission",
* "order:item:get",
* "order:collection:get",
* })
* @ORM\Column(type="float")
*/
private $priceTotal = 0;
/**
* Total tax from products.
* Without discounts and without product prices.
*
* @Groups({
* "order:collection:colikadotransmission",
* "order:item:get",
* })
* @ORM\Column(type="float")
*/
private $taxTotal = 0;
/**
* @Groups({
* "order:collection:colikadotransmission",
* "order:item:get",
* })
* @ORM\Column(type="float")
*/
private $discountTotal = 0;
/**
* @Groups({
* "orderitem:collection:get",
* "order:collection:get",
* "order:item:get",
* })
*
* @ORM\ManyToOne(targetEntity=OrderAddress::class, inversedBy="orders", cascade={"persist", "remove"})
* @ORM\JoinColumn(nullable=true)
*/
private $senderAddress;
public function __construct()
{
$this->payments = new ArrayCollection();
$this->orderItems = new ArrayCollection();
}
/**
* @return Collection|CartItem[]
*/
public function getSentCartsProducts()
{
$products = [];
/** @var OrderItem $product */
foreach ($this->orderItems as $product) {
if ($product->getState() === OrderItem::STATE_SCANNED) {
$products[] = $product;
}
}
return $products;
}
/**
* Count all quantities from all order items products items.
*
* @Groups({"order:item:get"})
*/
public function getAllQuantities()
{
return $this->getOrderItems()->count();
}
/**
* Get final price (incl. tax discounts)
*/
public function getFinalPrice()
{
return $this->getPriceTotal() $this->getTaxTotal();
}
public function getId(): ?int
{
return $this->id;
}
public function getOwner(): ?User
{
return $this->owner;
}
public function setOwner(?User $owner): self
{
$this->owner = $owner;
return $this;
}
public function getCart(): ?Cart
{
return $this->cart;
}
public function setCart(?Cart $cart): self
{
$this->cart = $cart;
return $this;
}
/**
* @return Collection|Payment[]
*/
public function getPayments(): Collection
{
return $this->payments;
}
public function addPayment(Payment $payment): self
{
if (!$this->payments->contains($payment)) {
$this->payments[] = $payment;
$payment->setOrder($this);
}
return $this;
}
public function removePayment(Payment $payment): self
{
if ($this->payments->removeElement($payment)) {
// set the owning side to null (unless already changed)
if ($payment->getOrder() === $this) {
$payment->setOrder(null);
}
}
return $this;
}
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
/**
* @return string
*/
public function getPaymentMethod(): string
{
return $this->paymentMethod;
}
/**
* @param string $paymentMethod
*
* @return self
*/
public function setPaymentMethod(string $paymentMethod): self
{
$this->paymentMethod = $paymentMethod;
return $this;
}
/**
* @return string|null
*/
public function getPaymentAdditionalInfo(): ?string
{
return $this->paymentAdditionalInfo;
}
/**
* @param string|null $paymentAdditionalInfo
*
* @return self
*/
public function setPaymentAdditionalInfo(
?string $paymentAdditionalInfo
): self {
$this->paymentAdditionalInfo = $paymentAdditionalInfo;
return $this;
}
public function getContactChannel(): ?string
{
return $this->contactChannel;
}
public function setContactChannel(?string $contactChannel): self
{
$this->contactChannel = $contactChannel;
return $this;
}
/**
* @return bool
*/
public function isArchived(): bool
{
return in_array($this->getStatus(), self::ORDER_ARCHIVED_STATUSES);
}
public function getSupportTicket(): ?SupportTicket
{
return $this->supportTicket;
}
public function setSupportTicket(?SupportTicket $supportTicket): self
{
// unset the owning side of the relation if necessary
if ($supportTicket === null && $this->supportTicket !== null) {
$this->supportTicket->setReshippedOrder(null);
}
// set the owning side of the relation if necessary
if (
$supportTicket !== null &&
$supportTicket->getReshippedOrder() !== $this
) {
$supportTicket->setReshippedOrder($this);
}
$this->supportTicket = $supportTicket;
return $this;
}
public function getInvoicePathName(): ?string
{
return $this->invoicePathName;
}
public function setInvoicePathName(?string $invoicePathName): self
{
$this->invoicePathName = $invoicePathName;
return $this;
}
/**
* @return Collection|OrderItem[]
*/
public function getOrderItems(): Collection
{
return $this->orderItems;
}
public function addOrderItem(OrderItem $orderItem): self
{
if (!$this->orderItems->contains($orderItem)) {
$this->orderItems[] = $orderItem;
$orderItem->setParentOrder($this);
}
return $this;
}
public function removeOrderItem(OrderItem $orderItem): self
{
if ($this->orderItems->removeElement($orderItem)) {
// set the owning side to null (unless already changed)
if ($orderItem->getParentOrder() === $this) {
$orderItem->setParentOrder(null);
}
}
return $this;
}
public function getPriceTotal(): ?float
{
return $this->priceTotal;
}
public function setPriceTotal(float $priceTotal): self
{
$this->priceTotal = $priceTotal;
return $this;
}
public function getTaxTotal(): ?float
{
return $this->taxTotal;
}
public function setTaxTotal(float $taxTotal): self
{
$this->taxTotal = $taxTotal;
return $this;
}
public function getDiscountTotal(): ?float
{
return $this->discountTotal;
}
public function setDiscountTotal(float $discountTotal): self
{
$this->discountTotal = $discountTotal;
return $this;
}
public function getSenderAddress(): ?OrderAddress
{
return $this->senderAddress;
}
public function setSenderAddress(?OrderAddress $senderAddress): self
{
$this->senderAddress = $senderAddress;
return $this;
}
}
TimestampableEntity.php (the trait)
<?php
namespace App\Entity\Traits;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* Timestampable Trait, usable with PHP >= 5.4
*
* @author Gediminas Morkevicius <[email protected]>
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
trait TimestampableEntity
{
/**
* @Groups({"timestampable"})
*
* @var \DateTime
* @Gedmo\Timestampable(on="create")
* @ORM\Column(type="datetime", nullable=true)
*/
protected $createdAt;
/**
* @Groups({"timestampable"})
*
* @var \DateTime
* @Gedmo\Timestampable(on="update")
* @ORM\Column(type="datetime", nullable=true)
*/
protected $updatedAt;
/**
* Sets createdAt.
*
* @return $this
*/
public function setCreatedAt(\DateTime $createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* Returns createdAt.
*
* @return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Sets updatedAt.
*
* @return $this
*/
public function setUpdatedAt(\DateTime $updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Returns updatedAt.
*
* @return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
}
CodePudding user response:
Your mistake is that you declared the $createdAt
and $updatedAt
properties in the TimestampableEntity
trait as protected
, so you can't access them in the ApiFilter annotations or anywhere else. Change protected
to public
and everything will work for you.
I also want to pay attention to DateFilter
as an alternative to SearchFilter
for date properties
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
/**
* ApiResource...
*
* @ApiFilter(DateFilter::class, properties={"createAt"})
*/
class Order