Add routes caching

This commit is contained in:
Dmitriy Simushev 2015-03-20 13:08:04 +00:00
parent 30fd4bad7c
commit 094139dead
3 changed files with 207 additions and 12 deletions

View File

@ -24,27 +24,31 @@ use Mibew\Application;
use Mibew\Authentication\AuthenticationManager;
use Mibew\Mail\MailerFactory;
use Mibew\Routing\Router;
use Mibew\Routing\Loader\CacheLoader;
use Mibew\Routing\Loader\PluginLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Loader\YamlFileLoader;
// Prepare router
$file_locator = new FileLocator(array(MIBEW_FS_ROOT));
$route_loader = new YamlFileLoader($file_locator);
$loader_resolver = new LoaderResolver(array(
$route_loader,
new PluginLoader(),
));
$router = new Router($route_loader, 'configs/routing.yml');
$application = new Application($router, new AuthenticationManager());
// Use custom files cache
// Use custom cache
$cache_driver = new \Stash\Driver\FileSystem();
$cache_driver->setOptions(array('path' => MIBEW_FS_ROOT . '/cache/stash'));
$cache = new \Stash\Pool($cache_driver);
// The main route loader which loads nothig but works as a cache proxy for other
// loaders.
$route_loader = new CacheLoader($cache);
// Real loaders are attached via the resolver.
$loader_resolver = new LoaderResolver(array(
$route_loader,
new YamlFileLoader(new FileLocator(array(MIBEW_FS_ROOT))),
new PluginLoader(),
));
$router = new Router($route_loader, 'configs/routing.yml');
$application = new Application($router, new AuthenticationManager());
$application->setCache($cache);
// Use custom config-dependent mailer factory

View File

@ -87,6 +87,11 @@ class PluginController extends AbstractController
return $this->indexAction($request);
}
// Plugins can have own routing files and when the plugin becomes
// enabled its routes should become enabled too. So the cache is cleared
// to make sure the routes set is up to date.
$this->getCache()->getItem('routing/resources')->clear();
return $this->redirect($this->generateUrl('plugins'));
}
@ -121,6 +126,11 @@ class PluginController extends AbstractController
return $this->indexAction($request);
}
// Plugins can have own routing files and when the plugin becomes
// disabled its routes should become disabled too. So the cache is
// cleared to make sure the routes set is up to date.
$this->getCache()->getItem('routing/resources')->clear();
return $this->redirect($this->generateUrl('plugins'));
}

View File

@ -0,0 +1,181 @@
<?php
/*
* This file is a part of Mibew Messenger.
*
* Copyright 2005-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Mibew\Routing\Loader;
use Stash\Interfaces\PoolInterface;
use Stash\Invalidation;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\RouteCollection;
/**
* Loads routes from the cache.
*/
class CacheLoader extends Loader
{
/**
* @var PoolInterface
*/
protected $cache;
/**
* Maximum time in seconds the cache can be stored.
*
* @var int
*/
protected $cacheMaxAge;
/**
* Constructor of the class.
*
* @param PoolInterface $cache An instance of cache pool.
* @param int $max_age Maximum time in seconds the cache can be stored.
* Notice that the cache can be invalidated before the max age timeout is
* expired.
*/
public function __construct(PoolInterface $cache, $max_age = 600)
{
$this->cache = $cache;
$this->cacheMaxAge = $max_age;
}
/**
* {@inheritdoc}
*/
public function load($resource, $type = null)
{
if (!is_string($resource)) {
// Resources which names are not strings cannot be cached.
return $this->import($resource, $type);
}
$key = sprintf('routing/resources/%s/%s/', ($type ?: '-'), $resource);
$item = $this->cache->getItem($key);
$data = $item->get(Invalidation::VALUE, null);
if ($item->isMiss()) {
// There is no value in the cache. We should read the target file
// and cache the result.
$item->lock();
$collection = $this->import($resource, $type);
// Store routes and some extra info that needed for cache
// invalidation.
$item->set(
array(
'collection' => $this->serializeCollection($collection),
// We need current timestamp to invalidate resources
// manually if they are changed.
'created' => time(),
),
$this->cacheMaxAge
);
return $collection;
}
if (!$data) {
// $item->isMiss() returs false but there are no data in the cache.
// It seems that another script instance already lock the item and
// generate the data. Just read the target file and return actuall
// results without any caching.
return $this->import($resource, $type);
}
$collection = $this->unserializeCollection($data['collection']);
// Check if the cache should be invalidated.
if (!$this->isCollectionFresh($collection, $data['created'])) {
// The collection contains stale resources. The cache should be
// cleared and fresh data should be return to the client. The other
// call for the "load" method will regenerate the cache.
$item->clear();
return $this->import($resource, $type);
}
return $collection;
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
// The loader itself can load nothing.
return false;
}
/**
* Serializes a RoutesCollection instance.
*
* @param RouteCollection $collection A collection that should be
* serialized.
* @return string
*/
protected function serializeCollection(RouteCollection $collection)
{
return serialize(array(
'routes' => $collection->all(),
'resources' => $collection->getResources(),
));
}
/**
* Unserializes a RoutesCollection instance.
*
* @param string $serialized_collection An output of
* {@link CacheLoader::serializeCollection} method.
* @return RouteCollection
*/
protected function unserializeCollection($serialized_collection)
{
$data = unserialize($serialized_collection);
$collection = new RouteCollection();
foreach ($data['routes'] as $name => $route) {
$collection->add($name, $route);
}
foreach ($data['resources'] as $resource) {
$collection->addResource($resource);
}
return $collection;
}
/**
* Checks if all resources related with the collection are fresh.
*
* @param RouteCollection $collection The collection which resources will be
* checked.
* @param int $timestamp The last time the collection was loaded.
* @return boolean True if all resources are fresh and false otherwise.
*/
protected function isCollectionFresh(RouteCollection $collection, $timestamp)
{
foreach ($collection->getResources() as $resource) {
if (!$resource->isFresh($timestamp)) {
return false;
}
}
return true;
}
}