Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<? | ||
|
||
class Oxygen_Router_Iterator extends Oxygen_Object implements Iterator { | ||
|
||
private $owner = null; | ||
private $internal = null; | ||
private $useKeys = false; | ||
|
||
public function __construct($owner) { | ||
$this->owner = $owner; | ||
$data = $owner->getData(); | ||
if (is_array($data)) { | ||
$data = new ArrayObject($data); | ||
$this->useKeys = true; | ||
} | ||
$this->internal = $data->getIterator(); | ||
} | ||
|
||
public function current(){ | ||
return $this->owner->wrap($this->internal->current()); | ||
} | ||
|
||
public function key(){ | ||
return $this->owner->formatKey($this->useKeys | ||
? $this->internal->key() | ||
: $this->internal->current() | ||
); | ||
} | ||
|
||
public function next() { | ||
$this->internal->next(); | ||
} | ||
|
||
public function valid() { | ||
return $this->internal->valid(); | ||
} | ||
|
||
public function rewind() { | ||
$this->internal->rewind(); | ||
} | ||
|
||
} | ||
|
||
|
||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
<? | ||
class Oxygen_Router extends Oxygen_Object | ||
implements IteratorAggregate, Countable, ArrayAccess | ||
|
||
{ | ||
|
||
const TYPES_REGEXP = '/^(?:(int)|(str)|(url)|(any)|\/([^\/]*(?:\\\\\/[^\/]*)*)\/)$/'; | ||
const PARAM_REGEXP = '/{([0-9A-Z\.a-z_]+):([^{}]+)}/'; | ||
|
||
const PARAM_GUARD_REPLACE = '#\\1#'; | ||
const PARAM_GUARD_REGEXP = '/#([0-9A-Z\.a-z_]+)#/e'; | ||
|
||
const INT_REGEXP_BARE = '-?[0-9]+'; | ||
const STR_REGEXP_BARE = '[^\/]+'; | ||
const URL_REGEXP_BARE = '[^\/&]+'; | ||
const ANY_REGEXP_BARE = '.+'; | ||
|
||
const SINGLE = 0; | ||
const COLLECTION = 1; | ||
const ARRAY_TYPE = 2; | ||
|
||
const INVALID_PARAM_TYPE = 'Invalid parameter type {0}'; | ||
|
||
private $extract = ''; | ||
private $regexp = ''; | ||
private $pattern = ''; | ||
private $data = null; | ||
private $params = array(); | ||
private $parseTransform = array(); | ||
private $formatTransform = array(); | ||
private $autoPopulate = false; | ||
private $autoRoute = ''; | ||
|
||
public $type = self::COLLECTION; | ||
|
||
|
||
public function setWrap($wrap, $method = false) { | ||
if($method !== false) { | ||
$this->wrapperClass = ''; | ||
$this->wrap = array($wrap, $method); | ||
} else if($wrap === false) { | ||
$this->wrapperClass = ''; | ||
$this->wrap = array(&$this,'sameObject'); | ||
} else if(is_string($wrap)) { | ||
$this->wrapperClass = $wrap; | ||
$this->wrap = array(&$this,'staticWrap'); | ||
} else { | ||
$this->wrapperClass = ''; | ||
$this->wrap = $wrap; | ||
} | ||
return $this; | ||
} | ||
|
||
public function setUnwrap($unwrap, $method = false) { | ||
if($method !== false) { | ||
$this->unwrapMethod = ''; | ||
$this->unwrap = array($unwrap, $method); | ||
} else if($unwrap === false) { | ||
$this->unwrapMethod = ''; | ||
$this->unwrap = array(&$this,'sameObject'); | ||
} else if (is_string($unwrap)) { | ||
$this->unwrapMethod = $unwrap; | ||
$this->unwrap = array(&$this,'staticUnwrap'); | ||
} else { | ||
$this->unwrapMethod = ''; | ||
$this->unwrap = $unwrap; | ||
} | ||
return $this; | ||
} | ||
|
||
public function __construct($pattern, $data, $wrap = false, $unwrap = false) { | ||
$this->pattern = $pattern; | ||
$this->data = $data; | ||
$this->setWrap($wrap); | ||
$this->setUnwrap($unwrap); | ||
} | ||
|
||
private function &sameObject(&$obj){ | ||
return $obj; | ||
} | ||
|
||
private function staticWrap($obj){ | ||
return $this->new_($this->wrapperClass,array($obj)); | ||
} | ||
|
||
private function staticUnwrap($obj){ | ||
return $obj->{$this->unwrapMethod}(); | ||
} | ||
|
||
public function wrap($obj){ | ||
return call_user_func($this->wrap,$obj); | ||
} | ||
|
||
public function unwrap($obj){ | ||
return call_user_func($this->unwrap,$obj); | ||
} | ||
|
||
private function url($data, $encode) { | ||
return $encode | ||
? urldecode($data) | ||
: urldecode($data) | ||
; | ||
} | ||
private function any($data, $encode) { | ||
return $data; | ||
} | ||
|
||
private function getTypeConfig($type){ | ||
if(!preg_match(self::TYPES_REGEXP, $type, $match)) { | ||
throw new Exception('Unrecognized type'); | ||
} | ||
switch (count($match)) { | ||
case 2: return array(self::INT_REGEXP_BARE, 'any'); | ||
case 3: return array(self::STR_REGEXP_BARE, 'any'); | ||
case 4: return array(self::URL_REGEXP_BARE, 'url'); | ||
case 5: return array(self::URL_REGEXP_BARE, 'any'); | ||
case 6: return $match[3]; | ||
} | ||
} | ||
|
||
public function formatKey($data){ | ||
$params = array(); | ||
foreach($this->params as $name => $config) { | ||
list($regexp, $transform) = $config; | ||
if(!$transform) $transform = 'any'; | ||
$value = $this->$transform( | ||
(($this->type === self::ARRAY_TYPE || $this->autoPopulate) | ||
? $data | ||
: $data[$name] | ||
), true | ||
); | ||
assert('preg_match($regexp, $value)'); | ||
$params[$name] = $value; | ||
} | ||
return preg_replace(self::PARAM_REGEXP . 'e', "\$params['\\1']", $this->pattern); | ||
} | ||
|
||
public function parseKey($str) { | ||
if(!$this->matchKey($str, $key)) { | ||
assert('$str!=$str'); | ||
} | ||
return $key; | ||
} | ||
|
||
public function getData() { | ||
return $this->data; | ||
} | ||
|
||
public function getType() { | ||
return $this->type; | ||
} | ||
|
||
public function getRegexp() { | ||
return $this->regexp; | ||
} | ||
|
||
public function offsetExists($offset) { | ||
if (!$this->matchKey($offset, $key)) return false; | ||
return $this->autoPopulate || isset($this->data[$key]); | ||
} | ||
|
||
//TODO: Change return mode to reference on php 5.3.4+ | ||
public function offsetGet($offset) { | ||
if ($this->autoPopulate) { | ||
$key = $this->parseKey($offset); | ||
if(!isset($this->data[$offset])) { | ||
$this->data[$offset] = $this->wrap($key); | ||
} | ||
return $this->data[$offset]; | ||
} else { | ||
return $this->wrap( | ||
$this->data[ | ||
$this->parseKey($offset) | ||
] | ||
); | ||
} | ||
} | ||
|
||
public function offsetSet($offset, $value) { | ||
$this->data[$this->parseKey($offset)] = $this->unwrap($value); | ||
} | ||
|
||
public function offsetUnset($offset) { | ||
unset($this->data[$this->parseKey($offset)]); | ||
} | ||
|
||
public function count(){ | ||
$this->ensureAuto(); | ||
return count($this->data); | ||
} | ||
|
||
private function ensureAuto() { | ||
if(!$this->autoPopulate) return; | ||
if(count($this->data)>0) return; | ||
if(!$this->autoRoute) return; | ||
$key = $this->parseKey($this->autoRoute); | ||
$this->data[$this->autoRoute] = $this->wrap($key); | ||
} | ||
|
||
public function getIterator() { | ||
$this->ensureAuto(); | ||
return $this->scope->Oxygen_Router_Iterator($this); | ||
} | ||
|
||
private function extractKey($match) { | ||
$key = array(); | ||
foreach($this->params as $name => $config){ | ||
$key[$name] = $this->{$config[1]}($match[$name], false); | ||
} | ||
return $key; | ||
} | ||
|
||
public function matchKey($str, &$key) { | ||
if(preg_match($this->extract, $str, $match)) { | ||
switch($this->type){ | ||
case self::SINGLE: | ||
reset($match); | ||
$key = current($match); | ||
break; | ||
case self::COLLECTION: | ||
$key = $this->extractKey($match); | ||
break; | ||
case self::ARRAY_TYPE: | ||
$key = $this->extractKey($match); | ||
reset($key); | ||
$key = current($key); | ||
break; | ||
} | ||
return true; | ||
} else { | ||
$key = array(); | ||
return false; | ||
} | ||
} | ||
|
||
private function compile() { | ||
if(0 < preg_match_all(self::PARAM_REGEXP, $this->pattern, $match)){ | ||
$names = $match[1]; | ||
$types = $match[2]; | ||
$params = array(); | ||
foreach($names as $i => $name){ | ||
if(isset($params[$name])) { | ||
$this->throw_Exception(self::ROUTE_PARAM_REDEFINED); | ||
} else { | ||
$config = self::getTypeConfig($t = $types[$i]); | ||
$params[$name] = $config[0]; | ||
$this->params[$name] = array('/'.$config[0].'/',$config[1]); | ||
} | ||
} | ||
$compiled = preg_replace(self::PARAM_REGEXP,self::PARAM_GUARD_REPLACE, $this->pattern); | ||
$compiled = preg_quote($compiled,'/'); | ||
$this->extract = '/' . preg_replace ( | ||
self::PARAM_GUARD_REGEXP, | ||
"'(?P<\\1>'.\$params['\\1'].')'", | ||
$compiled | ||
) . '/'; | ||
$this->regexp = preg_replace(self::PARAM_GUARD_REGEXP,"\$params['\\1']", $compiled); | ||
if(is_array($this->data)){ | ||
assert('count($names) === 1'); | ||
$this->type = self::ARRAY_TYPE; | ||
} else if(is_string($this->data) || !$this->data) { | ||
$this->autoRoute = $this->data; | ||
$this->data = array(); | ||
$this->autoPopulate = true; | ||
} else { | ||
$this->type = self::COLLECTION; | ||
} | ||
} else { | ||
$this->type = self::SINGLE; | ||
$this->regexp = preg_quote($this->pattern, '/'); | ||
$this->extract = '/' . $this->regexp . '/'; | ||
$data = array(); | ||
$data[$this->pattern] = $this->data; | ||
$this->data = $data; | ||
} | ||
} | ||
|
||
|
||
public function __complete($scope) { | ||
echo "COMPLETE"; | ||
$this->compile(); | ||
} | ||
} | ||
|
||
|
||
?> |