Преглед изворни кода

document objects extended

master
Daniel пре 1 година
родитељ
комит
ed5ac424ba
8 измењених фајлова са 360 додато и 12 уклоњено
  1. +35
    -0
      migrations/Version20240322155533.php
  2. +1
    -1
      src/ApiResource/UserApi.php
  3. +13
    -0
      src/DataFixtures/FakeValues.php
  4. +71
    -8
      src/Entity/DocumentObject.php
  5. +2
    -2
      src/Entity/MediaObject.php
  6. +34
    -0
      src/Entity/User.php
  7. +5
    -1
      src/Factory/DocumentObjectFactory.php
  8. +199
    -0
      src/Filter/SearchMultiFieldsFilter.php

+ 35
- 0
migrations/Version20240322155533.php Прегледај датотеку

@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240322155533 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE document_object ADD created_by_id INT NOT NULL, ADD name VARCHAR(255) NOT NULL, ADD description LONGTEXT DEFAULT NULL');
$this->addSql('ALTER TABLE document_object ADD CONSTRAINT FK_16CF1A8AB03A8386 FOREIGN KEY (created_by_id) REFERENCES `user` (id)');
$this->addSql('CREATE INDEX IDX_16CF1A8AB03A8386 ON document_object (created_by_id)');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE document_object DROP FOREIGN KEY FK_16CF1A8AB03A8386');
$this->addSql('DROP INDEX IDX_16CF1A8AB03A8386 ON document_object');
$this->addSql('ALTER TABLE document_object DROP created_by_id, DROP name, DROP description');
}
}

+ 1
- 1
src/ApiResource/UserApi.php Прегледај датотеку

@@ -46,7 +46,7 @@ use Symfony\Component\Validator\Constraints as Assert;
stateOptions: new Options(entityClass: User::class),

)]
#[ApiFilter(SearchFilter::class, properties: ['firstName' => 'partial', 'lastName' => 'partial'])]
#[ApiFilter(SearchFilter::class, properties: ['firstName' => 'ipartial', 'lastName' => 'ipartial'])]
class UserApi
{
#[ApiProperty(readable: false, writable: false, identifier: true)]


+ 13
- 0
src/DataFixtures/FakeValues.php Прегледај датотеку

@@ -221,4 +221,17 @@ class FakeValues
'Hausaufgaben machen', 'Papierkram sortieren',
];

const BUSINESS_FILE_NAMES = [
'Neue Preisliste', 'Produktübersicht', 'Kundenbewertungen', 'Jahresbericht', 'Projektplan',
'Angebotsvorschlag', 'Marketingstrategie', 'Lieferantenvertrag', 'Finanzplan', 'Geschäftsbericht',
'Firmenpräsentation', 'Qualitätsmanagementplan', 'Vertriebsstrategie', 'Mitarbeiterhandbuch', 'Umsatzprognose',
'Kundenvertrag', 'Marktanalyse', 'Entwicklungsplan', 'Vermarktungsplan', 'Forschungsbericht',
'Investitionsplan', 'Risikoanalyse', 'Besprechungsprotokoll', 'Kundenbefragung', 'Personalplanung',
'Produktentwicklungsbericht', 'Budgetplan', 'Geschäftsplanung', 'Kundenreferenzliste', 'Präsentationsvorlage',
'Beschwerdeprotokoll', 'Verkaufsanalyse', 'Zielvereinbarung', 'Produktkonzept', 'Kundendatenbank',
'Marketingkampagne', 'Projektabschlussbericht', 'Erfolgsbilanz', 'Strategieplan', 'Vertragsentwurf',
'Kundendienstleistungsbericht', 'Verkaufsprognose', 'Geschäftsordnung', 'Produktbeschreibung', 'Rechnungsvorlage',
'Personalakte', 'Dienstleistungsvertrag', 'Kundenbestellformular', 'Geschäftsbrief', 'Qualitätskontrollbericht'
];

}

+ 71
- 8
src/Entity/DocumentObject.php Прегледај датотеку

@@ -16,6 +16,7 @@ use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\OpenApi\Model;
use App\Controller\CreateDocumentObjectAction;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
@@ -39,6 +40,14 @@ use Vich\UploaderBundle\Mapping\Annotation as Vich;
'schema' => [
'type' => 'object',
'properties' => [
'name' => [
'type' => 'string',
'required' => true,
],
'description' => [
'type' => 'text',
'required' => true,
],
'file' => [
'type' => 'string',
'format' => 'binary'
@@ -59,7 +68,7 @@ use Vich\UploaderBundle\Mapping\Annotation as Vich;
])
)
),
validationContext: ['groups' => ['Default', 'document_object_create']],
validationContext: ['groups' => ['Default', 'document_object:create']],
deserialize: false
),
new Delete(
@@ -75,26 +84,44 @@ class DocumentObject
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;

#[Groups(['document_object:read', 'document_object:create'])]
#[ORM\ManyToOne(inversedBy: 'documentObjects')]
#[ORM\JoinColumn(nullable: false)]
private ?User $createdBy = null;

#[Groups(['document_object:read', 'document_object:create'])]
#[Assert\NotNull(groups: ['document_object:create'])]
#[ORM\Column(length: 255)]
private ?string $name = null;

#[Groups(['document_object:read', 'document_object:create'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $description = null;

#[Groups(['document_object:read', 'document_object:create'])]
#[ORM\ManyToOne(inversedBy: 'documentObjects')]
private ?Partner $partner = null;

#[Groups(['document_object:read', 'document_object:create'])]
#[ORM\ManyToOne(inversedBy: 'documentObjects')]
private ?Product $product = null;

#[ApiProperty(types: ['https://schema.org/contentUrl'])]
#[Groups(['document_object:read'])]
public ?string $contentUrl = null;

#[Vich\UploadableField(mapping: 'document_object', fileNameProperty: 'filePath')]
#[Assert\NotNull(groups: ['document_object_create'])]
#[Assert\NotNull(groups: ['document_object:create'])]
public ?File $file = null;

#[ORM\Column(nullable: true)]
public ?string $filePath = null;

#[ApiProperty(writable: false)]
#[Groups(['document_object:read'])]
#[ORM\Column]
private ?\DateTimeImmutable $createdAt = null;

#[ORM\ManyToOne(inversedBy: 'documentObjects')]
private ?Partner $partner = null;

#[ORM\ManyToOne(inversedBy: 'documentObjects')]
private ?Product $product = null;

public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
@@ -149,4 +176,40 @@ class DocumentObject
return $this;
}

public function getCreatedBy(): ?User
{
return $this->createdBy;
}

public function setCreatedBy(?User $createdBy): static
{
$this->createdBy = $createdBy;

return $this;
}

public function getName(): ?string
{
return $this->name;
}

public function setName(string $name): static
{
$this->name = $name;

return $this;
}

public function getDescription(): ?string
{
return $this->description;
}

public function setDescription(?string $description): static
{
$this->description = $description;

return $this;
}

}

+ 2
- 2
src/Entity/MediaObject.php Прегледај датотеку

@@ -48,7 +48,7 @@ use Vich\UploaderBundle\Mapping\Annotation as Vich;
])
)
),
validationContext: ['groups' => ['Default', 'media_object_create']],
validationContext: ['groups' => ['Default', 'media_object:create']],
deserialize: false
),
new Delete(
@@ -68,7 +68,7 @@ class MediaObject
public ?string $contentUrl = null;

#[Vich\UploadableField(mapping: 'media_object', fileNameProperty: 'filePath')]
#[Assert\NotNull(groups: ['media_object_create'])]
#[Assert\NotNull(groups: ['media_object:create'])]
public ?File $file = null;

#[ORM\Column(nullable: true)]


+ 34
- 0
src/Entity/User.php Прегледај датотеку

@@ -58,6 +58,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\OneToMany(mappedBy: 'user', targetEntity: PartnerFollow::class)]
private Collection $partnerFollows;

#[ORM\OneToMany(mappedBy: 'createdBy', targetEntity: DocumentObject::class)]
private Collection $documentObjects;


public function __construct()
{
@@ -67,6 +70,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
$this->comments = new ArrayCollection();
$this->sales = new ArrayCollection();
$this->partnerFollows = new ArrayCollection();
$this->documentObjects = new ArrayCollection();
}

public function getId(): ?int
@@ -314,4 +318,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface

return $this;
}

/**
* @return Collection<int, DocumentObject>
*/
public function getDocumentObjects(): Collection
{
return $this->documentObjects;
}

public function addDocumentObject(DocumentObject $documentObject): static
{
if (!$this->documentObjects->contains($documentObject)) {
$this->documentObjects->add($documentObject);
$documentObject->setCreatedBy($this);
}

return $this;
}

public function removeDocumentObject(DocumentObject $documentObject): static
{
if ($this->documentObjects->removeElement($documentObject)) {
// set the owning side to null (unless already changed)
if ($documentObject->getCreatedBy() === $this) {
$documentObject->setCreatedBy(null);
}
}

return $this;
}
}

+ 5
- 1
src/Factory/DocumentObjectFactory.php Прегледај датотеку

@@ -2,6 +2,7 @@

namespace App\Factory;

use App\DataFixtures\FakeValues;
use App\Entity\DocumentObject;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\HttpKernel\KernelInterface;
@@ -62,8 +63,11 @@ final class DocumentObjectFactory extends ModelFactory
$randBool = (bool)random_int(0, 1);
return [
'file' => new ReplacingFile($randomFile),
'createdBy' => UserFactory::random(),
'name' => self::faker()->randomElement(FakeValues::BUSINESS_FILE_NAMES),
'description' => self::faker()->text(),
'partner' => $randBool ? PartnerFactory::random() : null,
'product' => !$randBool ? ProductFactory::random() : null
'product' => !$randBool ? ProductFactory::random() : null,
];
}



+ 199
- 0
src/Filter/SearchMultiFieldsFilter.php Прегледај датотеку

@@ -0,0 +1,199 @@
<?php
/**
* @author Daniel Knudsen <d.knudsen@spawntree.de>
* @date 22.03.24
*/
// https://gist.github.com/axelvnk/edf879af5c7dbd9616a4eeb77c7181a3

namespace App\Filter;


use ApiPlatform\Api\IdentifiersExtractorInterface;
use ApiPlatform\Api\IriConverterInterface;
use ApiPlatform\Doctrine\Common\Filter\SearchFilterInterface;
use ApiPlatform\Doctrine\Common\Filter\SearchFilterTrait;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Exception\InvalidArgumentException;
use ApiPlatform\Metadata\Operation;
use Closure;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
use Psr\Log\LoggerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;

final class SearchMultiFieldsFilter extends AbstractFilter implements SearchFilterInterface
{
use SearchFilterTrait;

public function __construct(
ManagerRegistry $managerRegistry,
IriConverterInterface $iriConverter,
?PropertyAccessorInterface $propertyAccessor = null,
?LoggerInterface $logger = null,
?array $properties = null,
?IdentifiersExtractorInterface $identifiersExtractor = null,
?NameConverterInterface $nameConverter = null,
public string $searchParameterName = 'search',
) {
parent::__construct($managerRegistry, $logger, $properties, $nameConverter);

$this->iriConverter = $iriConverter;
$this->identifiersExtractor = $identifiersExtractor;
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
}

protected function getIriConverter(): IriConverterInterface
{
return $this->iriConverter;
}

protected function getPropertyAccessor(): PropertyAccessorInterface
{
return $this->propertyAccessor;
}

/**
* {@inheritDoc}
*/
protected function filterProperty(
string $property,
mixed $value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = [],
): void {
if (
null === $value
|| $property !== $this->searchParameterName
) {
return;
}

$alias = $queryBuilder->getRootAliases()[0];
$ors = [];
$count = 0;

foreach (($this->getProperties() ?? []) as $prop => $caseSensitive) {
$filter = $this->generatePropertyOrWhere(
$queryBuilder,
$queryNameGenerator,
$alias,
$prop,
$value,
$resourceClass,
$count,
$caseSensitive ?? false,
);

if (null === $filter) {
continue;
}

[$expr, $exprParams] = $filter;
$ors[] = $expr;

$queryBuilder->setParameter($exprParams[1], $exprParams[0]);

++$count;
}

$queryBuilder->andWhere($queryBuilder->expr()->orX(...$ors));
}

protected function generatePropertyOrWhere(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $alias,
string $property,
string $value,
string $resourceClass,
int $key,
bool $caseSensitive = false,
): ?array {
if (
!$this->isPropertyEnabled($property, $resourceClass)
|| !$this->isPropertyMapped($property, $resourceClass, true)
) {
return null;
}

$field = $property;
$associations = [];

if ($this->isPropertyNested($property, $resourceClass)) {
[$alias, $field, $associations] = $this->addJoinsForNestedProperty(
$property,
$alias,
$queryBuilder,
$queryNameGenerator,
$resourceClass,
Join::INNER_JOIN,
);
}

$metadata = $this->getNestedMetadata($resourceClass, $associations);

if (
'id' === $field
|| !$metadata->hasField($field)
) {
return null;
}

$wrapCase = $this->createWrapCase($caseSensitive);
$valueParameter = ':' . $queryNameGenerator->generateParameterName($field);
$aliasedField = sprintf('%s.%s', $alias, $field);
$keyValueParameter = sprintf('%s_%s', $valueParameter, $key);

return [
$queryBuilder->expr()->like(
$wrapCase($aliasedField),
$wrapCase((string) $queryBuilder->expr()->concat("'%'", $keyValueParameter, "'%'")),
),
[$caseSensitive ? $value : strtolower($value), $keyValueParameter],
];
}

protected function createWrapCase(bool $caseSensitive): Closure
{
return static function (string $expr) use ($caseSensitive): string {
if ($caseSensitive) {
return $expr;
}

return sprintf('LOWER(%s)', $expr);
};
}

/**
* {@inheritDoc}
*/
protected function getType(string $doctrineType): string
{
return 'string';
}

public function getDescription(string $resourceClass): array
{
$props = $this->getProperties();

if (null === $props) {
throw new InvalidArgumentException('Properties must be specified');
}

return [
$this->searchParameterName => [
'property' => implode(', ', array_keys($props)),
'type' => 'string',
'required' => false,
'description' => 'Recherche sur les propriétés spécifiées.',
],
];
}
}

Loading…
Откажи
Сачувај