Skip to content

Commit 5e5c69f

Browse files
committed
Merge branch 'master' into request-headers
2 parents 570c50b + ba5603e commit 5e5c69f

17 files changed

+283
-43
lines changed

README.markdown

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,47 @@ can amend the front controller to catch these exceptions and handle them.
374374
$response->output();
375375

376376

377+
User authentication
378+
-------------------
379+
380+
Need to secure a resource? Something like the following is a good pattern.
381+
382+
/**
383+
* @uri /secret
384+
*/
385+
class SecureResource extends Tonic\Resource {
386+
387+
/**
388+
* @method GET
389+
* @secure aUser aPassword
390+
*/
391+
function secret() {
392+
return 'My secret';
393+
}
394+
395+
function secure($username, $password) {
396+
if (
397+
isset($_SERVER['PHP_AUTH_USER']) && $_SERVER['PHP_AUTH_USER'] == $username &&
398+
isset($_SERVER['PHP_AUTH_PW']) && $_SERVER['PHP_AUTH_PW'] == $password
399+
) {
400+
return;
401+
}
402+
throw new Tonic\UnauthorizedException;
403+
}
404+
}
405+
406+
$app = new Tonic\Application();
407+
$request = new Tonic\Request();
408+
$resource = $app->getResource($request);
409+
try {
410+
$response = $resource->exec();
411+
} catch(Tonic\UnauthorizedException $e) {
412+
$response = new Tonic\Response(401);
413+
$response->wwwAuthenticate = 'Basic realm="My Realm"';
414+
}
415+
$response->output();
416+
417+
377418

378419
For more information, read the code. Start with the dispatcher "web/dispatch.php"
379420
and the Hello world in the "src/Tyrell" directory.

features/bootstrap/FeatureContext.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public function loadTheResource()
213213
public function theLoadedResourceShouldHaveAClassOf($className)
214214
{
215215
$loadedClassName = get_class($this->resource);
216-
if ($loadedClassName != $className) throw new Exception($loadedClassName);
216+
if ($loadedClassName != $className) throw new Exception($loadedClassName.' != '.$className);
217217
}
218218

219219
/**
@@ -416,4 +416,17 @@ public function theResourceShouldHaveTheURI($resourceName, $url)
416416
if (!$found) throw new Exception;
417417
}
418418

419+
/**
420+
* @Then /^the resource "([^"]*)" should have the condition "([^"]*)" with the parameters "([^"]*)"$/
421+
*/
422+
public function theResourceShouldHaveTheConditionWithTheParameters($className, $conditionName, $parameters)
423+
{
424+
$metadata = $this->app->getResourceMetadata($className);
425+
if ($parameters != join(',', $metadata['methods']['test']['foo'])) throw new Exception('Condition method not found');
426+
427+
$resource = new $className($this->app, new Request, array());
428+
$condition = call_user_func_array(array($resource, $conditionName), explode(',', $parameters));
429+
if ($condition != explode(',', $parameters)) throw new Exception('Condition parameters not returned');
430+
}
431+
419432
}

features/resource.feature

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,24 @@ Feature: HTTP resource object
9393
And a resource definition "child1" with URI "/child1" and priority of 1
9494
When I create an application object
9595
And I create a request object
96-
Then the loaded resource "child1" should respond with the method "method1"
96+
Then the loaded resource "child1" should respond with the method "method1"
97+
98+
Scenario: Condition methods should be passed all of the annotation parameters
99+
Given a class definition:
100+
"""
101+
/**
102+
* @uri /resource6
103+
*/
104+
class Resource6 extends Tonic\Resource {
105+
/**
106+
* @method get
107+
* @foo bar baz quux
108+
*/
109+
function test() {}
110+
function foo($bar, $baz, $quux) {
111+
return array($bar, $baz, $quux);
112+
}
113+
}
114+
"""
115+
When I create an application object
116+
Then the resource "Resource6" should have the condition "foo" with the parameters "bar,baz,quux"

src/Tonic/Application.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
*/
88
class Application
99
{
10+
/**
11+
* Application configuration options
12+
*/
13+
private $options = array();
14+
1015
/**
1116
* Metadata of the loaded resources
1217
*/
@@ -15,6 +20,7 @@ class Application
1520
public function __construct($options = array())
1621
{
1722
$this->baseUri = dirname($_SERVER['SCRIPT_NAME']);
23+
$this->options = $options;
1824

1925
// load resource metadata passed in via options array
2026
if (isset($options['resources']) && is_array($options['resources'])) {
@@ -88,7 +94,7 @@ public function loadResourceMetadata($uriSpace = NULL)
8894
public function mount($namespaceName, $uriSpace)
8995
{
9096
foreach ($this->resources as $className => $metadata) {
91-
if ($metadata['namespace'] == $namespaceName) {
97+
if ($metadata['namespace'][0] == $namespaceName) {
9298
foreach ($metadata['uri'] as $index => $uri) {
9399
$this->resources[$className]['uri'][$index][0] = '|^'.$uriSpace.substr($uri[0], 2);
94100
}
@@ -211,9 +217,9 @@ private function readResourceAnnotations($className)
211217
$classReflector = new \ReflectionClass($className);
212218

213219
$metadata['class'] = '\\'.$classReflector->getName();
214-
$metadata['namespace'] = $classReflector->getNamespaceName();
220+
$metadata['namespace'] = array($classReflector->getNamespaceName());
215221
$metadata['filename'] = $classReflector->getFileName();
216-
$metadata['priority'] = 1;
222+
$metadata['priority'] = array(1);
217223

218224
// get data from docComment
219225
$docComment = $this->parseDocComment($classReflector->getDocComment());
@@ -236,8 +242,9 @@ private function readResourceAnnotations($className)
236242
*/
237243
private function uriTemplateToRegex($uri)
238244
{
239-
preg_match_all('#((?<!\?):[^/]+|{[^0-9][^}]*}|\(.+?\))#', $uri, $params, PREG_PATTERN_ORDER);
240-
$return = array($uri);
245+
preg_match_all('#((?<!\?):[^/]+|{[^0-9][^}]*}|\(.+?\))#', $uri[0], $params, PREG_PATTERN_ORDER);
246+
#$return = array($uri);
247+
$return = $uri;
241248
if (isset($params[1])) {
242249
foreach ($params[1] as $index => $param) {
243250
if (substr($param, 0, 1) == ':') {
@@ -273,7 +280,7 @@ public function readMethodAnnotations($className)
273280
foreach ($docComment as $annotationName => $value) {
274281
$methodName = substr($annotationName, 1);
275282
if (method_exists($className, $methodName)) {
276-
$methodMetadata[$methodName] = $value;
283+
$methodMetadata[$methodName] = $value[0];
277284
}
278285
}
279286
$metadata[$methodReflector->getName()] = $methodMetadata;
@@ -298,9 +305,11 @@ private function parseDocComment($comment)
298305
if ($parts) {
299306
$key = array_shift($parts);
300307
if (isset($data[$key])) {
301-
$data[$key][] = trim(join(' ', $parts));
308+
//$data[$key][] = trim(join(' ', $parts));
309+
$data[$key][] = $parts;
302310
} else {
303-
$data[$key] = array(trim(join(' ', $parts)));
311+
//$data[$key] = array(trim(join(' ', $parts)));
312+
$data[$key] = array($parts);
304313
}
305314
}
306315
}

src/Tonic/ConditionException.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@
55
/**
66
* Exception to be thrown when a resource method condition fails
77
*/
8-
class ConditionException extends Exception {}
8+
class ConditionException extends Exception
9+
{
10+
protected $message = 'A method condition failed';
11+
}

src/Tonic/MetadataCache.php

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,33 @@
44

55
/**
66
* Cache resource metadata between invocations
7-
*
8-
* This class serializes the resource metadata and writes it to disk for reading in a later request.
97
*/
10-
class MetadataCache
8+
interface MetadataCache
119
{
12-
private $filename;
13-
14-
public function __construct($filename)
15-
{
16-
$this->filename = $filename;
17-
}
18-
1910
/**
2011
* Is there already cache file
2112
* @return boolean
2213
*/
23-
public function isCached()
24-
{
25-
return is_readable($this->filename);
26-
}
14+
public function isCached();
2715

2816
/**
2917
* Load the resource metadata from disk
3018
* @return str[]
3119
*/
32-
public function load()
33-
{
34-
return unserialize(file_get_contents($this->filename));
35-
}
20+
public function load();
3621

3722
/**
3823
* Save resource metadata to disk
3924
* @param str[] $resources Resource metadata
4025
* @return boolean
4126
*/
42-
public function save($resources)
43-
{
44-
return file_put_contents($this->filename, serialize($resources));
45-
}
27+
public function save($resources);
28+
29+
/**
30+
* Clear the cache
31+
*/
32+
public function clear();
4633

47-
public function __toString()
48-
{
49-
return $this->filename;
50-
}
34+
public function __toString();
5135

5236
}

src/Tonic/MetadataCacheAPC.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace Tonic;
4+
5+
/**
6+
* Cache resource metadata between invocations
7+
*
8+
* This class writes the resource metadata to APC for reading in a later request.
9+
*/
10+
class MetadataCacheAPC implements MetadataCache
11+
{
12+
const CACHENAME = 'tonicCache';
13+
14+
/**
15+
* Is there already cache file
16+
* @return boolean
17+
*/
18+
public function isCached()
19+
{
20+
return apc_exists(self::CACHENAME);
21+
}
22+
23+
/**
24+
* Load the resource metadata from disk
25+
* @return str[]
26+
*/
27+
public function load()
28+
{
29+
return apc_fetch(self::CACHENAME);
30+
}
31+
32+
/**
33+
* Save resource metadata to disk
34+
* @param str[] $resources Resource metadata
35+
* @return boolean
36+
*/
37+
public function save($resources)
38+
{
39+
return apc_store(self::CACHENAME, $resources);
40+
}
41+
42+
public function clear()
43+
{
44+
apc_delete(self::CACHENAME);
45+
}
46+
47+
public function __toString()
48+
{
49+
$info = apc_cache_info('user');
50+
return 'Metadata for '.count($this->load()).' resources stored in APC at '.date('r', $info['cache_list'][0]['creation_time']);
51+
}
52+
53+
}

src/Tonic/MetadataCacheFile.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Tonic;
4+
5+
/**
6+
* Cache resource metadata between invocations
7+
*
8+
* This class serializes the resource metadata and writes it to disk for reading in a later request.
9+
*/
10+
class MetadataCacheFile implements MetadataCache
11+
{
12+
private $filename;
13+
14+
public function __construct($filename)
15+
{
16+
$this->filename = $filename;
17+
}
18+
19+
/**
20+
* Is there already cache file
21+
* @return boolean
22+
*/
23+
public function isCached()
24+
{
25+
return is_readable($this->filename);
26+
}
27+
28+
/**
29+
* Load the resource metadata from disk
30+
* @return str[]
31+
*/
32+
public function load()
33+
{
34+
return unserialize(file_get_contents($this->filename));
35+
}
36+
37+
/**
38+
* Save resource metadata to disk
39+
* @param str[] $resources Resource metadata
40+
* @return boolean
41+
*/
42+
public function save($resources)
43+
{
44+
return file_put_contents($this->filename, serialize($resources));
45+
}
46+
47+
public function clear()
48+
{
49+
@unlink($this->filename);
50+
}
51+
52+
public function __toString()
53+
{
54+
return 'Metadata for '.count($this->load()).' resources stored in file "'.$this->filename.'" at '.date('r', filemtime($this->filename));
55+
}
56+
57+
}

src/Tonic/MethodNotAllowedException.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
class MethodNotAllowedException extends Exception
66
{
77
protected $code = 405;
8+
protected $message = 'The HTTP method specified in the Request-Line is not allowed for the resource identified by the Request-URI';
89
}

src/Tonic/NotAcceptableException.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
class NotAcceptableException extends Exception
66
{
77
protected $code = 406;
8+
protected $message = 'The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request';
89
}

0 commit comments

Comments
 (0)