Completed
Push — 2.0 ( d6b5ff...33bb83 )
by Peter
08:35 queued 11s
created

Like   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 135
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 16
lcom 1
cbo 4
dl 0
loc 135
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A getFilter() 0 13 2
A filterCollection() 0 12 3
A isSatisfiedBy() 0 8 1
A resolveContext() 0 12 4
A getUnescapedValue() 0 5 1
A isMatch() 0 13 3
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * This file is part of the Happyr Doctrine Specification package.
6
 *
7
 * (c) Tobias Nyholm <[email protected]>
8
 *     Kacper Gunia <[email protected]>
9
 *     Peter Gribanov <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Happyr\DoctrineSpecification\Filter;
16
17
use Doctrine\ORM\Query\Expr\Comparison as DoctrineComparison;
18
use Doctrine\ORM\QueryBuilder;
19
use Happyr\DoctrineSpecification\Operand\ArgumentToOperandConverter;
20
use Happyr\DoctrineSpecification\Operand\LikePattern;
21
use Happyr\DoctrineSpecification\Operand\Operand;
22
23
final class Like implements Filter, Satisfiable
24
{
25
    public const CONTAINS = LikePattern::CONTAINS;
26
27
    public const ENDS_WITH = LikePattern::ENDS_WITH;
28
29
    public const STARTS_WITH = LikePattern::STARTS_WITH;
30
31
    /**
32
     * @var Operand|string
33
     */
34
    private $field;
35
36
    /**
37
     * @var LikePattern
38
     */
39
    private $value;
40
41
    /**
42
     * @var string|null
43
     */
44
    private $context;
45
46
    /**
47
     * @param Operand|string     $field
48
     * @param LikePattern|string $value
49
     * @param string             $format
50
     * @param string|null        $context
51
     */
52
    public function __construct($field, $value, string $format = LikePattern::CONTAINS, ?string $context = null)
53
    {
54
        if (!($value instanceof LikePattern)) {
55
            $value = new LikePattern($value, $format);
56
        }
57
58
        $this->field = $field;
59
        $this->value = $value;
60
        $this->context = $context;
61
    }
62
63
    /**
64
     * @param QueryBuilder $qb
65
     * @param string       $context
66
     *
67
     * @return string
68
     */
69
    public function getFilter(QueryBuilder $qb, string $context): string
70
    {
71
        if (null !== $this->context) {
72
            $context = sprintf('%s.%s', $context, $this->context);
73
        }
74
75
        $field = ArgumentToOperandConverter::toField($this->field);
76
77
        $field = $field->transform($qb, $context);
78
        $value = $this->value->transform($qb, $context);
79
80
        return (string) new DoctrineComparison($field, 'LIKE', $value);
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function filterCollection(iterable $collection, ?string $context = null): iterable
87
    {
88
        $context = $this->resolveContext($context);
89
        $field = ArgumentToOperandConverter::toField($this->field);
90
        $value = $this->getUnescapedValue();
91
92
        foreach ($collection as $candidate) {
93
            if ($this->isMatch($field->execute($candidate, $context), $value)) {
94
                yield $candidate;
95
            }
96
        }
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function isSatisfiedBy($candidate, ?string $context = null): bool
103
    {
104
        $context = $this->resolveContext($context);
105
        $field = ArgumentToOperandConverter::toField($this->field);
106
        $value = $this->getUnescapedValue();
107
108
        return $this->isMatch($field->execute($candidate, $context), $value);
0 ignored issues
show
Bug introduced by
It seems like $candidate defined by parameter $candidate on line 102 can also be of type array; however, Happyr\DoctrineSpecifica...rand\Operand::execute() does only seem to accept array<integer,*>|object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
109
    }
110
111
    /**
112
     * @param string|null $context
113
     *
114
     * @return string|null
115
     */
116
    private function resolveContext(?string $context): ?string
117
    {
118
        if (null !== $this->context && null !== $context) {
119
            return sprintf('%s.%s', $context, $this->context);
120
        }
121
122
        if (null !== $this->context) {
123
            return $this->context;
124
        }
125
126
        return $context;
127
    }
128
129
    /**
130
     * @return string
131
     */
132
    private function getUnescapedValue(): string
133
    {
134
        // remove escaping
135
        return str_replace('%%', '%', $this->value->getValue());
136
    }
137
138
    /**
139
     * @param string $haystack
140
     * @param string $needle
141
     *
142
     * @return bool
143
     */
144
    private function isMatch(string $haystack, string $needle): bool
145
    {
146
        switch ($this->value->getFormat()) {
147
            case LikePattern::STARTS_WITH:
148
                return str_starts_with($haystack, $needle);
149
150
            case LikePattern::ENDS_WITH:
151
                return str_ends_with($haystack, $needle);
152
153
            default:
154
                return str_contains($haystack, $needle);
155
        }
156
    }
157
}
158