• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 12282713649

11 Dec 2024 06:34PM UTC coverage: 84.432% (+0.007%) from 84.425%
12282713649

Pull #9302

github

web-flow
Merge c866e750c into 0b0126cf8
Pull Request #9302: refactor: fix `phpstan` only boolean allowed

108 of 115 new or added lines in 35 files covered. (93.91%)

1 existing line in 1 file now uncovered.

20420 of 24185 relevant lines covered (84.43%)

189.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.5
/system/Test/FilterTestTrait.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Test;
15

16
use Closure;
17
use CodeIgniter\Filters\Exceptions\FilterException;
18
use CodeIgniter\Filters\FilterInterface;
19
use CodeIgniter\Filters\Filters;
20
use CodeIgniter\HTTP\RequestInterface;
21
use CodeIgniter\HTTP\ResponseInterface;
22
use CodeIgniter\Router\RouteCollection;
23
use Config\Filters as FiltersConfig;
24
use InvalidArgumentException;
25
use RuntimeException;
26

27
/**
28
 * Filter Test Trait
29
 *
30
 * Provides functionality for testing
31
 * filters and their route associations.
32
 *
33
 * @mixin CIUnitTestCase
34
 */
35
trait FilterTestTrait
36
{
37
    /**
38
     * Have the one-time classes been instantiated?
39
     *
40
     * @var bool
41
     */
42
    private $doneFilterSetUp = false;
43

44
    /**
45
     * The active IncomingRequest or CLIRequest
46
     *
47
     * @var RequestInterface
48
     */
49
    protected $request;
50

51
    /**
52
     * The active Response instance
53
     *
54
     * @var ResponseInterface
55
     */
56
    protected $response;
57

58
    /**
59
     * The Filters configuration to use.
60
     * Extracted for access to aliases
61
     * during Filters::discoverFilters().
62
     *
63
     * @var FiltersConfig|null
64
     */
65
    protected $filtersConfig;
66

67
    /**
68
     * The prepared Filters library.
69
     *
70
     * @var Filters|null
71
     */
72
    protected $filters;
73

74
    /**
75
     * The default App and discovered
76
     * routes to check for filters.
77
     *
78
     * @var RouteCollection|null
79
     */
80
    protected $collection;
81

82
    // --------------------------------------------------------------------
83
    // Staging
84
    // --------------------------------------------------------------------
85

86
    /**
87
     * Initializes dependencies once.
88
     */
89
    protected function setUpFilterTestTrait(): void
90
    {
91
        if ($this->doneFilterSetUp === true) {
12✔
92
            return;
×
93
        }
94

95
        // Create our own Request and Response so we can
96
        // use the same ones for Filters and FilterInterface
97
        // yet isolate them from outside influence
98
        $this->request ??= clone service('request');
12✔
99
        $this->response ??= clone service('response');
12✔
100

101
        // Create our config and Filters instance to reuse for performance
102
        $this->filtersConfig ??= config(FiltersConfig::class);
12✔
103
        $this->filters ??= new Filters($this->filtersConfig, $this->request, $this->response);
12✔
104

105
        if ($this->collection === null) {
12✔
106
            $this->collection = service('routes')->loadRoutes();
12✔
107
        }
108

109
        $this->doneFilterSetUp = true;
12✔
110
    }
111

112
    // --------------------------------------------------------------------
113
    // Utility
114
    // --------------------------------------------------------------------
115

116
    /**
117
     * Returns a callable method for a filter position
118
     * using the local HTTP instances.
119
     *
120
     * @param FilterInterface|string $filter   The filter instance, class, or alias
121
     * @param string                 $position "before" or "after"
122
     *
123
     * @phpstan-return Closure(list<string>|null=): mixed
124
     */
125
    protected function getFilterCaller($filter, string $position): Closure
126
    {
127
        if (! in_array($position, ['before', 'after'], true)) {
6✔
128
            throw new InvalidArgumentException('Invalid filter position passed: ' . $position);
1✔
129
        }
130

131
        if ($filter instanceof FilterInterface) {
5✔
132
            $filterInstances = [$filter];
1✔
133
        }
134

135
        if (is_string($filter)) {
5✔
136
            // Check for an alias (no namespace)
137
            if (! str_contains($filter, '\\')) {
4✔
138
                if (! isset($this->filtersConfig->aliases[$filter])) {
3✔
139
                    throw new RuntimeException("No filter found with alias '{$filter}'");
×
140
                }
141

142
                $filterClasses = (array) $this->filtersConfig->aliases[$filter];
3✔
143
            } else {
144
                // FQCN
145
                $filterClasses = [$filter];
1✔
146
            }
147

148
            $filterInstances = [];
4✔
149

150
            foreach ($filterClasses as $class) {
4✔
151
                // Get an instance
152
                $filter = new $class();
4✔
153

154
                if (! $filter instanceof FilterInterface) {
4✔
155
                    throw FilterException::forIncorrectInterface($filter::class);
×
156
                }
157

158
                $filterInstances[] = $filter;
4✔
159
            }
160
        }
161

162
        $request = clone $this->request;
5✔
163

164
        if ($position === 'before') {
5✔
165
            return static function (?array $params = null) use ($filterInstances, $request) {
5✔
166
                foreach ($filterInstances as $filter) {
4✔
167
                    $result = $filter->before($request, $params);
4✔
168

169
                    // @TODO The following logic is in Filters class.
170
                    //       Should use Filters class.
171
                    if ($result instanceof RequestInterface) {
4✔
172
                        $request = $result;
4✔
173

174
                        continue;
4✔
175
                    }
176
                    if ($result instanceof ResponseInterface) {
×
177
                        return $result;
×
178
                    }
179
                    if (empty($result)) {
×
180
                        continue;
×
181
                    }
182
                }
183

184
                return $result;
4✔
185
            };
5✔
186
        }
187

188
        $response = clone $this->response;
×
189

190
        return static function (?array $params = null) use ($filterInstances, $request, $response) {
×
191
            foreach ($filterInstances as $filter) {
×
192
                $result = $filter->after($request, $response, $params);
×
193

194
                // @TODO The following logic is in Filters class.
195
                //       Should use Filters class.
196
                if ($result instanceof ResponseInterface) {
×
197
                    $response = $result;
×
198

199
                    continue;
×
200
                }
201
            }
202

203
            return $result;
×
204
        };
×
205
    }
206

207
    /**
208
     * Gets an array of filter aliases enabled
209
     * for the given route at position.
210
     *
211
     * @param string $route    The route to test
212
     * @param string $position "before" or "after"
213
     *
214
     * @return list<string> The filter aliases
215
     */
216
    protected function getFiltersForRoute(string $route, string $position): array
217
    {
218
        if (! in_array($position, ['before', 'after'], true)) {
5✔
219
            throw new InvalidArgumentException('Invalid filter position passed:' . $position);
×
220
        }
221

222
        $this->filters->reset();
5✔
223

224
        $routeFilters = $this->collection->getFiltersForRoute($route);
5✔
225

226
        if ($routeFilters !== []) {
5✔
UNCOV
227
            $this->filters->enableFilters($routeFilters, $position);
×
228
        }
229

230
        $aliases = $this->filters->initialize($route)->getFilters();
5✔
231

232
        $this->filters->reset();
5✔
233

234
        return $aliases[$position];
5✔
235
    }
236

237
    // --------------------------------------------------------------------
238
    // Assertions
239
    // --------------------------------------------------------------------
240

241
    /**
242
     * Asserts that the given route at position uses
243
     * the filter (by its alias).
244
     *
245
     * @param string $route    The route to test
246
     * @param string $position "before" or "after"
247
     * @param string $alias    Alias for the anticipated filter
248
     */
249
    protected function assertFilter(string $route, string $position, string $alias): void
250
    {
251
        $filters = $this->getFiltersForRoute($route, $position);
1✔
252

253
        $this->assertContains(
1✔
254
            $alias,
1✔
255
            $filters,
1✔
256
            "Filter '{$alias}' does not apply to '{$route}'.",
1✔
257
        );
1✔
258
    }
259

260
    /**
261
     * Asserts that the given route at position does not
262
     * use the filter (by its alias).
263
     *
264
     * @param string $route    The route to test
265
     * @param string $position "before" or "after"
266
     * @param string $alias    Alias for the anticipated filter
267
     */
268
    protected function assertNotFilter(string $route, string $position, string $alias)
269
    {
270
        $filters = $this->getFiltersForRoute($route, $position);
1✔
271

272
        $this->assertNotContains(
1✔
273
            $alias,
1✔
274
            $filters,
1✔
275
            "Filter '{$alias}' applies to '{$route}' when it should not.",
1✔
276
        );
1✔
277
    }
278

279
    /**
280
     * Asserts that the given route at position has
281
     * at least one filter set.
282
     *
283
     * @param string $route    The route to test
284
     * @param string $position "before" or "after"
285
     */
286
    protected function assertHasFilters(string $route, string $position)
287
    {
288
        $filters = $this->getFiltersForRoute($route, $position);
1✔
289

290
        $this->assertNotEmpty(
1✔
291
            $filters,
1✔
292
            "No filters found for '{$route}' when at least one was expected.",
1✔
293
        );
1✔
294
    }
295

296
    /**
297
     * Asserts that the given route at position has
298
     * no filters set.
299
     *
300
     * @param string $route    The route to test
301
     * @param string $position "before" or "after"
302
     */
303
    protected function assertNotHasFilters(string $route, string $position)
304
    {
305
        $filters = $this->getFiltersForRoute($route, $position);
1✔
306

307
        $this->assertSame(
1✔
308
            [],
1✔
309
            $filters,
1✔
310
            "Found filters for '{$route}' when none were expected: " . implode(', ', $filters) . '.',
1✔
311
        );
1✔
312
    }
313
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc