VacancyRepository   A
last analyzed

Complexity

Total Complexity 9

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 9
eloc 53
c 2
b 0
f 0
dl 0
loc 188
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A findBySlug() 0 6 1
A applyPaginationHints() 0 36 4
A setPaginator() 0 3 1
A setOptions() 0 11 1
A getPagination() 0 30 1
A findByRoadmapNameAndExternalIdentifier() 0 11 1
1
<?php
2
3
/*
4
 * This file is part of the Veslo project <https://github.com/symfony-doge/veslo>.
5
 *
6
 * (C) 2019 Pavel Petrov <[email protected]>.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @license https://opensource.org/licenses/GPL-3.0 GPL-3.0
12
 */
13
14
declare(strict_types=1);
15
16
namespace Veslo\AnthillBundle\Entity\Repository;
17
18
use Doctrine\Common\Collections\Criteria;
19
use Doctrine\ORM\Cache;
20
use Doctrine\ORM\QueryBuilder;
21
use Knp\Component\Pager\Pagination\AbstractPagination;
22
use Knp\Component\Pager\PaginatorInterface;
23
use Symfony\Component\OptionsResolver\OptionsResolver;
24
use Veslo\AnthillBundle\Entity\Vacancy;
25
use Veslo\AnthillBundle\Entity\Vacancy\Category;
26
use Veslo\AppBundle\Dto\Paginator\CriteriaDto as PaginationCriteria;
27
use Veslo\AppBundle\Entity\Repository\BaseEntityRepository;
28
use Veslo\AppBundle\Entity\Repository\PaginateableInterface;
29
30
/**
31
 * Vacancy repository
32
 */
33
class VacancyRepository extends BaseEntityRepository implements PaginateableInterface
34
{
35
    /**
36
     * A hint for the pagination building process to include a specific category match statement
37
     *
38
     * Usage example:
39
     * ```
40
     * $paginationCriteria->addHint(VacancyRepository::PAGINATION_HINT_CATEGORY, $category);
41
     * ```
42
     *
43
     * @const string
44
     */
45
    public const PAGINATION_HINT_CATEGORY = 'category';
46
47
    /**
48
     * A hint for the pagination building process to include a sync date after filter statement
49
     *
50
     * @const string
51
     */
52
    public const PAGINATION_HINT_SYNC_DATE_AFTER = 'synchronization_date_after';
53
54
    /**
55
     * A hint for the pagination building process to include a sync date before filter statement
56
     *
57
     * @const string
58
     */
59
    public const PAGINATION_HINT_SYNC_DATE_BEFORE = 'synchronization_date_before';
60
61
    /**
62
     * Modifies vacancy search query to provide data in small bunches
63
     *
64
     * @var PaginatorInterface
65
     */
66
    private $paginator;
67
68
    /**
69
     * Options for vacancy repository, ex. page cache time
70
     *
71
     * @var array
72
     */
73
    private $options;
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function setPaginator(PaginatorInterface $paginator): void
79
    {
80
        $this->paginator = $paginator;
81
    }
82
83
    /**
84
     * Sets options for vacancy repository
85
     *
86
     * @param array $options Options for vacancy repository, ex. page cache time
87
     *
88
     * @return void
89
     */
90
    public function setOptions(array $options): void
91
    {
92
        $optionsResolver = new OptionsResolver();
93
        $optionsResolver->setDefaults(
94
            [
95
                'cache_result_namespace' => md5(__CLASS__),
96
                'cache_result_lifetime'  => 300,
97
            ]
98
        );
99
100
        $this->options = $optionsResolver->resolve($options);
101
    }
102
103
    /**
104
     * Returns vacancy by specified SEO-friendly identifier
105
     *
106
     * @param string $slug SEO-friendly vacancy identifier
107
     *
108
     * @return Vacancy|null
109
     */
110
    public function findBySlug(string $slug): ?Vacancy
111
    {
112
        $criteria = new Criteria();
113
        $criteria->andWhere($criteria->expr()->eq('e.slug', $slug));
114
115
        return $this->getQuery($criteria)->getOneOrNullResult();
116
    }
117
118
    /**
119
     * Returns vacancy by roadmap name and identifier on external job website
120
     *
121
     * @param string $roadmapName        Roadmap name
122
     * @param string $externalIdentifier Identifier on external job website
123
     *
124
     * @return Vacancy|null
125
     */
126
    public function findByRoadmapNameAndExternalIdentifier(string $roadmapName, string $externalIdentifier): ?Vacancy
127
    {
128
        $criteria = new Criteria();
129
        $criteria
130
            ->andWhere($criteria->expr()->eq('e.roadmapName', $roadmapName))
131
            ->andWhere($criteria->expr()->eq('e.externalIdentifier', $externalIdentifier))
132
        ;
133
134
        $query = $this->getQuery($criteria);
135
136
        return $query->getOneOrNullResult();
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     *
142
     * Prevents exposition of database layer details to other application layers
143
     * All doctrine-related things (ex. criteria building) remains encapsulated
144
     */
145
    public function getPagination(PaginationCriteria $criteria): AbstractPagination
146
    {
147
        $queryBuilder = $this->createQueryBuilder('e');
148
        $queryBuilder
149
            // fetch joins for caching.
150
            ->innerJoin('e.company', 'c')
151
            ->addSelect('c')
152
            // inner join for company is required; due to fixtures loading logic there are some cases
153
            // when a deletion date can be set in company and not present in related vacancies at the same time.
154
            // it leads to inconsistent state in the test environment; normally, a soft delete logic should be
155
            // properly applied for all relations at once.
156
            ->leftJoin('e.categories', 'ct')
157
            ->addSelect('ct')
158
            ->orderBy('e.id', Criteria::DESC)
159
        ;
160
161
        $queryBuilder = $this->applyPaginationHints($queryBuilder, $criteria);
162
163
        list($page, $limit, $options) = [$criteria->getPage(), $criteria->getLimit(), $criteria->getOptions()];
164
165
        $query = $queryBuilder->getQuery();
166
        $query
167
            ->setCacheable(true)
168
            ->setCacheMode(Cache::MODE_NORMAL)
169
        ;
170
171
        /** @var AbstractPagination $pagination */
172
        $pagination = $this->paginator->paginate($query, $page, $limit, $options);
173
174
        return $pagination;
175
    }
176
177
    /**
178
     * Returns a query builder instance modified according to the pagination hints, provided by criteria
179
     *
180
     * @param QueryBuilder       $queryBuilder A query builder instance
181
     * @param PaginationCriteria $criteria     Pagination criteria
182
     *
183
     * @return QueryBuilder
184
     */
185
    private function applyPaginationHints(QueryBuilder $queryBuilder, PaginationCriteria $criteria): QueryBuilder
186
    {
187
        $paginationHints = $criteria->getHints();
188
189
        // Hint: category.
190
        if (array_key_exists(self::PAGINATION_HINT_CATEGORY, $paginationHints)) {
191
            /** @var Category $category */
192
            $category = $paginationHints[self::PAGINATION_HINT_CATEGORY];
193
194
            $queryBuilder
195
                ->andWhere($queryBuilder->expr()->isMemberOf(':category', 'e.categories'))
196
                ->setParameter('category', $category)
197
            ;
198
        }
199
200
        // Hint: sync date before.
201
        if (array_key_exists(self::PAGINATION_HINT_SYNC_DATE_BEFORE, $paginationHints)) {
202
            $syncDateUpperBound = $paginationHints[self::PAGINATION_HINT_SYNC_DATE_BEFORE];
203
204
            $queryBuilder
205
                ->andWhere($queryBuilder->expr()->lt('e.synchronizationDate', ':syncDateUpperBound'))
206
                ->setParameter('syncDateUpperBound', $syncDateUpperBound)
207
            ;
208
        }
209
210
        // Hint: sync date after.
211
        if (array_key_exists(self::PAGINATION_HINT_SYNC_DATE_AFTER, $paginationHints)) {
212
            $syncDateLowerBound = $paginationHints[self::PAGINATION_HINT_SYNC_DATE_AFTER];
213
214
            $queryBuilder
215
                ->andWhere($queryBuilder->expr()->gte('e.synchronizationDate', ':syncDateLowerBound'))
216
                ->setParameter('syncDateLowerBound', $syncDateLowerBound)
217
            ;
218
        }
219
220
        return $queryBuilder;
221
    }
222
}
223