Skip to content

Instantly share code, notes, and snippets.

@fprochazka
Created November 19, 2010 01:00
Show Gist options
  • Save fprochazka/705975 to your computer and use it in GitHub Desktop.
Save fprochazka/705975 to your computer and use it in GitHub Desktop.
PresenterTree
services:
presenterTree:
class: Kdyby\PresenterTree
<?php
require LIBS_DIR . '/Nette/loader.php';
Nette\Environment::loadConfig();
$pt = Nette\Environment::getContext()->getService("Kdyby\\PresenterTree");
# presenters
dump($pt->presenters); // all of them!
dump($pt->getPresenters('Front')); // directry in front
dump($pt->getPresenters('Front', TRUE)); // even those in submodules!
dump($pt->getPresenters('Front:Forum')); // wanna have a nice menu in forum ? :)
# modules
dump($pt->modules); // root modules
dump($pt->getModules('Front')); // directry in front
dump($pt->getModules('Front:Client')); // avalaible submodules?
# actions
dump($pt->getActions('Front:Homepage')); // homepage actions?
dump($pt->getActions('Front:Client')); // damn that's a module... too bad, returns NULL
foreach ($pt->getPresenters('Front') as $presenter) {
dump($presenter->actions); //
}
# excluding presenters & actions
abstract class BasePresenter extends Nette\Application\UI\Presenter { } // one
/** @hideInTree */
class BasePresenter extends Nette\Application\UI\Presenter { } // two
class BasePresenter extends Nette\Application\UI\Presenter
{
/** @hideInTree */
public function actionDefault() { } // three
/** @hideInTree */
public function renderSomethink() { } // three
}
<?php
namespace Kdyby;
use Nette;
use Nette\Reflection\ClassType;
/**
* @author Filip Procházka <hosiplan@kdyby.org>
*/
class PresenterInfo extends Nette\Object
{
/** @var string */
private $name;
/** @var string */
private $module;
/** @var string */
private $class;
/** @var array */
private $actions;
/** @var Kdyby\PresenterTree */
private $tree;
/**
* @param string $name
* @param string $module
* @param string $class
*/
public function __construct($name, $module, $class)
{
$this->name = $name;
$this->module = $module;
$this->class = $class;
}
/**
* @return bool
*/
public function isPublic()
{
$ref = $this->getPresenterReflection();
return !$ref->hasAnnotation('hideInTree');
}
/**
* @return ClassType
*/
public function getPresenterReflection()
{
return new ClassType($this->getPresenterClass());
}
/**
* @return array
*/
public function getActions()
{
if ($this->actions === NULL) {
$this->actions = $this->getTree()->getPresenterActions($this);
}
return $this->actions;
}
/**
* @return string
*/
public function getName($full = FALSE)
{
return ($full ? ':' . $this->module . ':' : NULL) . $this->name;
}
/**
* @return string
*/
public function getPresenterClass()
{
return $this->class;
}
/**
* @return string
*/
public function getModule()
{
return $this->module;
}
public function __toString()
{
return $this->getName(TRUE);
}
/**
* @return Kdyby\PresenterTree
*/
private function getTree()
{
if ($this->tree === NULL) {
$this->tree = $this->getContext()->getService("Kdyby\\PresenterTree");
}
return $this->tree;
}
/**
* @return Nette\context
*/
private function getContext()
{
return Nette\Environment::getContext();
}
public function __sleep()
{
$properties = (array)$this;
unset($properties['tree'], $properties['actions']);
return array_keys($properties);
}
}
<?php
namespace Kdyby;
use Nette;
use Nette\Caching\Cache;
use Nette\Reflection\Method;
use Nette\Utils\Finder;
use Nette\Utils\Strings;
/**
* @author Filip Procházka <hosiplan@kdyby.org>
*/
class PresenterTree extends Nette\Object
{
const ALL = TRUE;
const DIRECT = FALSE;
// const TREE = 'tree'; // what does this mean? bad memmory :(
/** @var Nette\Caching\Cache */
private $cache;
public function __construct()
{
$cache = $this->getCache();
if (!$this->isActual($cache)) {
$cache->save('presenters', $this->buildPresenterTree());
$cache->save('modules', $this->buildModuleTree($cache['presenters']));
$cache->save('actions', $this->buildActionTree($cache['presenters']));
$this->isActual(TRUE);
}
}
/**
* @param Nette\Caching\Cache|bool $cache
* @return bool
*/
private function isActual($cache = NULL)
{
$classes = $this->getRobotLoader()->getIndexedClasses();
$hash = md5(serialize($classes));
if ($cache === TRUE) {
return $this->getCache()->save('hash', $hash);
}
if ($cache['hash'] != $hash) {
return FALSE;
}
return TRUE;
}
/**
* @return array
*/
private function buildPresenterTree()
{
$classes = array_keys($this->getRobotLoader()->getIndexedClasses());
$tree = array();
foreach ($i = new \RegexIterator(new \ArrayIterator($classes), "~.*Presenter$~") as $class) {
$nettePath = Strings::split(substr($class, 0, -9), '~Module\\\\~i');
$presenter = array_pop($nettePath);
$module = strlen($module = $this->formatNettePath($nettePath)) ? substr($module, 1) : NULL;
$presenterInfo = new PresenterInfo($presenter, $module, $class);
$ref = $presenterInfo->getPresenterReflection();
if (!$ref->isAbstract() && $presenterInfo->isPublic()) {
$t =& $tree['byModule'];
foreach ($nettePath as $step) {
$t[$step] = isset($t[$step]) ? $t[$step] : array();
$t =& $t[$step];
}
$t[$presenter] = $presenterInfo;
$steps = array();
foreach ($nettePath as $step) {
$steps[] = $step;
$module = substr($this->formatNettePath($steps), 1);
$relative = substr($this->formatNettePath(array_diff($nettePath, $steps), $presenter), 1);
$tree['all'][NULL][':'.$module.':'.$relative] = $presenterInfo;
$tree['all'][$module][$relative] = $presenterInfo;
}
}
}
ksort($tree['all']);
return $tree;
}
/**
* @return array
*/
private function buildModuleTree($presenters)
{
$tree = array();
$modules = array();
foreach ($presenters['all'][NULL] as $fullPath => $presenter) {
if (!in_array($presenter->module, $modules)) {
$modules[] = $presenter->module;
}
}
foreach ($modules as $module) {
$nettePath = explode(':', $module);
$module = array_pop($nettePath);
$t =& $tree['byModule'];
foreach ($nettePath as $step) {
$t[$step] = isset($t[$step]) ? $t[$step] : array();
$t =& $t[$step];
}
$t = is_array($t) ? $t : array();
$t[] = $module;
}
return $tree;
}
/**
* @return array
*/
private function buildActionTree($presenters)
{
$tree = array();
foreach ($presenters['all'][NULL] as $fullPath => $presenter) {
$ref = $presenter->getPresenterReflection();
$presenterInstance = new $presenter->presenterClass;
$templateViewPattern = $presenterInstance->formatTemplateFiles(substr($fullPath, 1), '*');
$views = array();
foreach ($templateViewPattern as $pattern) {
$filePattern = Strings::split(basename($pattern), '~\*~');
if (is_dir(dirname($pattern))) {
foreach (Finder::findFiles(basename($pattern))->in(dirname($pattern)) as $view) {
$views[] = Strings::replace($view->getFilename(), array(
'~^'.preg_quote($filePattern[0]).'~' => '',
'~'.preg_quote($filePattern[1]).'$~' => ''
));
}
}
}
$actions = array();
foreach ($views as $view) {
$actions[$view] = $fullPath.':'.lcfirst($view);
}
$methods = array_map(function($method) {
return $method->name;
}, $ref->getMethods(Method::IS_PUBLIC));
$methods = array_filter($methods, function($method){
return in_array(substr($method, 0, 6), array('action', 'render'));
});
$allowed = array();
foreach ($methods as $method) {
$method = $ref->getMethod($method);
$action = lcfirst(substr($method->name, 6));
if (!$method->hasAnnotation('hideInTree')) {
if (!isset($allowed[$action])) {
$allowed[$action] = $fullPath.':'.$action;
}
} else {
$allowed[$action] = FALSE;
}
}
$actions = array_filter(array_merge($actions, $allowed), function ($action) { return (bool)$action; });
if ($actions) {
$tree['byPresenterClass'][$presenter->presenterClass] = array_flip($actions);
$t =& $tree['byModule'];
foreach (Strings::split($presenter->module, '~:~') as $step) {
$t[$step] = isset($t[$step]) ? $t[$step] : array();
$t =& $t[$step];
}
$t[$presenter->name] = array_flip($actions);
}
}
return $tree;
}
/**
* @param string $nettePath
* @param bool $all
* @return array
*/
public function getPresenters($nettePath = NULL, $all = FALSE)
{
$nettePath = trim($nettePath, ':');
if ($all === FALSE && $nettePath === NULL) {
return isset($this->cache['presenters']['all'][$nettePath]) ? $this->cache['presenters']['all'][$nettePath] : NULL;
}
$tree = $this->cache['presenters']['byModule'];
foreach (Strings::split($nettePath, '~:~') as $step) {
if (!isset($tree[$step])) {
return NULL;
}
$tree =& $tree[$step];
}
return array_filter($tree, function($item) { return !is_array($item); });
}
/**
* @param Kdyby\PresenterInfo $presenter
* @return array
*/
public function getPresenterActions(PresenterInfo $presenter)
{
return $this->cache['actions']['byPresenterClass'][$presenter->class];
}
/**
* @param string $nettePath
* @return array
*/
public function getActions($nettePath)
{
$nettePath = trim($nettePath, ':');
if (!$nettePath) {
return NULL;
}
$presenters = array();
$tree = $this->cache['actions']['byModule'];
foreach (Strings::split($nettePath, '~:~') as $step) {
if (!isset($tree[$step])) {
return NULL;
}
$tree =& $tree[$step];
}
return array_filter($tree, function($item) { return !is_array($item); });
}
/**
* @param string $nettePath
* @param bool $all
* @return array
*/
public function getModules($nettePath = NULL)
{
$nettePath = trim($nettePath, ':');
if (!$nettePath) {
return array_filter($this->cache['modules']['byModule'], function($item) { return !is_array($item); });
}
$presenters = array();
$tree = $this->cache['modules']['byModule'];
foreach (Strings::split($nettePath, '~:~') as $step) {
if (!isset($tree[$step])) {
return NULL;
}
$tree =& $tree[$step];
}
return array_filter($tree, function($item) { return !is_array($item); });
}
/**
* @param array $steps
* @param string $presenter
* @return string
*/
private function formatNettePath($steps, $presenter = NULL)
{
return "" . ($steps ? ':'.implode(':', $steps) : NULL) . ($presenter ? ':'.$presenter : NULL);
}
/**
* @return Nette\Caching\Cache
*/
private function getCache()
{
if ($this->cache === NULL) {
$this->cache = Nette\Environment::getCache("Kdyby.Presenter.Tree");
}
return $this->cache;
}
/**
* @return Nette\Loaders\RobotLoader
*/
private function getRobotLoader()
{
return $this->getContext()->getService("robotLoader");
}
/**
* @return Nette\Context
*/
public function getContext()
{
return Nette\Environment::getContext();
}
/**
* @return Kdyby\PresenterTree
*/
public static function createPresenterTree()
{
return new static;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment