Home > Blockchain >  Add filters with API Platform on column from a Trait
Add filters with API Platform on column from a Trait

Time:03-23

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
  • Related