Skip to content
Snippets Groups Projects
Commit a7de91ea authored by Nikita Chernykh's avatar Nikita Chernykh
Browse files

feature: Добавлена база фильтров и первые тесты

parent a2dbd225
No related branches found
No related tags found
No related merge requests found
Showing with 591 additions and 3 deletions
......@@ -3,6 +3,7 @@
### Composer ###
composer.phar
composer.lock
/vendor/
/var/
......
......@@ -5,7 +5,8 @@ use PhpCsFixer\Finder;
$finder = Finder::create()
->in([
__DIR__ . '/app',
__DIR__ . '/src',
__DIR__ . '/tests',
])
->name('*.php');
......
......@@ -9,7 +9,11 @@
},
"require-dev": {
"phpunit/phpunit": "^12.0",
"friendsofphp/php-cs-fixer": "^3.70"
"friendsofphp/php-cs-fixer": "^3.70",
"symfony/cache": "^7.2",
"doctrine/migrations": "^3.8",
"doctrine/data-fixtures": "^2.0",
"fakerphp/faker": "^1.24"
},
"license": "MIT",
"autoload": {
......
<?php
declare(strict_types=1);
namespace IQDEV\Packages\DoctrineHttpFilter\Filter;
use Doctrine\ORM\QueryBuilder;
use IQDEV\Packages\DoctrineHttpFilter\HttpFilter;
final class Like extends HttpFilter
{
public function addToQuery(QueryBuilder $queryBuilder): QueryBuilder
{
$queryBuilder->where(
$this->getColumn() . ' LIKE \'%' . $this->getHttpValue() . '%\'',
);
return $queryBuilder;
}
}
<?php
declare(strict_types=1);
namespace IQDEV\Packages\DoctrineHttpFilter\Filter;
use Doctrine\ORM\QueryBuilder;
use IQDEV\Packages\DoctrineHttpFilter\HttpFilter;
final class Where extends HttpFilter
{
public function addToQuery(QueryBuilder $queryBuilder): QueryBuilder
{
$queryBuilder->where(
$this->getColumn() . ' = :' . $this->getParameterKey(),
);
$queryBuilder->setParameter($this->getParameterKey(), $this->getHttpValue());
return $queryBuilder;
}
}
<?php
declare(strict_types=1);
namespace IQDEV\Packages\DoctrineHttpFilter;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\Request;
abstract class HttpFilter
{
public const string REQUEST_FILTER_KEY = 'filters';
protected Request $request;
public function __construct(
protected string $tableAlias,
protected string $filed,
?Request $request = null,
) {
$this->request = $request ?? Request::createFromGlobals();
}
protected function getColumn(): string
{
return $this->tableAlias . '.' . $this->filed;
}
protected function getHttpValue(): mixed
{
$filter = $this
->request
->query
->getIterator()[static::REQUEST_FILTER_KEY] ?? null;
if ($filter === null) {
return null;
}
return $filter[$this->filed] ?? null;
}
public function getParameterKey(): string
{
return str_replace('.', '_', $this->getColumn());
}
abstract public function addToQuery(QueryBuilder $queryBuilder): QueryBuilder;
}
<?php
declare(strict_types=1);
namespace IQDEV\Packages\DoctrineHttpFilter;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\Request;
abstract class HttpFilterEntityRepository extends EntityRepository implements QueryFilterInterface
{
/** @inheritDoc */
public function createQueryByFilter(iterable $filters, ?Request $request = null): QueryBuilder
{
$tableAlias = $this->getAliasTableForFilter();
$queryBuilder = $this->createQueryBuilder($tableAlias);
/**
* @var string $field
* @var string|HttpFilter $filter
*/
foreach ($filters as $field => $filter) {
if (! $filter instanceof HttpFilter && is_string($filter)) {
$filter = new $filter($tableAlias, $field, $request);
}
$filter->addToQuery($queryBuilder);
}
return $queryBuilder;
}
abstract public function getAliasTableForFilter(): string;
}
<?php
declare(strict_types=1);
namespace IQDEV\Packages\DoctrineHttpFilter;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\Request;
interface QueryFilterInterface
{
/** @param iterable<string, HttpFilter> $filters */
public function createQueryByFilter(iterable $filters, ?Request $request = null): QueryBuilder;
}
<?php
declare(strict_types=1);
namespace IQDEV\Tests\Packages\DoctrineHttpFilter\Entity;
use Doctrine\ORM\Mapping as ORM;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Repository\CommentRepository;
#[ORM\Entity(repositoryClass: CommentRepository::class)]
class Comment
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
public ?string $author = null;
#[ORM\Column(type: 'text')]
public ?string $content = null;
#[ORM\Column(name: 'created_at', type: 'datetime_immutable')]
public ?\DateTimeImmutable $createdAt = null;
#[ORM\Column(name: 'updated_at', type: 'datetime_immutable', nullable: true)]
public ?\DateTimeImmutable $updatedAt = null;
#[ORM\ManyToOne(inversedBy: 'comments')]
public ?Post $post = null;
public function __construct(
string $author,
string $content,
\DateTimeImmutable $createdAt,
Post $post,
?\DateTimeImmutable $updatedAt = null,
) {
$this->author = $author;
$this->content = $content;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
$this->post = $post;
}
}
<?php
declare(strict_types=1);
namespace IQDEV\Tests\Packages\DoctrineHttpFilter\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Repository\PostRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: PostRepository::class)]
class Post
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
public ?string $title = null;
#[ORM\Column(type: 'text')]
public ?string $content = null;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
public ?bool $moderated = null;
#[ORM\Column(name: 'created_at', type: 'datetime_immutable')]
public ?\DateTimeImmutable $createdAt = null;
#[ORM\Column(name: 'updated_at', type: 'datetime_immutable', nullable: true)]
public ?\DateTimeImmutable $updatedAt = null;
#[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'post')]
public Collection $comments;
public function __construct(
string $title,
?string $content,
?bool $moderated,
?\DateTimeImmutable $createdAt,
?\DateTimeImmutable $updatedAt = null,
) {
$this->title = $title;
$this->content = $content;
$this->moderated = $moderated;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
$this->comments = new ArrayCollection();
}
}
<?php
declare(strict_types=1);
namespace IQDEV\Tests\Packages\DoctrineHttpFilter;
use IQDEV\Packages\DoctrineHttpFilter\Filter\Where;
use IQDEV\Packages\DoctrineHttpFilter\HttpFilter;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Entity\Post;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Repository\PostRepository;
use Symfony\Component\HttpFoundation\Request;
class FilterByWhereTest extends TestCase
{
public function testSuccessFilterWhereWithOneResult(): void
{
/** @var PostRepository $postRepository */
$postRepository = $this->em->getRepository(Post::class);
$title = $this->faker->name();
$post = new Post(
$title,
$this->faker->text(),
$this->faker->boolean(),
\DateTimeImmutable::createFromInterface($this->faker->dateTime()),
);
$post2 = new Post(
$this->faker->name(),
$this->faker->text(),
$this->faker->boolean(),
\DateTimeImmutable::createFromInterface($this->faker->dateTime()),
);
$this->em->persist($post);
$this->em->persist($post2);
$this->em->flush();
$result = $postRepository->createQueryByFilter([
'title' => Where::class,
], new Request([
HttpFilter::REQUEST_FILTER_KEY => [
'title' => $title,
],
]))
->getQuery()
->getResult();
$this->assertNotEmpty($result);
$this->assertCount(1, $result);
$this->assertEquals($title, current($result)->title);
}
public function testSuccessFilterWhereWithSeveralResult(): void
{
/** @var PostRepository $postRepository */
$postRepository = $this->em->getRepository(Post::class);
$title = $this->faker->name();
$post = new Post(
$title,
$this->faker->text(),
$this->faker->boolean(),
\DateTimeImmutable::createFromInterface($this->faker->dateTime()),
);
$post2 = new Post(
$title,
$this->faker->text(),
$this->faker->boolean(),
\DateTimeImmutable::createFromInterface($this->faker->dateTime()),
);
$this->em->persist($post);
$this->em->persist($post2);
$this->em->flush();
$result = $postRepository->createQueryByFilter([
'title' => Where::class,
], new Request([
HttpFilter::REQUEST_FILTER_KEY => [
'title' => $title,
],
]))
->getQuery()
->getResult();
$this->assertNotEmpty($result);
$this->assertCount(2, $result);
$this->assertEquals($title, current($result)->title);
$this->assertEquals($title, next($result)->title);
}
public function testFilterWhereWithNotResult(): void
{
/** @var PostRepository $postRepository */
$postRepository = $this->em->getRepository(Post::class);
$result = $postRepository->createQueryByFilter([
'title' => Where::class,
], new Request([
HttpFilter::REQUEST_FILTER_KEY => [
'title' => 'Стимул',
],
]))
->getQuery()
->getResult();
$this->assertEmpty($result);
}
}
<?php
declare(strict_types=1);
namespace IQDEV\Tests\Packages\DoctrineHttpFilter\Fixture;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Faker\Factory;
use Faker\Generator;
abstract class BaseFixture extends AbstractFixture
{
protected Generator $faker;
public function __construct(
protected int $count = 1,
string $locale = Factory::DEFAULT_LOCALE,
) {
$this->faker = Factory::create($locale);
}
}
<?php
declare(strict_types=1);
namespace IQDEV\Tests\Packages\DoctrineHttpFilter\Fixture;
use Doctrine\Persistence\ObjectManager;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Entity\Comment;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Entity\Post;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Repository\PostRepository;
class CommentFixture extends BaseFixture
{
public function load(ObjectManager $manager): void
{
/** @var PostRepository $postRepository */
$postRepository = $manager->getRepository(Post::class);
/** @var array<Post> $posts */
$posts = $postRepository->findAll();
for ($i = 0; $i < $this->count; $i++) {
$comment = new Comment(
$this->faker->userName(),
$this->faker->text(),
\DateTimeImmutable::createFromInterface($this->faker->dateTime()),
$this->faker->randomElement($posts),
);
$manager->persist($comment);
}
$manager->flush();
}
public function getDependencies(): array
{
return [
PostFixture::class,
];
}
}
<?php
declare(strict_types=1);
namespace IQDEV\Tests\Packages\DoctrineHttpFilter\Fixture;
use Doctrine\Persistence\ObjectManager;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Entity\Post;
class PostFixture extends BaseFixture
{
public function load(ObjectManager $manager): void
{
for ($i = 0; $i < $this->count; ++$i) {
$post = new Post(
$this->faker->name(),
$this->faker->text(),
$this->faker->boolean(),
\DateTimeImmutable::createFromInterface($this->faker->dateTime()),
);
$manager->persist($post);
}
$manager->flush();
}
}
<?php
declare(strict_types=1);
namespace IQDEV\Tests\Packages\DoctrineHttpFilter\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use IQDEV\Packages\DoctrineHttpFilter\HttpFilterEntityRepository;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Entity\Comment;
class CommentRepository extends HttpFilterEntityRepository
{
public function __construct(EntityManagerInterface $em)
{
parent::__construct($em, new ClassMetadata(Comment::class));
}
public function getAliasTableForFilter(): string
{
return 'c';
}
}
<?php
declare(strict_types=1);
namespace IQDEV\Tests\Packages\DoctrineHttpFilter\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use IQDEV\Packages\DoctrineHttpFilter\HttpFilterEntityRepository;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Entity\Post;
class PostRepository extends HttpFilterEntityRepository
{
public function __construct(EntityManagerInterface $em)
{
parent::__construct($em, new ClassMetadata(Post::class));
}
public function getAliasTableForFilter(): string
{
return 'p';
}
}
......@@ -4,6 +4,100 @@ declare(strict_types=1);
namespace IQDEV\Tests\Packages\DoctrineHttpFilter;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Loader;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Tools\SchemaTool;
use Faker\Factory;
use Faker\Generator;
use IQDEV\Packages\DoctrineHttpFilter\HttpFilterEntityRepository;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Fixture\BaseFixture;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Fixture\CommentFixture;
use IQDEV\Tests\Packages\DoctrineHttpFilter\Fixture\PostFixture;
use PHPUnit\Framework\TestCase as BaseTestCase;
use Symfony\Component\HttpFoundation\Request;
abstract class TestCase extends BaseTestCase {}
abstract class TestCase extends BaseTestCase
{
protected EntityManagerInterface $em;
protected Generator $faker;
protected function setUp(): void
{
$this->em = $this->makeEntityManager();
$schemaTool = new SchemaTool($this->em);
$schemaTool->createSchema($this->em->getMetadataFactory()->getAllMetadata());
$this->faker = Factory::create();
$this->loadFixtures([
new PostFixture(count: 20),
new CommentFixture(200),
]);
$this->em->getConnection()->beginTransaction();
}
protected function tearDown(): void
{
if ($this->em->isOpen()) {
$this->em->getConnection()->rollBack();
$this->em->close();
}
unset($this->em);
}
protected function makeEntityManager(): EntityManagerInterface
{
$config = ORMSetup::createAttributeMetadataConfiguration(
[__DIR__ . '/Entity'],
true,
);
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'memory' => true,
], $config);
return new EntityManager(
$connection,
$config,
);
}
protected function makeRepositoryWithRequest(string $repositoryClass, Request $request): HttpFilterEntityRepository
{
/** @var HttpFilterEntityRepository $repoMock */
$repoMock = $this->getMockBuilder($repositoryClass)
->setConstructorArgs([$this->em])
->onlyMethods(['getRequest'])
->getMock();
$repoMock
->method('getRequest')
->willReturn($request);
return $repoMock;
}
/** @param iterable<BaseFixture> $fixtures */
protected function loadFixtures(iterable $fixtures): void
{
$fixtureLoader = new Loader();
/** @var BaseFixture $fixture */
foreach ($fixtures as $fixture) {
$fixtureLoader->addFixture($fixture);
}
$purger = new ORMPurger();
$executor = new ORMExecutor($this->em, $purger);
$executor->execute($fixtureLoader->getFixtures());
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment