Passed
Push — master ( a57d9e...3a516f )
by Joschi
02:19
created

CalcLengthFactory::createViewportUnitFromTokens()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 3
dl 0
loc 9
ccs 0
cts 4
cp 0
crap 2
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * responsive-images-css
5
 *
6
 * @category   Jkphl
7
 * @package    Jkphl\Respimgcss
8
 * @subpackage Jkphl\Respimgcss\Application\Factory
9
 * @author     Joschi Kuphal <[email protected]> / @jkphl
10
 * @copyright  Copyright © 2018 Joschi Kuphal <[email protected]> / @jkphl
11
 * @license    http://opensource.org/licenses/MIT The MIT License (MIT)
12
 */
13
14
/***********************************************************************************
15
 *  The MIT License (MIT)
16
 *
17
 *  Copyright © 2018 Joschi Kuphal <[email protected]> / @jkphl
18
 *
19
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of
20
 *  this software and associated documentation files (the "Software"), to deal in
21
 *  the Software without restriction, including without limitation the rights to
22
 *  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
23
 *  the Software, and to permit persons to whom the Software is furnished to do so,
24
 *  subject to the following conditions:
25
 *
26
 *  The above copyright notice and this permission notice shall be included in all
27
 *  copies or substantial portions of the Software.
28
 *
29
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
31
 *  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
32
 *  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
33
 *  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
34
 *  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
 ***********************************************************************************/
36
37
namespace Jkphl\Respimgcss\Application\Factory;
38
39
use ChrisKonnertz\StringCalc\Parser\Nodes\ContainerNode;
40
use ChrisKonnertz\StringCalc\Tokenizer\Token;
41
use Jkphl\Respimgcss\Application\Contract\UnitLengthInterface;
42
use Jkphl\Respimgcss\Application\Exceptions\InvalidArgumentException;
43
use Jkphl\Respimgcss\Application\Model\AbsoluteLength;
44
use Jkphl\Respimgcss\Application\Model\StringCalculator;
45
use Jkphl\Respimgcss\Application\Model\ViewportLength;
46
use Jkphl\Respimgcss\Application\Service\LengthNormalizerService;
47
48
/**
49
 * AbstractLength factory for calc() based values
50
 *
51
 * @package    Jkphl\Respimgcss
52
 * @subpackage Jkphl\Respimgcss\Application\Factory
53
 */
54
class CalcLengthFactory
55
{
56
    /**
57
     * Create a unit length from a calc() size string
58
     *
59
     * @param string $calcString calc() size string
60
     * @param int $emPixel       EM to pixel ratio
61
     *
62
     * @return UnitLengthInterface
63
     * @throws \ChrisKonnertz\StringCalc\Exceptions\ContainerException
64
     * @throws \ChrisKonnertz\StringCalc\Exceptions\InvalidIdentifierException
65
     * @throws \ChrisKonnertz\StringCalc\Exceptions\NotFoundException
66
     */
67
    public static function createFromString(string $calcString, int $emPixel = 16): UnitLengthInterface
68
    {
69
        // If the calc() string is ill-formatted
70
        if (!preg_match('/^calc\(.+\)$/', $calcString)) {
71
            throw new InvalidArgumentException(
72
                sprintf(InvalidArgumentException::ILL_FORMATTED_CALC_LENGTH_STRING_STR, $calcString),
73
                InvalidArgumentException::ILL_FORMATTED_CALC_LENGTH_STRING
74
            );
75
        }
76
77
        return self::createCalculationContainerFromString($calcString, $emPixel);
78
    }
79
80
    /**
81
     * Parse a calculation string and return a precompiled calculation node container
82
     *
83
     * @param string $calcString Calculation string
84
     * @param int $emPixel       EM to pixel ratio
85
     *
86
     * @return ContainerNode Calculation node container
87
     * @throws \ChrisKonnertz\StringCalc\Exceptions\ContainerException
88
     * @throws \ChrisKonnertz\StringCalc\Exceptions\InvalidIdentifierException
89
     * @throws \ChrisKonnertz\StringCalc\Exceptions\NotFoundException
90
     */
91
    protected static function createCalculationContainerFromString(
92
        string $calcString,
93
        int $emPixel = 16
94
    ): UnitLengthInterface {
95
        $stringCalc    = new StringCalculator();
96
        $calcTokens    = $stringCalc->tokenize($calcString);
97
        $refinedTokens = self::refineCalculationTokens($calcTokens, $emPixel);
98
99
        // If there's the viewport involved in the calculation: Create a relative calculated length
100
        /** @var Token $token */
101
        foreach ($refinedTokens as $token) {
102
            if (($token->getType() == Token::TYPE_WORD) && ($token->getValue() === 'viewport')) {
103
                return new ViewportLength(
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Jkphl\Respimg...$emPixel), $calcString) returns the type Jkphl\Respimgcss\Application\Model\ViewportLength which is incompatible with the documented return type ChrisKonnertz\StringCalc...ser\Nodes\ContainerNode.
Loading history...
104
                    $stringCalc->parse($refinedTokens),
105
                    new LengthNormalizerService($emPixel),
106
                    $calcString
107
                );
108
            }
109
        }
110
111
        // Create and return an absolute length
112
        return new AbsoluteLength(
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Jkphl\Respimg...lizerService($emPixel)) returns the type Jkphl\Respimgcss\Application\Model\AbsoluteLength which is incompatible with the documented return type ChrisKonnertz\StringCalc...ser\Nodes\ContainerNode.
Loading history...
113
            $stringCalc->calculate($refinedTokens),
0 ignored issues
show
Bug introduced by
$refinedTokens of type array is incompatible with the type string expected by parameter $term of ChrisKonnertz\StringCalc\StringCalc::calculate(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

113
            $stringCalc->calculate(/** @scrutinizer ignore-type */ $refinedTokens),
Loading history...
114
            UnitLengthInterface::UNIT_PIXEL,
115
            new LengthNormalizerService($emPixel)
116
        );
117
    }
118
119
    /**
120
     * Refine a list of symbol tokens
121
     *
122
     * @param Token[] $tokens Symbol tokens
123
     * @param int $emPixel    EM to pixel ratio
124
     *
125
     * @return Token[] Refined symbol tokens
126
     */
127
    protected static function refineCalculationTokens(array $tokens, int $emPixel = 16): array
128
    {
129
        $refinedTokens = [];
130
        $previousToken = null;
131
132
        // Run through all tokens
133
        foreach ($tokens as $token) {
134
            $previousToken = self::handleToken($refinedTokens, $token, $previousToken, $emPixel);
135
        }
136
137
        // Add the last token
138
        if ($previousToken) {
139
            array_push($refinedTokens, $previousToken);
140
        }
141
142
        return $refinedTokens;
143
    }
144
145
    /**
146
     * Handle a particular token
147
     *
148
     * @param Token[] $refinedTokens    Refined tokens
149
     * @param Token $token              Token
150
     * @param Token|null $previousToken Previous token
151
     * @param int $emPixel              EM to pixel ratio
152
     *
153
     * @return Token|null               Stash token
154
     */
155
    protected static function handleToken(
156
        array &$refinedTokens,
157
        Token $token,
158
        Token $previousToken = null,
159
        int $emPixel = 16
160
    ): ?Token {
161
        // If it's a word token: Handle individually
162
        if ($token->getType() == Token::TYPE_WORD) {
163
            return self::handleWordToken($refinedTokens, $token, $previousToken, $emPixel);
164
        }
165
166
        // In all other cases: Register the previou token (if any)
167
        if ($previousToken) {
168
            array_push($refinedTokens, $previousToken);
169
        }
170
171
        // If it's a number token: Stash
172
        if ($token->getType() == Token::TYPE_NUMBER) {
173
            return $token;
174
        }
175
176
        array_push($refinedTokens, $token);
177
178
        return null;
179
    }
180
181
    /**
182
     * Handle a particular token
183
     *
184
     * The method returns a list of zero or more (possibly refined) tokens to preerve
185
     *
186
     * @param Token[] $refinedTokens    Refined tokens
187
     * @param Token $token              Token
188
     * @param Token|null $previousToken Previous token
189
     * @param int $emPixel              EM to pixel ratio
190
     *
191
     * @return Token|null               Stash token
192
     * @throws InvalidArgumentException If the word token is invalid
193
     */
194
    protected static function handleWordToken(
195
        array &$refinedTokens,
196
        Token $token,
197
        Token $previousToken = null,
198
        int $emPixel = 16
199
    ): ?Token {
200
        // If it's a calc() function call: Add the previous token and skip the current one
201
        if ($token->getValue() == 'calc') {
202
            if ($previousToken) {
203
                array_push($refinedTokens, $previousToken);
204
            }
205
206
            return null;
207
        }
208
209
        // If the previous token is a number: Try to generate a unit length
210
        if ($previousToken && ($previousToken->getType() == Token::TYPE_NUMBER)) {
211
            try {
212
                $unitLength = LengthFactory::createLengthFromString(
213
                    $previousToken->getValue().$token->getValue(),
214
                    $emPixel
215
                );
216
                self::handleUnitLengthToken($refinedTokens, $unitLength);
217
218
                return null;
219
            } catch (InvalidArgumentException $e) {
220
                // Ignore
221
            }
222
        }
223
224
        // Invalid word token
225
        throw new InvalidArgumentException(
226
            sprintf(InvalidArgumentException::INVALID_WORD_TOKEN_IN_SOURCE_SIZE_VALUE_STR, $token->getValue()),
227
            InvalidArgumentException::INVALID_WORD_TOKEN_IN_SOURCE_SIZE_VALUE
228
        );
229
    }
230
231
    /**
232
     * Handle a unit length token
233
     *
234
     * @param Token[] $refinedTokens          Refined tokens
235
     * @param UnitLengthInterface $unitLength Unit length
236
     */
237
    protected static function handleUnitLengthToken(
238
        array &$refinedTokens,
239
        UnitLengthInterface $unitLength
240
    ): void {
241
        // If it's an absolute value
242
        if ($unitLength->isAbsolute()) {
243
            array_push($refinedTokens, new Token(strval($unitLength->getValue()), Token::TYPE_NUMBER, 0));
0 ignored issues
show
Bug introduced by
The call to Jkphl\Respimgcss\Domain\...thInterface::getValue() has too few arguments starting with viewport. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

243
            array_push($refinedTokens, new Token(strval($unitLength->/** @scrutinizer ignore-call */ getValue()), Token::TYPE_NUMBER, 0));

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
244
245
            return;
246
        }
247
248
        // Else: Substitute with multiplied function expression
249
        array_push(
250
            $refinedTokens,
251
            new Token('(', Token::TYPE_CHARACTER, 0),
252
            new Token(strval($unitLength->getValue() / 100), Token::TYPE_NUMBER, 0),
253
            new Token('*', Token::TYPE_CHARACTER, 0),
254
            new Token('viewport', Token::TYPE_WORD, 0),
255
            new Token('(', Token::TYPE_CHARACTER, 0),
256
            new Token(')', Token::TYPE_CHARACTER, 0),
257
            new Token(')', Token::TYPE_CHARACTER, 0)
258
        );
259
    }
260
}