mirror of
https://github.com/smarty-php/smarty.git
synced 2026-08-09 23:11:30 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12587abea1 |
@@ -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 +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)
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+729
-740
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
-208
@@ -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);
|
||||
}
|
||||
}
|
||||
-4
@@ -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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user