This repository has been archived on 2024-10-25. You can view files and clone it, but cannot push or open issues or pull requests.
Sequenzia/Rails/Routing/Mapper.php

584 lines
17 KiB
PHP
Raw Normal View History

2013-10-02 08:12:58 +02:00
<?php
namespace Rails\Routing;
use Closure;
use Rails;
use Rails\Toolbox;
class Mapper
{
const ROOT_URL = '/';
const ROOT_DEFAULT_TO = 'welcome#index';
const ADMIN_DEFAULT_TO = 'admin#';
const ADMIN_STYLESHEET_TO = 'admin#stylesheet';
private $concernList = [];
private $paths = [];
private $scopeParams = [];
private $routeNames = [];
private $modules = [];
private $createAsMember = false;
private $resources = [];
private $resource = [];
private $resourceNesting = [];
private $routeSet;
public function __construct(Route\RouteSet $routeSet)
{
$this->routeSet = $routeSet;
}
public function __call($method, $params)
{
if ($method == 'namespace') {
call_user_func_array([$this, 'namespaced'], $params);
}
}
/**
* Must specify a verb through the via parameter.
*/
public function match($url, $to = null, array $params = array())
{
if (!$params && is_array($to)) {
$params = $to;
$to = null;
}
$this->createAndAddRoute($url, $to, $params);
}
public function get($url, $to = null, array $params = array())
{
$this->createRouteWithVerb('get', $url, $to, $params);
}
public function post($url, $to = null, array $params = array())
{
$this->createRouteWithVerb('post', $url, $to, $params);
}
public function put($url, $to = null, array $params = array())
{
$this->createRouteWithVerb('put', $url, $to, $params);
}
public function patch($url, $to = null, array $params = array())
{
$this->createRouteWithVerb('patch', $url, $to, $params);
}
public function delete($url, $to = null, array $params = array())
{
$this->createRouteWithVerb('delete', $url, $to, $params);
}
public function root($to)
{
if (!$this->paths) {
$this->routeSet->set_root_route(
$this->createRoute(
self::ROOT_URL,
$to,
[
'via' => ['get'],
'as' => 'root'
]
)
);
} else {
$this->createAndAddRoute('', $to, ['via' => ['get'], 'as' => implode('_', $this->paths) . '_root']);
}
}
public function scope($params, Closure $routesBlock)
{
if (is_string($params)) {
$params = [ 'path' => $params ];
} elseif (!is_array($params)) {
$params = [ $params ];
}
if (isset($params['path'])) {
$this->paths[] = $params['path'];
unset($params['path']);
}
$this->scopeParams[] = $params;
$routesBlock();
array_pop($this->scopeParams);
array_pop($this->paths);
}
public function member(Closure $block)
{
$this->scopeParams[] = ['on' => 'member'];
$block();
array_pop($this->scopeParams);
}
/**
* Namespace is a mix of module + path options.
*/
public function namespaced($namespace, Closure $block)
{
$this->paths[] = $namespace;
$this->modules[] = $namespace;
$this->routeNames[] = $namespace;
$block();
array_pop($this->paths);
array_pop($this->modules);
array_pop($this->routeNames);
}
/**
* @param string $name String with format /^\w+$/
*/
public function resources($name, $params = null, Closure $block = null)
{
$this->resources[] = $name;
$this->resourceNesting[] = 'resources';
$this->createRoutesWithResource(true, $params, $block);
array_pop($this->resources);
array_pop($this->resourceNesting);
}
/**
* @param string $name String with format /^\w+$/
*/
public function resource($name, $params = null, Closure $block = null)
{
$this->resource[] = $name;
$this->resourceNesting[] = 'resource';
$this->createRoutesWithResource(false, $params, $block);
array_pop($this->resource);
array_pop($this->resourceNesting);
}
public function concern($name, Closure $block)
{
$this->concernList[$name] = $block;
}
private function createRoutesWithResource($multiple, $params, Closure $block = null)
{
if ($params instanceof Closure) {
$block = $params;
$params = [];
} elseif (!is_array($params)) {
$params = [];
}
if ($block) {
$block();
}
if (isset($params['concerns'])) {
$params['concerns'] = (array)$params['concerns'];
foreach ($params['concerns'] as $name) {
if (!isset($this->concernList[$name])) {
throw new Exception\RuntimeException(
sprintf("No concern named %s was found")
);
}
$this->concernList[$name]();
}
unset($params['concerns']);
}
if (!isset($params['defaults']) || $params['defaults']) {
$actions = [ 'index', 'create', 'new', 'edit', 'show', 'update', 'destroy' ];
if (!$multiple) {
array_shift($actions);
}
$only = !empty($params['only']) ? $params['only'] : [];
$except = !empty($params['except']) ? $params['except'] : [];
unset($params['only'], $params['except']);
foreach ($actions as $action) {
if ($only) {
$create = in_array($action, $only);
} elseif ($except) {
$create = !in_array($action, $except);
} else {
$create = true;
}
if ($create) {
$this->addRoute($this->createResourceRoute($action, $params, $multiple));
}
}
}
}
private function createResourceRoute($action, $params, $multiple = true, $to = null)
{
$path = '';
$pre_alias = [];
$resources = $this->resources;
$count = count($resources);
$inflector = Rails::services()->get('inflector');
$uncountable = ($action == 'index' || $action == 'create' || $action == 'new');
$single = $count == 1;
foreach ($resources as $k => $res) {
$path .= '/' . $res;
$last = $k + 1 == $count;
if ($multiple) {
if ($uncountable && $last) {
continue;
} elseif ($last || $single) {
$path .= '/:id';
} else {
$path .= '/:' . $inflector->singularize($res) . '_id';
}
} else {
$singularRes = $inflector->singularize($res);
$pre_alias[] = $singularRes;
$path .= '/:' . $singularRes . '_id';
}
}
if (!$multiple) {
$resources = $this->resource;
foreach ($resources as $res) {
$path .= '/' . $res;
}
}
$path = ltrim($path, '/');
$setAlias = true;
switch ($action) {
case 'index':
$method = 'get';
break;
case 'new':
$method = 'get';
$path .= '/new';
$action = 'blank';
array_unshift($pre_alias, 'new');
break;
case 'create':
$method = 'post';
$setAlias = !$multiple;
break;
case 'show':
$method = 'get';
break;
case 'edit':
$method = 'get';
$path .= '/edit';
array_unshift($pre_alias, 'edit');
break;
case 'update':
$method = ['put', 'patch'];
$setAlias = false;
break;
case 'destroy':
$setAlias = false;
$method = 'delete';
break;
}
$controller = end($resources);
$plural = substr($controller, -1, 1) == 's';
if ($setAlias) {
if ($multiple) {
$aliasedResources = [];
foreach ($resources as $resource) {
$aliasedResources[] = $inflector->singularize($resource);
}
} else {
$aliasedResources = $resources;
}
if ($action == 'index') {
$lastIndex = count($resources) - 1;
$aliasedResources[$lastIndex] = $resources[$lastIndex];
}
$pre_alias = implode('_', $pre_alias);
$pre_alias .= ($pre_alias && $this->routeNames ? '_' : '') . implode($this->routeNames);
$alias = ($pre_alias ? $pre_alias . '_' : '') . implode('_', $aliasedResources);
if ($action == 'index') {
if (!$plural) {
$alias .= '_index';
}
}
// $alias = $inflector->camelize($alias, false);
if (Route\RouteSet::validate_route_alias($alias)) {
Route\RouteSet::add_route_alias($alias);
$params['as'] = $alias;
}
} else {
$params['as'] = '';
}
if ($method) {
$params['via'] = $method;
}
if (!$to) {
if (!$multiple) {
$controller = $inflector->pluralize($controller);
}
$to = $controller . '#' . $action;
}
unset($params['on']);
return $this->createRoute($path, $to, $params, ['skip_resources' => true]);
}
private function createResourceNestedRoute($action, $params, $multiple = true, $to = null)
{
$path = '';
$setAlias = true;
$resources = $this->resources;
if (isset($params['as'])) {
$pre_alias = '';
// $pre_alias = $params['as'];
$setAlias = false;
} elseif (preg_match('/\A\w+\Z/', $action)) {
$pre_alias = $action;
} else {
$pre_alias = '';
$setAlias = false;
}
foreach ($resources as $k => $res) {
$path .= '/' . $res;
if ($this->createAsMember || (isset($params['on']) && $params['on'] == 'member'))
$path .= '/:id';
else
$path .= '/:' . $res . '_id';
$pre_alias .= '_' . $res;
}
if (!$multiple) {
$resources = $this->resource;
foreach ($resources as $res) {
$path .= '/' . $res;
}
}
$method = !empty($params['via']) ? $params['via'] : null;
if ($action != 'index') {
$path .= '/' . $action;
}
$path = ltrim($path, '/');
$controller = end($resources);
$plural = substr($controller, -1, 1) == 's';
$inflector = Rails::services()->get('inflector');
if ($setAlias) {
$aliasedResources = [];
foreach ($resources as $resource) {
$aliasedResources[] = $inflector->singularize($resource);
}
if ($action == 'index') {
$lastIndex = count($resources) - 1;
$aliasedResources[$lastIndex] = $resources[$lastIndex];
}
$pre_alias .= ($pre_alias && $this->routeNames ? '_' : '') . implode($this->routeNames);
$alias = ($pre_alias ? $pre_alias . '_' : '') . implode('_', $aliasedResources);
if ($action == 'index') {
if (!$plural)
$alias .= '_index';
}
// elseif ($plural)
// $alias = substr($alias, 0, -1);
if (Route\RouteSet::validate_route_alias($alias)) {
Route\RouteSet::add_route_alias($alias);
$params['as'] = $alias;
}
} elseif (empty($params['as'])) {
$params['as'] = '';
}
if ($method) {
$params['via'] = $method;
}
if (!$to) {
if (!$multiple) {
$controller = $inflector->pluralize($controller);
}
$to = $controller . '#' . $action;
}
return $this->createRoute($path, $to, $params, ['skip_resources' => true]);
}
/**
* Used by Rails.
*/
public function drawRoutes(Closure $block)
{
$block = $block->bindTo($this);
$block();
if (!$this->routeSet->rootRoute()) {
$this->root(self::ROOT_DEFAULT_TO);
}
$this->createPanelRoute();
$this->createAssetsRoute();
}
private function createRoute($url, $to, array $params = [], array $createOpts = [])
{
if (!array_key_exists('as', $params)) {
if (preg_match('/^[\w\/]+$/', $url)) {
$params['as'] = str_replace('/', '_', $url);
}
}
$this->addNestedParams($url, $params);
if (empty($createOpts['skip_resources']) && $this->resourceNesting) {
$current = end($this->resourceNesting);
$route = $this->createResourceNestedRoute($url, $params, $current == 'resources', $to);
} else {
unset($params['on']);
$route = new Route\Route($url, $to, $params);
}
return $route;
}
private function addNestedParams(&$url, array &$params)
{
if ($this->scopeParams) {
$scopeParams = [];
foreach ($this->scopeParams as $k => $param) {
$scopeParams = array_merge($scopeParams, $param);
}
$params = array_merge($scopeParams, $params);
}
if ($this->modules) {
if (!isset($params['module'])) {
$params['module'] = [];
} else {
$params['module'] = explode('/', $params['module']);
}
$params['modules'] = array_filter(array_merge($this->modules, $params['module']));
unset($params['module']);
}
if ($this->paths) {
if (!isset($params['path'])) {
$params['path'] = [];
} else {
$params['path'] = explode('/', $params['path']);
}
$params['paths'] = array_filter(array_merge(Toolbox\ArrayTools::flatten($this->paths), $params['path']));
unset($params['path']);
}
}
private function addRoute($route)
{
$route->build();
$this->routeSet->add($route);
}
private function createAndAddRoute($url, $to, array $params)
{
$this->addRoute($this->createRoute($url, $to, $params));
}
private function createRouteWithVerb($via, $url, $to = null, array $params)
{
if (is_array($to)) {
$params = $to;
$to = null;
}
$params['via'] = $via;
$this->createAndAddRoute($url, $to, $params);
}
private function createPanelRoute()
{
if ($panelPath = Rails::application()->config()->rails_panel_path) {
$route = new Route\PanelRoute(
$panelPath . '(/:action)',
self::ADMIN_DEFAULT_TO,
[
'rails_panel' => true,
'defaults' => ['controller' => 'admin'],
'via' => ['get', 'post']
]
);
$route->build();
$this->routeSet->set_panel_route($route);
$this->routeSet->add(new Route\HiddenRoute($panelPath . '/stylesheet.css',
self::ADMIN_STYLESHEET_TO,
[
'via' => ['get'],
'format' => false
]
));
}
}
private function createAssetsRoute()
{
if (Rails::application()->config()->assets->enabled && !Rails::application()->config()->serve_static_assets) {
$prefix = ltrim(Rails::application()->config()->assets->prefix, '/');
$this->routeSet->set_assets_route(
$this->createRoute(
$prefix . '/*file',
'#',
[
'assets_route' => true,
'via' => ['get'],
'format' => false
]
)
);
}
}
}