Merge pull request #3393 from halkyon/flushable

NEW Provide a consistent way of triggering flush
This commit is contained in:
Damian Mooyman 2014-08-22 10:28:26 +12:00
commit aec8430395
15 changed files with 244 additions and 61 deletions

View File

@ -5,4 +5,5 @@ Injector:
RequestProcessor: RequestProcessor:
properties: properties:
filters: filters:
- '%$FlushRequestFilter'
- '%$VersionedRequestFilter' - '%$VersionedRequestFilter'

View File

@ -9,7 +9,7 @@
* @package framework * @package framework
* @subpackage integration * @subpackage integration
*/ */
class RestfulService extends ViewableData { class RestfulService extends ViewableData implements Flushable {
protected $baseURL; protected $baseURL;
protected $queryString; protected $queryString;
@ -33,6 +33,19 @@ class RestfulService extends ViewableData {
*/ */
private static $default_curl_options = array(); private static $default_curl_options = array();
/**
* @config
* @var bool Flushes caches if set to true. This is set by {@link flush()}
*/
private static $flush = false;
/**
* Triggered early in the request when someone requests a flush.
*/
public static function flush() {
self::$flush = true;
}
/** /**
* set a curl option that will be applied to all requests as default * set a curl option that will be applied to all requests as default
* {@see http://php.net/manual/en/function.curl-setopt.php#refsect1-function.curl-setopt-parameters} * {@see http://php.net/manual/en/function.curl-setopt.php#refsect1-function.curl-setopt-parameters}
@ -171,7 +184,7 @@ class RestfulService extends ViewableData {
// Check for unexpired cached feed (unless flush is set) // Check for unexpired cached feed (unless flush is set)
//assume any cache_expire that is 0 or less means that we dont want to //assume any cache_expire that is 0 or less means that we dont want to
// cache // cache
if($this->cache_expire > 0 && !isset($_GET['flush']) if($this->cache_expire > 0 && self::$flush
&& @file_exists($cache_path) && @file_exists($cache_path)
&& @filemtime($cache_path) + $this->cache_expire > time()) { && @filemtime($cache_path) + $this->cache_expire > time()) {

View File

@ -0,0 +1,24 @@
<?php
/**
* Triggers a call to flush() on all implementors of Flushable.
*
* @package framework
* @subpackage control
*/
class FlushRequestFilter implements RequestFilter {
public function preRequest(SS_HTTPRequest $request, Session $session, DataModel $model) {
if($request->getVar('flush')) {
foreach(ClassInfo::implementorsOf('Flushable') as $class) {
$class::flush();
}
}
return true;
}
public function postRequest(SS_HTTPRequest $request, SS_HTTPResponse $response, DataModel $model) {
return true;
}
}

20
core/Flushable.php Normal file
View File

@ -0,0 +1,20 @@
<?php
/**
* Provides an interface for classes to implement their own flushing functionality
* whenever flush=1 is requested.
*
* @package framework
* @subpackage core
*/
interface Flushable {
/**
* This function is triggered early in the request if the "flush" query
* parameter has been set. Each class that implements Flushable implements
* this function which looks after it's own specific flushing functionality.
*
* @see FlushRequestFilter
*/
public static function flush();
}

View File

@ -78,19 +78,24 @@ class TestRunner extends Controller {
* top of the loader stacks. * top of the loader stacks.
*/ */
public static function use_test_manifest() { public static function use_test_manifest() {
$flush = true;
if(isset($_GET['flush']) && $_GET['flush'] === '0') {
$flush = false;
}
$classManifest = new SS_ClassManifest( $classManifest = new SS_ClassManifest(
BASE_PATH, true, isset($_GET['flush']) BASE_PATH, true, $flush
); );
SS_ClassLoader::instance()->pushManifest($classManifest, false); SS_ClassLoader::instance()->pushManifest($classManifest, false);
SapphireTest::set_test_class_manifest($classManifest); SapphireTest::set_test_class_manifest($classManifest);
SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest( SS_TemplateLoader::instance()->pushManifest(new SS_TemplateManifest(
BASE_PATH, project(), true, isset($_GET['flush']) BASE_PATH, project(), true, $flush
)); ));
Config::inst()->pushConfigStaticManifest(new SS_ConfigStaticManifest( Config::inst()->pushConfigStaticManifest(new SS_ConfigStaticManifest(
BASE_PATH, true, isset($_GET['flush']) BASE_PATH, true, $flush
)); ));
// Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class // Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
@ -109,14 +114,6 @@ class TestRunner extends Controller {
if(!PhpUnitWrapper::has_php_unit()) { if(!PhpUnitWrapper::has_php_unit()) {
die("Please install PHPUnit using pear"); die("Please install PHPUnit using pear");
} }
if(!isset($_GET['flush']) || !$_GET['flush']) {
Debug::message(
"WARNING: Manifest not flushed. " .
"Add flush=1 as an argument to discover new classes or files.\n",
false
);
}
} }
public function Link() { public function Link() {

View File

@ -86,7 +86,21 @@ or through a `<php>` flag in your `phpunit.xml` (see [Appenix C: "Setting PHP IN
<phpunit> <phpunit>
<!-- ... --> <!-- ... -->
<php> <php>
<var name="db" value="sqlite3"/> <get name="db" value="sqlite3"/>
</php>
</phpunit>
Note that on every test run, the manifest is flushed to avoid any bugs where a test doesn't clean up after
itself properly. You can override that behaviour by passing `flush=0` to the test command:
phpunit framework/tests flush=0
Alternatively, you can set the var in your `phpunit.xml` file:
<phpunit>
<!-- ... -->
<php>
<get name="flush" value="0"/>
</php> </php>
</phpunit> </phpunit>

View File

@ -93,3 +93,12 @@ You can access the following controller-method with /team/signup
## SSViewer template rendering ## SSViewer template rendering
See [templates](/reference/templates) for information on the SSViewer template system. See [templates](/reference/templates) for information on the SSViewer template system.
## Flush requests
If `?flush=1` is requested in the URL, e.g. http://mysite.com?flush=1, this will trigger a call to `flush()` on
any classes that implement the `Flushable` interface.
See [reference documentation on Flushable](/reference/flushable) for more details.

View File

@ -0,0 +1,56 @@
# Flushable
## Introduction
Allows a class to define it's own flush functionality, which is triggered when `flush=1` is requested in the URL.
`[api:FlushRequestFilter]` is run before a request is made, calling `flush()` statically on all
implementors of `[api:Flushable]`.
## Usage
To use this API, you need to make your class implement `[api:Flushable]`, and define a `flush()` static function,
this defines the actions that need to be executed on a flush request.
### Using with SS_Cache
This example uses `[api:SS_Cache]` in some custom code, and the same cache is cleaned on flush:
:::php
<?php
class MyClass extends DataObject implements Flushable {
public static function flush() {
SS_Cache::factory('mycache')->clean(Zend_Cache::CLEANING_MODE_ALL);
}
public function MyCachedContent() {
$cache = SS_Cache::factory('mycache')
$something = $cache->get('mykey');
if(!$something) {
$something = 'value to be cached';
$cache->save($something);
}
return $something;
}
}
### Using with filesystem
Another example, some temporary files are created in a directory in assets, and are deleted on flush. This would be
useful in an example like `GD` or `Imagick` generating resampled images, but we want to delete any cached images on
flush so they are re-created on demand.
:::php
<?php
class MyClass extends DataObject implements Flushable {
public static function flush() {
foreach(glob(ASSETS_PATH . '/_tempfiles/*.jpg') as $file) {
unlink($file);
}
}
}

View File

@ -98,6 +98,13 @@ this is reasonably quick, but still requires access to slow disk I/O.
The `Zend_Cache` API supports various caching backends ([list](http://framework.zend.com/manual/1.12/en/zend.cache.backends.html)) The `Zend_Cache` API supports various caching backends ([list](http://framework.zend.com/manual/1.12/en/zend.cache.backends.html))
which can provide better performance, including APC, Xcache, ZendServer, Memcached and SQLite. which can provide better performance, including APC, Xcache, ZendServer, Memcached and SQLite.
## Cleaning caches on flush=1 requests
If `?flush=1` is requested in the URL, e.g. http://mysite.com?flush=1, this will trigger a call to `flush()` on
any classes that implement the `Flushable` interface. Using this, you can trigger your caches to clean.
See [reference documentation on Flushable](/reference/flushable) for implementation details.
### Memcached ### Memcached
This backends stores cache records into a [memcached](http://www.danga.com/memcached/) This backends stores cache records into a [memcached](http://www.danga.com/memcached/)

View File

@ -61,7 +61,7 @@ require_once 'i18nSSLegacyAdapter.php';
* @package framework * @package framework
* @subpackage misc * @subpackage misc
*/ */
class i18n extends Object implements TemplateGlobalProvider { class i18n extends Object implements TemplateGlobalProvider, Flushable {
/** /**
* This static variable is used to store the current defined locale. * This static variable is used to store the current defined locale.
@ -97,6 +97,21 @@ class i18n extends Object implements TemplateGlobalProvider {
*/ */
protected static $translators; protected static $translators;
/**
* Triggered early in the request when someone requests a flush.
*/
public static function flush() {
self::get_cache()->clean(Zend_Cache::CLEANING_MODE_ALL);
}
/**
* Return an instance of the cache used for i18n data.
* @return Zend_Cache
*/
public static function get_cache() {
return SS_Cache::factory('i18n', 'Output', array('lifetime' => null, 'automatic_serialization' => true));
}
/** /**
* Use javascript i18n through the ss.i18n class (enabled by default). * Use javascript i18n through the ss.i18n class (enabled by default).
* If set to TRUE, includes javascript requirements for the base library * If set to TRUE, includes javascript requirements for the base library
@ -2039,11 +2054,11 @@ class i18n extends Object implements TemplateGlobalProvider {
// which is instanciated by core with a $clean instance variable. // which is instanciated by core with a $clean instance variable.
if(!$adapter->isAvailable($lang)) { if(!$adapter->isAvailable($lang)) {
i18n::include_by_locale($lang, (isset($_GET['flush']))); i18n::include_by_locale($lang);
} }
if(!$adapter->isAvailable($locale)) { if(!$adapter->isAvailable($locale)) {
i18n::include_by_locale($locale, (isset($_GET['flush']))); i18n::include_by_locale($locale);
} }
$translation = $adapter->translate($entity, $locale); $translation = $adapter->translate($entity, $locale);
@ -2107,9 +2122,7 @@ class i18n extends Object implements TemplateGlobalProvider {
*/ */
public static function get_translators() { public static function get_translators() {
if(!Zend_Translate::getCache()) { if(!Zend_Translate::getCache()) {
Zend_Translate::setCache( Zend_Translate::setCache(self::get_cache());
SS_Cache::factory('i18n', 'Output', array('lifetime' => null, 'automatic_serialization' => true))
);
} }
if(!self::$translators) { if(!self::$translators) {
@ -2122,8 +2135,8 @@ class i18n extends Object implements TemplateGlobalProvider {
)) ))
); );
i18n::include_by_locale('en', isset($_GET['flush'])); i18n::include_by_locale('en');
i18n::include_by_locale('en_US', isset($_GET['flush'])); i18n::include_by_locale('en_US');
} }
return self::$translators; return self::$translators;
@ -2515,7 +2528,7 @@ class i18n extends Object implements TemplateGlobalProvider {
*/ */
public static function include_by_locale($locale, $clean = false) { public static function include_by_locale($locale, $clean = false) {
if($clean) { if($clean) {
Zend_Translate::clearCache(); self::flush();
} }
// Get list of module => path pairs, and then just the names // Get list of module => path pairs, and then just the names

View File

@ -6,7 +6,7 @@
* @package framework * @package framework
* @subpackage filesystem * @subpackage filesystem
*/ */
class Image extends File { class Image extends File implements Flushable {
const ORIENTATION_SQUARE = 0; const ORIENTATION_SQUARE = 0;
const ORIENTATION_PORTRAIT = 1; const ORIENTATION_PORTRAIT = 1;
@ -72,6 +72,19 @@ class Image extends File {
*/ */
private static $force_resample = false; private static $force_resample = false;
/**
* @config
* @var bool Regenerates images if set to true. This is set by {@link flush()}
*/
private static $flush = false;
/**
* Triggered early in the request when someone requests a flush.
*/
public static function flush() {
self::$flush = true;
}
public static function set_backend($backend) { public static function set_backend($backend) {
self::config()->backend = $backend; self::config()->backend = $backend;
} }
@ -424,7 +437,7 @@ class Image extends File {
if($this->ID && $this->Filename && Director::fileExists($this->Filename)) { if($this->ID && $this->Filename && Director::fileExists($this->Filename)) {
$cacheFile = call_user_func_array(array($this, "cacheFilename"), $args); $cacheFile = call_user_func_array(array($this, "cacheFilename"), $args);
if(!file_exists(Director::baseFolder()."/".$cacheFile) || isset($_GET['flush'])) { if(!file_exists(Director::baseFolder()."/".$cacheFile) || self::$flush) {
call_user_func_array(array($this, "generateFormattedImage"), $args); call_user_func_array(array($this, "generateFormattedImage"), $args);
} }

View File

@ -66,10 +66,3 @@ SapphireTest::set_is_running_test(true);
// Remove the error handler so that PHPUnit can add its own // Remove the error handler so that PHPUnit can add its own
restore_error_handler(); restore_error_handler();
if(!isset($_GET['flush']) || !$_GET['flush']) {
Debug::message(
"WARNING: Manifest not flushed. " .
"Add flush=1 as an argument to discover new classes or files.\n",
false
);
}

View File

@ -7,7 +7,14 @@
* @package framework * @package framework
* @subpackage view * @subpackage view
*/ */
class Requirements { class Requirements implements Flushable {
/**
* Triggered early in the request when someone requests a flush.
*/
public static function flush() {
self::delete_all_combined_files();
}
/** /**
* Enable combining of css/javascript files. * Enable combining of css/javascript files.
@ -272,6 +279,13 @@ class Requirements {
return self::backend()->delete_combined_files($combinedFileName); return self::backend()->delete_combined_files($combinedFileName);
} }
/**
* Deletes all generated combined files in the configured combined files directory,
* but doesn't delete the directory itself.
*/
public static function delete_all_combined_files() {
return self::backend()->delete_all_combined_files();
}
/** /**
* Re-sets the combined files definition. See {@link Requirements_Backend::clear_combined_files()} * Re-sets the combined files definition. See {@link Requirements_Backend::clear_combined_files()}
@ -1005,6 +1019,20 @@ class Requirements_Backend {
} }
} }
/**
* Deletes all generated combined files in the configured combined files directory,
* but doesn't delete the directory itself.
*/
public function delete_all_combined_files() {
$combinedFolder = $this->getCombinedFilesFolder();
if(!$combinedFolder) return false;
$path = Director::baseFolder() . '/' . $combinedFolder;
if(file_exists($path)) {
Filesystem::removeFolder($path, true);
}
}
public function clear_combined_files() { public function clear_combined_files() {
$this->combine_files = array(); $this->combine_files = array();
} }
@ -1085,7 +1113,7 @@ class Requirements_Backend {
} }
// Determine if we need to build the combined include // Determine if we need to build the combined include
if(file_exists($combinedFilePath) && !isset($_GET['flush'])) { if(file_exists($combinedFilePath)) {
// file exists, check modification date of every contained file // file exists, check modification date of every contained file
$srcLastMod = 0; $srcLastMod = 0;
foreach($fileList as $file) { foreach($fileList as $file) {

View File

@ -555,7 +555,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope {
* <b>Caching</b> * <b>Caching</b>
* *
* Compiled templates are cached via {@link SS_Cache}, usually on the filesystem. * Compiled templates are cached via {@link SS_Cache}, usually on the filesystem.
* If you put ?flush=all on your URL, it will force the template to be recompiled. * If you put ?flush=1 on your URL, it will force the template to be recompiled.
* *
* @see http://doc.silverstripe.org/themes * @see http://doc.silverstripe.org/themes
* @see http://doc.silverstripe.org/themes:developing * @see http://doc.silverstripe.org/themes:developing
@ -563,7 +563,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope {
* @package framework * @package framework
* @subpackage view * @subpackage view
*/ */
class SSViewer { class SSViewer implements Flushable {
/** /**
* @config * @config
@ -638,6 +638,13 @@ class SSViewer {
*/ */
private static $global_key = '$CurrentReadingMode, $CurrentUser.ID'; private static $global_key = '$CurrentReadingMode, $CurrentUser.ID';
/**
* Triggered early in the request when someone requests a flush.
*/
public static function flush() {
self::flush_template_cache();
}
/** /**
* Create a template from a string instead of a .ss file * Create a template from a string instead of a .ss file
* *
@ -744,18 +751,6 @@ class SSViewer {
public function __construct($templateList, TemplateParser $parser = null) { public function __construct($templateList, TemplateParser $parser = null) {
$this->setParser($parser ?: Injector::inst()->get('SSTemplateParser')); $this->setParser($parser ?: Injector::inst()->get('SSTemplateParser'));
// flush template manifest cache if requested
if (isset($_GET['flush']) && $_GET['flush'] == 'all') {
if(Director::isDev() || Director::is_cli() || Permission::check('ADMIN')) {
self::flush_template_cache();
} else {
if(!Security::ignore_disallowed_actions()) {
return Security::permissionFailure(null, 'Please log in as an administrator to flush ' .
'the template cache.');
}
}
}
if(!is_array($templateList) && substr((string) $templateList,-3) == '.ss') { if(!is_array($templateList) && substr((string) $templateList,-3) == '.ss') {
$this->chosenTemplates['main'] = $templateList; $this->chosenTemplates['main'] = $templateList;
} else { } else {
@ -927,7 +922,7 @@ class SSViewer {
if (!self::$flushed) { if (!self::$flushed) {
$dir = dir(TEMP_FOLDER); $dir = dir(TEMP_FOLDER);
while (false !== ($file = $dir->read())) { while (false !== ($file = $dir->read())) {
if (strstr($file, '.cache')) { unlink(TEMP_FOLDER.'/'.$file); } if (strstr($file, '.cache')) unlink(TEMP_FOLDER . '/' . $file);
} }
self::$flushed = true; self::$flushed = true;
} }
@ -1038,7 +1033,7 @@ class SSViewer {
. str_replace(array('\\','/',':'), '.', Director::makeRelative(realpath($template))); . str_replace(array('\\','/',':'), '.', Director::makeRelative(realpath($template)));
$lastEdited = filemtime($template); $lastEdited = filemtime($template);
if(!file_exists($cacheFile) || filemtime($cacheFile) < $lastEdited || isset($_GET['flush'])) { if(!file_exists($cacheFile) || filemtime($cacheFile) < $lastEdited) {
$content = file_get_contents($template); $content = file_get_contents($template);
$content = $this->parseTemplateContent($content, $template); $content = $this->parseTemplateContent($content, $template);