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

codeigniter4 / CodeIgniter4 / 9366456936

04 Jun 2024 11:44AM UTC coverage: 84.448% (+0.006%) from 84.442%
9366456936

Pull #8908

github

web-flow
Merge 737312559 into 8c459cf12
Pull Request #8908: feat: Controller.php Add custom Config\Validation class

5 of 6 new or added lines in 1 file covered. (83.33%)

85 existing lines in 5 files now uncovered.

20357 of 24106 relevant lines covered (84.45%)

188.44 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 Config\Services;
21
use ReflectionException;
22
use ReflectionMethod;
23

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

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

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

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

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

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

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

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

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

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

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

112
        return $output;
28✔
113
    }
114

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

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

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

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

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

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

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

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

161
        return $params;
23✔
162
    }
163

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

261
        return $mountParams;
6✔
262
    }
263

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

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

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

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

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

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

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

© 2025 Coveralls, Inc