diff --git a/application/Config/Services.php b/application/Config/Services.php index 65df1940c124..844570b16b7d 100644 --- a/application/Config/Services.php +++ b/application/Config/Services.php @@ -70,6 +70,22 @@ public static function cache(\Config\Cache $config = null, $getShared = true) //-------------------------------------------------------------------- + /** + * View cells are intended to let you insert HTML into view + * that has been generated by any callable in the system. + */ + public static function viewcell($getShared = true) + { + if ($getShared) + { + return self::getSharedInstance('viewcell'); + } + + return new \CodeIgniter\View\Cell(self::cache()); + } + + //-------------------------------------------------------------------- + /** * The CLI Request class provides for ways to interact with * a command line request. diff --git a/system/Common.php b/system/Common.php index 6e3544588beb..572d935908f0 100644 --- a/system/Common.php +++ b/system/Common.php @@ -123,6 +123,27 @@ function view(string $name, array $data = [], array $options = []) //-------------------------------------------------------------------- +if (! function_exists('view_cell')) +{ + /** + * View cells are used within views to insert HTML chunks that are managed + * by other classes. + * + * @param string $library + * @param null $params + * @param int $ttl + * @param string|null $cacheName + * + * @return string + */ + function view_cell(string $library, $params = null, int $ttl = 0, string $cacheName = null) + { + return Services::viewcell()->render($library, $params, $ttl, $cacheName); + } +} + +//-------------------------------------------------------------------- + if ( ! function_exists('esc')) { /** diff --git a/system/View/Cell.php b/system/View/Cell.php index 8124306dc5c4..ec57e8dfaf63 100644 --- a/system/View/Cell.php +++ b/system/View/Cell.php @@ -140,42 +140,6 @@ public function render(string $library, $params = null, int $ttl = 0, string $ca //-------------------------------------------------------------------- - /** - * Given the library string, attempts to determine the class and method - * to call. - * - * @param string $library - * - * @return array - */ - protected function determineClass(string $library) - { - // We don't want to actually call static methods - // by default, so convert any double colons. - $library = str_replace('::', ':', $library); - - list($class, $method) = explode(':', $library); - - if (empty($class)) - { - throw new \InvalidArgumentException('No view cell class provided.'); - } - - if (! class_exists($class, true)) - { - throw new \InvalidArgumentException('Unable to locate view cell class: '.$class.'.'); - } - - if (empty($method)) - { - $method = 'index'; - } - - return [$class, $method]; - } - - //-------------------------------------------------------------------- - /** * Parses the params attribute. If an array, returns untouched. * If a string, it should be in the format "key1=value key2=value". @@ -226,4 +190,39 @@ public function prepareParams($params) //-------------------------------------------------------------------- + /** + * Given the library string, attempts to determine the class and method + * to call. + * + * @param string $library + * + * @return array + */ + protected function determineClass(string $library) + { + // We don't want to actually call static methods + // by default, so convert any double colons. + $library = str_replace('::', ':', $library); + + list($class, $method) = explode(':', $library); + + if (empty($class)) + { + throw new \InvalidArgumentException('No view cell class provided.'); + } + + if (! class_exists($class, true)) + { + throw new \InvalidArgumentException('Unable to locate view cell class: '.$class.'.'); + } + + if (empty($method)) + { + $method = 'index'; + } + + return [$class, $method]; + } + + //-------------------------------------------------------------------- } diff --git a/tests/_support/Cache/Handlers/MockHandler.php b/tests/_support/Cache/Handlers/MockHandler.php new file mode 100644 index 000000000000..886da6c07684 --- /dev/null +++ b/tests/_support/Cache/Handlers/MockHandler.php @@ -0,0 +1,197 @@ +prefix.$key; + + return isset($this->cache[$key]) + ? $this->cache[$key] + : false; + } + + //-------------------------------------------------------------------- + + /** + * Saves an item to the cache store. + * + * The $raw parameter is only utilized by Mamcache in order to + * allow usage of increment() and decrement(). + * + * @param string $key Cache item name + * @param $value the data to save + * @param null $ttl Time To Live, in seconds (default 60) + * @param bool $raw Whether to store the raw value. + * + * @return mixed + */ + public function save(string $key, $value, int $ttl = 60, bool $raw = false) + { + $key = $this->prefix.$key; + + $this->cache[$key] = $value; + + return true; + } + + //-------------------------------------------------------------------- + + /** + * Deletes a specific item from the cache store. + * + * @param string $key Cache item name + * + * @return mixed + */ + public function delete(string $key) + { + unset($this->cache[$key]); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic incrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function increment(string $key, int $offset = 1) + { + $key = $this->prefix.$key; + + $data = $this->cache[$key] ?: null; + + if (empty($data)) + { + $data = 0; + } + elseif (! is_int($data)) + { + return false; + } + + return $this->save($key, $data+$offset); + } + + //-------------------------------------------------------------------- + + /** + * Performs atomic decrementation of a raw stored value. + * + * @param string $key Cache ID + * @param int $offset Step/value to increase by + * + * @return mixed + */ + public function decrement(string $key, int $offset = 1) + { + $key = $this->prefix.$key; + + $data = $this->cache[$key] ?: null; + + if (empty($data)) + { + $data = 0; + } + elseif (! is_int($data)) + { + return false; + } + + return $this->save($key, $data-$offset); + } + + //-------------------------------------------------------------------- + + /** + * Will delete all items in the entire cache. + * + * @return mixed + */ + public function clean() + { + $this->cache = []; + } + + //-------------------------------------------------------------------- + + /** + * Returns information on the entire cache. + * + * The information returned and the structure of the data + * varies depending on the handler. + * + * @return mixed + */ + public function getCacheInfo() + { + return []; + } + + //-------------------------------------------------------------------- + + /** + * Returns detailed information about the specific item in the cache. + * + * @param string $key Cache item name. + * + * @return mixed + */ + public function getMetaData(string $key) + { + return false; + } + + //-------------------------------------------------------------------- + + /** + * Determines if the driver is supported on this system. + * + * @return boolean + */ + public function isSupported(): bool + { + return true; + } + + //-------------------------------------------------------------------- + +} \ No newline at end of file diff --git a/tests/system/View/CellTest.php b/tests/system/View/CellTest.php new file mode 100644 index 000000000000..f933bd5b30c3 --- /dev/null +++ b/tests/system/View/CellTest.php @@ -0,0 +1,129 @@ +cache = new MockHandler(); + $this->cell = new Cell($this->cache); + } + + //-------------------------------------------------------------------- + + public function testPrepareParamsReturnsNullWithInvalidParam() + { + $this->assertTrue(is_null($this->cell->prepareParams(1.023))); + } + + //-------------------------------------------------------------------- + + public function testPrepareParamsReturnsNullWithEmptyString() + { + $this->assertNull($this->cell->prepareParams('')); + } + + //-------------------------------------------------------------------- + + public function testPrepareParamsRetunsSelfWhenArray() + { + $object = ['one' => 'two', 'three' => 'four']; + + $this->assertEquals($object, $this->cell->prepareParams($object)); + } + + //-------------------------------------------------------------------- + + public function testPrepareParamsReturnsNullWithEmptyArray() + { + $this->assertNull($this->cell->prepareParams([])); + } + + //-------------------------------------------------------------------- + + public function testPrepareParamsReturnsArrayWithString() + { + $params = 'one=two three=four'; + $expected = ['one' => 'two', 'three' => 'four']; + + $this->assertEquals($expected, $this->cell->prepareParams($params)); + } + + //-------------------------------------------------------------------- + + public function testPrepareParamsHandlesCommas() + { + $params = 'one=2, three=4.15'; + $expected = ['one' => 2, 'three' => 4.15]; + + $this->assertEquals($expected, $this->cell->prepareParams($params)); + } + + //-------------------------------------------------------------------- + + public function testPrepareParamsWorksWithoutSpaces() + { + $params = 'one=two,three=four'; + $expected = ['one' => 'two', 'three' => 'four']; + + $this->assertEquals($expected, $this->cell->prepareParams($params)); + } + + //-------------------------------------------------------------------- + + public function testPrepareParamsWorksWithOddEqualsSpaces() + { + $params = 'one= two,three =four, five = six'; + $expected = ['one' => 'two', 'three' => 'four', 'five' => 'six']; + + $this->assertEquals($expected, $this->cell->prepareParams($params)); + } + + //-------------------------------------------------------------------- + + //-------------------------------------------------------------------- + // Render + //-------------------------------------------------------------------- + + public function testDisplayRendersWithNamespacedClass() + { + $expected = 'Hello'; + + $this->assertEquals($expected, $this->cell->render('\CodeIgniter\View\SampleClass::hello')); + } + + //-------------------------------------------------------------------- + + public function testDisplayRendersWithValidParamString() + { + $params = 'one=two,three=four'; + $expected = ['one' => 'two', 'three' => 'four']; + + $this->assertEquals(implode(',', $expected), $this->cell->render('\CodeIgniter\View\SampleClass::echobox', $params)); + } + + //-------------------------------------------------------------------- + + public function testDisplayRendersWithStaticMethods() + { + $params = 'one=two,three=four'; + $expected = ['one' => 'two', 'three' => 'four']; + + $this->assertEquals(implode(',', $expected), $this->cell->render('\CodeIgniter\View\SampleClass::staticEcho', $params)); + } + + //-------------------------------------------------------------------- +} diff --git a/tests/system/View/SampleClass.php b/tests/system/View/SampleClass.php new file mode 100644 index 000000000000..88dd413b6ead --- /dev/null +++ b/tests/system/View/SampleClass.php @@ -0,0 +1,43 @@ + +View Cells +========== + +View Cells allow you to insert HTML that is generated outside of your controller. It simply calls the specified +class and method, which must return valid HTML. This method could be in an callable method, found in any class +that the autoloader can locate. The only restriction is that the class can not have any constructor parameters. +This is intended to be used within views, and is a great aid to modularizing your code. +:: + + + +In this example, the class ``App\Libraries\Blog` is loaded, and the method ``recentPosts()`` is ran. That method +must return a string with the generated HTML. The method used can be either a static method or not. Either way works. + +Cell Parameters +--------------- + +You can further refine the call by passing a string with a list of parameters in the second parameter that are passed +to the method as an array of key/value pairs:: + + + + public function recentPosts(array $params=[]) + { + $posts = $this->blogModel->where('category', $params['category']) + ->orderBy('published_on', 'desc') + ->limit($params['limit']) + ->get(); + + return view('recentPosts', ['posts' => $posts]); + } + +Additionally, you can use parameter names that match the parameter variables in the method for better readability. +When you use it this way, all of the parameters must always be specified in the view cell call:: + + + + public function recentPosts(int $limit, string $category) + { + $posts = $this->blogModel->where('category', $category) + ->orderBy('published_on', 'desc') + ->limit($limit) + ->get(); + + return view('recentPosts', ['posts' => $posts]); + } + +Cell Caching +------------ + +You can cache the results of the view cell call by passing the number of seconds to cache the data for as the +third parameter. This will use the currently configured cache engine. +:: + + // Cache the view for 5 minutes + + +You can provide a custom name to use instead of the auto-generated one if you like, by passing the new name +as the fourth parameter.:: + + // Cache the view for 5 minutes + +