Compare commits

..
Author SHA1 Message Date
Simon Wisselink 12587abea1 Document missing inline implementation. Fixed #1152 2025-12-21 22:11:15 +01:00
13 changed files with 892 additions and 977 deletions
-2
View File
@@ -115,7 +115,5 @@ If you are a maintainer, you can publish the document using [mike](https://githu
mike deploy 5.x
```
Then, push the `gh-pages` branch.
## Attribution
This guide is based on the **contributing.md**. [Make your own](https://contributing.md/)!
-1
View File
@@ -1 +0,0 @@
- Support for Laravel Collections style object chaining for objects return from function calls implemented as modifiers [#1151](https://github.com/smarty-php/smarty/issues/1151)
+8 -2
View File
@@ -127,6 +127,8 @@ class File extends Base
&& (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api'))) < 1
) {
opcache_invalidate($_template->getCached()->filepath, true);
} elseif (function_exists('apc_compile_file')) {
apc_compile_file($_template->getCached()->filepath);
}
$cached = $_template->getCached();
$cached->timestamp = $cached->exists = is_file($cached->filepath);
@@ -221,8 +223,10 @@ class File extends Base
$_filepath = (string)$_file;
// directory ?
if ($_file->isDir()) {
// delete folder if empty
@rmdir($_file->getPathname());
if (!$_cache->isDot()) {
// delete folder if empty
@rmdir($_file->getPathname());
}
} else {
// delete only php files
if (substr($_filepath, -4) !== '.php') {
@@ -275,6 +279,8 @@ class File extends Base
&& (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1)
) {
opcache_invalidate($_filepath, true);
} elseif (function_exists('apc_delete_file')) {
apc_delete_file($_filepath);
}
}
}
File diff suppressed because it is too large Load Diff
+6 -16
View File
@@ -1062,22 +1062,12 @@ object(res) ::= varindexed(vi) objectchain(oc). {
}
}
// optional objectchain - empty
optobjectchain(res) ::= . {
res = '';
}
// optional objectchain - present
optobjectchain(res) ::= objectchain(oc). {
res = oc;
}
// single element
objectchain(res) ::= objectelement(oe). {
res = oe;
}
// chain of elements
// chain of elements
objectchain(res) ::= objectchain(oc) objectelement(oe). {
res = oc.oe;
}
@@ -1121,7 +1111,7 @@ objectelement(res)::= PTR method(f). {
//
// function
//
function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP optobjectchain(oc). {
function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP. {
if (f == 'isset') {
res = '(true';
@@ -1135,15 +1125,15 @@ function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP optobjectchain(oc). {
res .= ' && (' . $value . ' !== null)';
}
}
res .= ')' . oc;
res .= ')';
} elseif (f == 'empty') {
if (count(v) != 1) {
throw new CompilerException("Invalid number of arguments for empty. empty expects at exactly one parameter.");
}
if (is_array(v[0])) {
res = '( !' . v[0][0] . ' || empty(' . v[0][1] . '))' . oc;
res .= '( !' . v[0][0] . ' || empty(' . v[0][1] . '))';
} else {
res = 'false == ' . v[0] . oc;
res = 'false == ' . v[0];
}
} else {
$p = array();
@@ -1154,7 +1144,7 @@ function(res) ::= ns1(f) OPENP variablelist(v) CLOSEP optobjectchain(oc). {
$p[] = $value;
}
}
res = $this->compiler->compileModifierInExpression(f, $p) . oc;
res = $this->compiler->compileModifierInExpression(f, $p);
}
}
+6 -2
View File
@@ -1344,8 +1344,10 @@ class Smarty extends \Smarty\TemplateBase {
}
$_filepath = (string)$_file;
if ($_file->isDir()) {
// delete folder if empty
@rmdir($_file->getPathname());
if (!$_compile->isDot()) {
// delete folder if empty
@rmdir($_file->getPathname());
}
} else {
// delete only php files
if (substr($_filepath, -4) !== '.php') {
@@ -1383,6 +1385,8 @@ class Smarty extends \Smarty\TemplateBase {
&& (!function_exists('ini_get') || strlen(ini_get('opcache.restrict_api')) < 1)
) {
opcache_invalidate($_filepath, true);
} elseif (function_exists('apc_delete_file')) {
apc_delete_file($_filepath);
}
}
}
+2
View File
@@ -251,6 +251,8 @@ class Compiled extends GeneratedPhpFile {
&& (!function_exists('ini_get') || strlen(ini_get("opcache.restrict_api")) < 1)
) {
opcache_invalidate($this->filepath, true);
} elseif (function_exists('apc_compile_file')) {
apc_compile_file($this->filepath);
}
}
if (defined('HHVM_VERSION')) {
+4 -2
View File
@@ -291,8 +291,10 @@ KEY `name` (`name`)
}
// directory ?
if ($file->isDir()) {
// delete folder if empty
@rmdir($file->getPathname());
if (!$ri->isDot()) {
// delete folder if empty
@rmdir($file->getPathname());
}
} else {
unlink($file->getPathname());
}
@@ -0,0 +1,31 @@
<?php
/**
* Smarty PHPunit tests for cache resource Apc
*
* @author Uwe Tews
*/
include_once __DIR__ . '/../Memcache/CacheResourceCustomMemcacheTest.php';
include_once __DIR__ . '/../_shared/PHPunitplugins/cacheresource.apctest.php';
/**
* class for cache resource file tests
*
*
* @preserveGlobalState disabled
*
*/
class CacheResourceCustomApcTest extends CacheResourceCustomMemcacheTest
{
public function setUp(): void
{
if (!function_exists('apc_cache_info') || ini_get('apc.enable_cli')) {
$this->markTestSkipped('APC cache not available');
}
$this->setUpSmarty(__DIR__);
parent::setUp();
$this->smarty->setCachingType('apc');
$this->smarty->registerCacheResource('apc', new Smarty_CacheResource_Apctest());
}
}
@@ -0,0 +1,33 @@
<?php
use Smarty\Smarty;
use Smarty\Template;
use Smarty\Template\Cached;
require_once __DIR__ . '/../../../__shared/cacheresources/cacheresource.apc.php';
class Smarty_CacheResource_Apctest extends Smarty_CacheResource_Apc
{
public $lockTime = 0;
public function hasLock(Smarty $smarty, Cached $cached)
{
if ($this->lockTime) {
$this->lockTime--;
if (!$this->lockTime) {
$this->releaseLock($smarty, $cached);
}
}
return parent::hasLock($smarty, $cached);
}
public function get(Template $_template)
{
$this->contents = array();
$this->timestamps = array();
$t = $this->getContent($_template);
return $t ? $t : null;
}
}
@@ -1,208 +0,0 @@
<?php
/**
* Smarty PHPunit tests for object chain functionality after function calls
* Tests the new feature: {$x = collect($data)->filter()->values()->toJson()}
*
* @author Smarty Vibe
*/
/**
* Helper class to simulate a chainable collection object
*/
class ChainableCollection
{
private $data;
public function __construct($data)
{
$this->data = $data;
}
public function filter()
{
$this->data = array_filter($this->data);
return $this;
}
public function values()
{
$this->data = array_values($this->data);
return $this;
}
public function toJson()
{
return json_encode($this->data);
}
public function toArray()
{
return $this->data;
}
public function count()
{
return count($this->data);
}
}
/**
* Modifier plugin that returns a chainable object (simulates collect())
*/
function smarty_modifier_collect($data)
{
return new ChainableCollection($data);
}
/**
* Modifier plugin that returns an object with methods
*/
function smarty_modifier_create_object($value = null)
{
return new class {
public function getName() {
return 'TestObject';
}
public function getNext() {
return new class {
public function getValue() {
return 'ChainedValue';
}
};
}
};
}
/**
* class for function object chain tests
*
* @preserveGlobalState disabled
*
*/
class FunctionObjectChainTest extends PHPUnit_Smarty
{
public function setUp(): void
{
$this->setUpSmarty(__DIR__);
// Register modifier plugins that return chainable objects
// These are called like functions: {collect($data)}
$this->smarty->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'collect', 'smarty_modifier_collect');
$this->smarty->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'create_object', 'smarty_modifier_create_object');
}
public function testInit()
{
$this->cleanDirs();
}
/**
* Test the NEW feature: function call followed by method chain
* This is the core test for: {$x = collect($data)->filter()->values()->toJson()}
*/
public function testFunctionCallWithMethodChain()
{
$data = [1, 2, 0, 3, null, 4, '', 5];
$this->smarty->assign('data', $data);
// Test the new syntax: function()->method()->method()->method()
$result = $this->smarty->fetch('string:{collect($data)->filter()->values()->toJson()}');
$this->assertEquals('[1,2,3,4,5]', $result);
}
/**
* Test function call with method chain assigned to a variable
* This tests: {$x = collect($data)->filter()->values()->toJson()}
*/
public function testFunctionCallWithMethodChainAssignment()
{
$data = ['a', 'b', '', 'c', null, 'd'];
$this->smarty->assign('data', $data);
// Test assignment with chained methods
$result = $this->smarty->fetch('string:{$result = collect($data)->filter()->values()->toJson()}{$result}');
$this->assertEquals('["a","b","c","d"]', $result);
}
/**
* Test function call with single method chain
*/
public function testFunctionCallWithSingleMethod()
{
$data = [1, 2, 3];
$this->smarty->assign('data', $data);
$result = $this->smarty->fetch('string:{collect($data)->count()}');
$this->assertEquals('3', $result);
}
/**
* Test function call with nested method chains
*/
public function testFunctionCallWithNestedChain()
{
$result = $this->smarty->fetch('string:{create_object("")->getNext()->getValue()}');
$this->assertEquals('ChainedValue', $result);
}
/**
* Test that old syntax still works (two-step process)
*/
public function testOldSyntaxStillWorks()
{
$data = [1, 2, 0, 3];
$this->smarty->assign('data', $data);
// Old syntax: assign to variable first, then chain
$result = $this->smarty->fetch('string:{$x = collect($data)}{$x->filter()->values()->toJson()}');
$this->assertEquals('[1,2,3]', $result);
}
/**
* Test object chain functionality using template file
*/
public function testFunctionObjectChainFromTemplateFile()
{
$data = ['foo', '', 'bar', null, 'baz'];
$this->smarty->assign('data', $data);
$result = $this->smarty->fetch('test_function_chain.tpl');
// Expected: JSON of filtered array and count
$this->assertStringContainsString('["foo","bar","baz"]', $result);
$this->assertStringContainsString('3', $result);
}
/**
* Test complex chaining with multiple operations
* This demonstrates the power of the new feature
*/
public function testComplexChaining()
{
$data = [1, 2, 0, 3, '', 4, null, 5, false, 6];
$this->smarty->assign('data', $data);
// Complex chain: collect -> filter -> values -> toArray
$result = $this->smarty->fetch('string:{$filtered = collect($data)->filter()->values()->toArray()}{$filtered|@json_encode}');
$this->assertEquals('[1,2,3,4,5,6]', $result);
}
/**
* Test that demonstrates the benefit: one line vs multiple lines
*/
public function testNewSyntaxVsOldSyntax()
{
$data = [10, 20, 0, 30];
$this->smarty->assign('data', $data);
// NEW syntax (single line)
$new = $this->smarty->fetch('string:{collect($data)->filter()->count()}');
// OLD syntax (multiple steps)
$old = $this->smarty->fetch('string:{$temp = collect($data)}{$temp = $temp->filter()}{$temp->count()}');
// Both should produce the same result
$this->assertEquals('3', $new);
$this->assertEquals($new, $old);
}
}
@@ -1,4 +0,0 @@
{* Test template for NEW function object chain feature *}
{* Test: function()->method()->method()->method() *}
{collect($data)->filter()->values()->toJson()}
{collect($data)->filter()->count()}
@@ -0,0 +1,73 @@
<?php
/**
* APC CacheResource
* CacheResource Implementation based on the KeyValueStore API to use
* memcache as the storage resource for Smarty's output caching.
* *
*
* @author Uwe Tews
*/
class Smarty_CacheResource_Apc extends \Smarty\Cacheresource\KeyValueStore
{
/**
* Read values for a set of keys from cache
*
* @param array $keys list of keys to fetch
*
* @return array list of values with the given keys used as indexes
* @return boolean true on success, false on failure
*/
protected function read(array $keys)
{
$_res = array();
$res = apc_fetch($keys);
foreach ($res as $k => $v) {
$_res[ $k ] = $v;
}
return $_res;
}
/**
* Save values for a set of keys to cache
*
* @param array $keys list of values to save
* @param int $expire expiration time
*
* @return boolean true on success, false on failure
*/
protected function write(array $keys, $expire = null)
{
foreach ($keys as $k => $v) {
apc_store($k, $v, $expire);
}
return true;
}
/**
* Remove values from cache
*
* @param array $keys list of keys to delete
*
* @return boolean true on success, false on failure
*/
protected function delete(array $keys)
{
foreach ($keys as $k) {
apc_delete($k);
}
return true;
}
/**
* Remove *all* values from cache
*
* @return boolean true on success, false on failure
*/
protected function purge()
{
return apc_clear_cache('user');
}
}