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

codeigniter4 / CodeIgniter4 / 11878724576

17 Nov 2024 12:12PM UTC coverage: 84.428% (+0.001%) from 84.427%
11878724576

push

github

web-flow
fix: code issues after merging develop (#9284)

* cs-fix

* fix services call

* apply rector rules

3 of 3 new or added lines in 2 files covered. (100.0%)

361 existing lines in 20 files now uncovered.

20597 of 24396 relevant lines covered (84.43%)

189.64 hits per line

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

97.94
/system/View/Cell.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\View;
15

16
use CodeIgniter\Cache\CacheInterface;
17
use CodeIgniter\Config\Factories;
18
use CodeIgniter\View\Cells\Cell as BaseCell;
19
use CodeIgniter\View\Exceptions\ViewException;
20
use ReflectionException;
21
use ReflectionMethod;
22

23
/**
24
 * Class Cell
25
 *
26
 * A simple class that can call any other class that can be loaded,
27
 * and echo out it's result. Intended for displaying small blocks of
28
 * content within views that can be managed by other libraries and
29
 * not require they are loaded within controller.
30
 *
31
 * Used with the helper function, it's use will look like:
32
 *
33
 *         viewCell('\Some\Class::method', 'limit=5 sort=asc', 60, 'cache-name');
34
 *
35
 * Parameters are matched up with the callback method's arguments of the same name:
36
 *
37
 *         class Class {
38
 *             function method($limit, $sort)
39
 *         }
40
 *
41
 * Alternatively, the params will be passed into the callback method as a simple array
42
 * if matching params are not found.
43
 *
44
 *         class Class {
45
 *             function method(array $params=null)
46
 *         }
47
 *
48
 * @see \CodeIgniter\View\CellTest
49
 */
50
class Cell
51
{
52
    /**
53
     * Instance of the current Cache Instance
54
     *
55
     * @var CacheInterface
56
     */
57
    protected $cache;
58

59
    /**
60
     * Cell constructor.
61
     */
62
    public function __construct(CacheInterface $cache)
63
    {
64
        $this->cache = $cache;
31✔
65
    }
66

67
    /**
68
     * Render a cell, returning its body as a string.
69
     *
70
     * @param string                            $library   Cell class and method name.
71
     * @param array<string, string>|string|null $params    Parameters to pass to the method.
72
     * @param int                               $ttl       Number of seconds to cache the cell.
73
     * @param string|null                       $cacheName Cache item name.
74
     *
75
     * @throws ReflectionException
76
     */
77
    public function render(string $library, $params = null, int $ttl = 0, ?string $cacheName = null): string
78
    {
79
        [$instance, $method] = $this->determineClass($library);
35✔
80

81
        $class = is_object($instance)
33✔
82
            ? $instance::class
33✔
UNCOV
83
            : null;
×
84

85
        $params = $this->prepareParams($params);
33✔
86

87
        // Is the output cached?
88
        $cacheName ??= str_replace(['\\', '/'], '', $class) . $method . md5(serialize($params));
33✔
89

90
        if ($output = $this->cache->get($cacheName)) {
33✔
91
            return $output;
1✔
92
        }
93

94
        if (method_exists($instance, 'initController')) {
33✔
95
            $instance->initController(service('request'), service('response'), service('logger'));
1✔
96
        }
97

98
        if (! method_exists($instance, $method)) {
33✔
99
            throw ViewException::forInvalidCellMethod($class, $method);
2✔
100
        }
101

102
        $output = $instance instanceof BaseCell
31✔
103
            ? $this->renderCell($instance, $method, $params)
16✔
104
            : $this->renderSimpleClass($instance, $method, $params, $class);
15✔
105

106
        // Can we cache it?
107
        if ($ttl !== 0) {
28✔
108
            $this->cache->save($cacheName, $output, $ttl);
2✔
109
        }
110

111
        return $output;
28✔
112
    }
113

114
    /**
115
     * Parses the params attribute. If an array, returns untouched.
116
     * If a string, it should be in the format "key1=value key2=value".
117
     * It will be split and returned as an array.
118
     *
119
     * @param         array<string, string>|string|null       $params
120
     * @phpstan-param array<string, string>|string|float|null $params
121
     *
122
     * @return array<string, string>
123
     */
124
    public function prepareParams($params)
125
    {
126
        if (
127
            ($params === null || $params === '' || $params === [])
41✔
128
            || (! is_string($params) && ! is_array($params))
41✔
129
        ) {
130
            return [];
18✔
131
        }
132

133
        if (is_string($params)) {
24✔
134
            $newParams = [];
15✔
135
            $separator = ' ';
15✔
136

137
            if (str_contains($params, ',')) {
15✔
138
                $separator = ',';
14✔
139
            }
140

141
            $params = explode($separator, $params);
15✔
142
            unset($separator);
15✔
143

144
            foreach ($params as $p) {
15✔
145
                if ($p !== '') {
15✔
146
                    [$key, $val] = explode('=', $p);
14✔
147

148
                    $newParams[trim($key)] = trim($val, ', ');
14✔
149
                }
150
            }
151

152
            $params = $newParams;
15✔
153
            unset($newParams);
15✔
154
        }
155

156
        if ($params === []) {
24✔
157
            return [];
1✔
158
        }
159

160
        return $params;
23✔
161
    }
162

163
    /**
164
     * Given the library string, attempts to determine the class and method
165
     * to call.
166
     */
167
    protected function determineClass(string $library): array
168
    {
169
        // We don't want to actually call static methods
170
        // by default, so convert any double colons.
171
        $library = str_replace('::', ':', $library);
35✔
172

173
        // controlled cells might be called with just
174
        // the class name, so add a default method
175
        if (! str_contains($library, ':')) {
35✔
176
            $library .= ':render';
15✔
177
        }
178

179
        [$class, $method] = explode(':', $library);
35✔
180

181
        if ($class === '') {
35✔
182
            throw ViewException::forNoCellClass();
1✔
183
        }
184

185
        // locate and return an instance of the cell
186
        $object = Factories::cells($class, ['getShared' => false]);
34✔
187

188
        if (! is_object($object)) {
34✔
189
            throw ViewException::forInvalidCellClass($class);
1✔
190
        }
191

192
        if ($method === '') {
33✔
193
            $method = 'index';
1✔
194
        }
195

196
        return [
33✔
197
            $object,
33✔
198
            $method,
33✔
199
        ];
33✔
200
    }
201

202
    /**
203
     * Renders a cell that extends the BaseCell class.
204
     */
205
    final protected function renderCell(BaseCell $instance, string $method, array $params): string
206
    {
207
        // Only allow public properties to be set, or protected/private
208
        // properties that have a method to get them (get<Foo>Property())
209
        $publicProperties  = $instance->getPublicProperties();
16✔
210
        $privateProperties = array_column($instance->getNonPublicProperties(), 'name');
16✔
211
        $publicParams      = array_intersect_key($params, $publicProperties);
16✔
212

213
        foreach ($params as $key => $value) {
16✔
214
            $getter = 'get' . ucfirst((string) $key) . 'Property';
8✔
215
            if (in_array($key, $privateProperties, true) && method_exists($instance, $getter)) {
8✔
216
                $publicParams[$key] = $value;
1✔
217
            }
218
        }
219

220
        // Fill in any public properties that were passed in
221
        // but only ones that are in the $pulibcProperties array.
222
        $instance = $instance->fill($publicParams);
16✔
223

224
        // If there are any protected/private properties, we need to
225
        // send them to the mount() method.
226
        if (method_exists($instance, 'mount')) {
16✔
227
            // if any $params have keys that match the name of an argument in the
228
            // mount method, pass those variables to the method.
229
            $mountParams = $this->getMethodParams($instance, 'mount', $params);
6✔
230
            $instance->mount(...$mountParams);
6✔
231
        }
232

233
        return $instance->{$method}();
16✔
234
    }
235

236
    /**
237
     * Returns the values from $params that match the parameters
238
     * for a method, in the order they are defined. This allows
239
     * them to be passed directly into the method.
240
     */
241
    private function getMethodParams(BaseCell $instance, string $method, array $params): array
242
    {
243
        $mountParams = [];
6✔
244

245
        try {
246
            $reflectionMethod = new ReflectionMethod($instance, $method);
6✔
247
            $reflectionParams = $reflectionMethod->getParameters();
6✔
248

249
            foreach ($reflectionParams as $reflectionParam) {
6✔
250
                $paramName = $reflectionParam->getName();
4✔
251

252
                if (array_key_exists($paramName, $params)) {
4✔
253
                    $mountParams[] = $params[$paramName];
1✔
254
                }
255
            }
UNCOV
256
        } catch (ReflectionException) {
×
257
            // do nothing
258
        }
259

260
        return $mountParams;
6✔
261
    }
262

263
    /**
264
     * Renders the non-Cell class, passing in the string/array params.
265
     *
266
     * @todo Determine if this can be refactored to use $this-getMethodParams().
267
     *
268
     * @param object $instance
269
     */
270
    final protected function renderSimpleClass($instance, string $method, array $params, string $class): string
271
    {
272
        // Try to match up the parameter list we were provided
273
        // with the parameter name in the callback method.
274
        $refMethod  = new ReflectionMethod($instance, $method);
15✔
275
        $paramCount = $refMethod->getNumberOfParameters();
15✔
276
        $refParams  = $refMethod->getParameters();
15✔
277

278
        if ($paramCount === 0) {
15✔
279
            if ($params !== []) {
6✔
280
                throw ViewException::forMissingCellParameters($class, $method);
1✔
281
            }
282

283
            $output = $instance->{$method}();
5✔
284
        } elseif (($paramCount === 1)
9✔
285
            && ((! array_key_exists($refParams[0]->name, $params))
9✔
286
            || (array_key_exists($refParams[0]->name, $params)
9✔
287
            && count($params) !== 1))
9✔
288
        ) {
289
            $output = $instance->{$method}($params);
7✔
290
        } else {
291
            $fireArgs     = [];
2✔
292
            $methodParams = [];
2✔
293

294
            foreach ($refParams as $arg) {
2✔
295
                $methodParams[$arg->name] = true;
2✔
296
                if (array_key_exists($arg->name, $params)) {
2✔
297
                    $fireArgs[$arg->name] = $params[$arg->name];
2✔
298
                }
299
            }
300

301
            foreach (array_keys($params) as $key) {
2✔
302
                if (! isset($methodParams[$key])) {
2✔
303
                    throw ViewException::forInvalidCellParameter($key);
1✔
304
                }
305
            }
306

307
            $output = $instance->{$method}(...array_values($fireArgs));
1✔
308
        }
309

310
        return $output;
13✔
311
    }
312
}
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