├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml.dist ├── src └── Flintstones │ └── Rest │ ├── PimpleDecoderProvider.php │ └── ServiceProvider.php └── tests ├── Flintstones └── Rest │ └── Tests │ └── ServiceProviderTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Igor Wiedler 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flintstones RestServiceProvider 2 | 3 | Adding some REST capabilities to [Silex][1], so you can 4 | more easily build RESTful APIs. 110% Buzzword-Driven. 5 | 6 | You get accept header support and request body decoding. 7 | 8 | ## Registering 9 | 10 | $app->register(new Flintstones\Rest\ServiceProvider(), array( 11 | 'rest.fos.class_path' => __DIR__.'/vendor', 12 | 'rest.serializer.class_path' => __DIR__.'/vendor', 13 | )); 14 | 15 | ## Running the tests 16 | 17 | $ curl -s https://getcomposer.org/installer | php 18 | $ php composer.phar install 19 | $ phpunit 20 | 21 | ## Credits 22 | 23 | * [FOSRestBundle][2] 24 | * [Symfony2 Serializer Component][3] 25 | 26 | ## License 27 | 28 | The RestServiceProvider is licensed under the MIT license. 29 | 30 | [1]: http://silex-project.org 31 | [2]: https://github.com/FriendsOfSymfony/FOSRestBundle 32 | [3]: https://github.com/symfony/Serializer 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flintstones/rest", 3 | "description": "Adding some REST capabilities to Silex, so you can more easily build RESTful APIs. 110% Buzzword-Driven.", 4 | "keywords": ["rest", "silex"], 5 | "authors": [ 6 | { 7 | "name": "Igor Wiedler", 8 | "email": "igor@wiedler.ch" 9 | } 10 | ], 11 | "require": { 12 | "friendsofsymfony/rest-bundle": "*", 13 | "symfony/serializer": "2.1.x", 14 | "silex/silex": "1.0.*" 15 | }, 16 | "autoload": { 17 | "psr-0": { "Flintstones": "src" } 18 | }, 19 | "minimum-stability": "dev" 20 | } 21 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "fbaad4ca06268918d11410b6f0e70525", 3 | "packages": [ 4 | { 5 | "package": "doctrine/common", 6 | "version": "2.3.x-dev", 7 | "source-reference": "2ec4bc6db13db6a0976a16b71058083c55cbcdbd", 8 | "commit-date": "1343847076" 9 | }, 10 | { 11 | "package": "friendsofsymfony/rest", 12 | "version": "dev-master", 13 | "alias-pretty-version": "0.7.x-dev", 14 | "alias-version": "0.7.9999999.9999999-dev" 15 | }, 16 | { 17 | "package": "friendsofsymfony/rest", 18 | "version": "dev-master", 19 | "source-reference": "5ea703f5a3f5c7110cadeccb91b212c0eb7ea2d0", 20 | "commit-date": "1339701893" 21 | }, 22 | { 23 | "package": "friendsofsymfony/rest-bundle", 24 | "version": "dev-master", 25 | "alias-pretty-version": "0.8.x-dev", 26 | "alias-version": "0.8.9999999.9999999-dev" 27 | }, 28 | { 29 | "package": "friendsofsymfony/rest-bundle", 30 | "version": "dev-master", 31 | "source-reference": "d3f15d6c913d25fe5b29185e6e6be510afd09c73", 32 | "commit-date": "1345237156" 33 | }, 34 | { 35 | "package": "jms/metadata", 36 | "version": "dev-master", 37 | "alias-pretty-version": "1.2.x-dev", 38 | "alias-version": "1.2.9999999.9999999-dev" 39 | }, 40 | { 41 | "package": "jms/metadata", 42 | "version": "dev-master", 43 | "source-reference": "7c58d49ff13de62464837c3c7d91aff1deb2c442", 44 | "commit-date": "1345552810" 45 | }, 46 | { 47 | "package": "jms/serializer-bundle", 48 | "version": "dev-master", 49 | "alias-pretty-version": "0.9.x-dev", 50 | "alias-version": "0.9.9999999.9999999-dev" 51 | }, 52 | { 53 | "package": "jms/serializer-bundle", 54 | "version": "dev-master", 55 | "source-reference": "1f308a587742246e87e580290f3bae23073d1d70", 56 | "commit-date": "1343641450" 57 | }, 58 | { 59 | "package": "pimple/pimple", 60 | "version": "dev-master", 61 | "alias-pretty-version": "1.0.x-dev", 62 | "alias-version": "1.0.9999999.9999999-dev" 63 | }, 64 | { 65 | "package": "pimple/pimple", 66 | "version": "dev-master", 67 | "source-reference": "db836b8cfadc0f39dacafa2bf311a1ab603600bb", 68 | "commit-date": "1343051648" 69 | }, 70 | { 71 | "package": "sensio/framework-extra-bundle", 72 | "version": "dev-master", 73 | "alias-pretty-version": "2.1.x-dev", 74 | "alias-version": "2.1.9999999.9999999-dev" 75 | }, 76 | { 77 | "package": "sensio/framework-extra-bundle", 78 | "version": "dev-master", 79 | "source-reference": "ade2d53b20a9fc3428f04c863b3fd4fca0b62181", 80 | "commit-date": "1344072374" 81 | }, 82 | { 83 | "package": "silex/silex", 84 | "version": "dev-master", 85 | "alias-pretty-version": "1.0.x-dev", 86 | "alias-version": "1.0.9999999.9999999-dev" 87 | }, 88 | { 89 | "package": "silex/silex", 90 | "version": "dev-master", 91 | "source-reference": "6d7cf048c704c0c58276b01eccfacab4dd11d013", 92 | "commit-date": "1344453846" 93 | }, 94 | { 95 | "package": "symfony/config", 96 | "version": "dev-master", 97 | "alias-pretty-version": "2.1.x-dev", 98 | "alias-version": "2.1.9999999.9999999-dev" 99 | }, 100 | { 101 | "package": "symfony/config", 102 | "version": "dev-master", 103 | "source-reference": "f68ad44f3ee1603ce96387bacba0ecba488c2a33", 104 | "commit-date": "1344599303" 105 | }, 106 | { 107 | "package": "symfony/dependency-injection", 108 | "version": "dev-master", 109 | "alias-pretty-version": "2.1.x-dev", 110 | "alias-version": "2.1.9999999.9999999-dev" 111 | }, 112 | { 113 | "package": "symfony/dependency-injection", 114 | "version": "dev-master", 115 | "source-reference": "98fa735a7ef5dc07c43bfe6faa4367a983b6ba6f", 116 | "commit-date": "1344599303" 117 | }, 118 | { 119 | "package": "symfony/event-dispatcher", 120 | "version": "dev-master", 121 | "alias-pretty-version": "2.1.x-dev", 122 | "alias-version": "2.1.9999999.9999999-dev" 123 | }, 124 | { 125 | "package": "symfony/event-dispatcher", 126 | "version": "dev-master", 127 | "source-reference": "76c76f62702b09e0f182ae618be0f1d79e2a711f", 128 | "commit-date": "1345066712" 129 | }, 130 | { 131 | "package": "symfony/filesystem", 132 | "version": "dev-master", 133 | "alias-pretty-version": "2.1.x-dev", 134 | "alias-version": "2.1.9999999.9999999-dev" 135 | }, 136 | { 137 | "package": "symfony/filesystem", 138 | "version": "dev-master", 139 | "source-reference": "72acf65c2390c9066e1174d269a4e146ffad9296", 140 | "commit-date": "1344875591" 141 | }, 142 | { 143 | "package": "symfony/framework-bundle", 144 | "version": "dev-master", 145 | "alias-pretty-version": "2.1.x-dev", 146 | "alias-version": "2.1.9999999.9999999-dev" 147 | }, 148 | { 149 | "package": "symfony/framework-bundle", 150 | "version": "dev-master", 151 | "source-reference": "4f0d296b4414cb5322e7ee45af117e5234bc75d0", 152 | "commit-date": "1345278333" 153 | }, 154 | { 155 | "package": "symfony/http-foundation", 156 | "version": "dev-master", 157 | "alias-pretty-version": "2.1.x-dev", 158 | "alias-version": "2.1.9999999.9999999-dev" 159 | }, 160 | { 161 | "package": "symfony/http-foundation", 162 | "version": "dev-master", 163 | "source-reference": "8d5e7f909fa519853e71bcfc57a1da8c637ebcbb", 164 | "commit-date": "1345191498" 165 | }, 166 | { 167 | "package": "symfony/http-kernel", 168 | "version": "dev-master", 169 | "alias-pretty-version": "2.1.x-dev", 170 | "alias-version": "2.1.9999999.9999999-dev" 171 | }, 172 | { 173 | "package": "symfony/http-kernel", 174 | "version": "dev-master", 175 | "source-reference": "0d0ee371ab0425f3399dba9e25d7ad98ca5c0d62", 176 | "commit-date": "1344074675" 177 | }, 178 | { 179 | "package": "symfony/routing", 180 | "version": "dev-master", 181 | "alias-pretty-version": "2.1.x-dev", 182 | "alias-version": "2.1.9999999.9999999-dev" 183 | }, 184 | { 185 | "package": "symfony/routing", 186 | "version": "dev-master", 187 | "source-reference": "v2.1.0-RC1", 188 | "commit-date": "1343744077" 189 | }, 190 | { 191 | "package": "symfony/serializer", 192 | "version": "dev-master", 193 | "alias-pretty-version": "2.1.x-dev", 194 | "alias-version": "2.1.9999999.9999999-dev" 195 | }, 196 | { 197 | "package": "symfony/serializer", 198 | "version": "dev-master", 199 | "source-reference": "v2.1.0-RC1", 200 | "commit-date": "1343307511" 201 | }, 202 | { 203 | "package": "symfony/templating", 204 | "version": "dev-master", 205 | "alias-pretty-version": "2.1.x-dev", 206 | "alias-version": "2.1.9999999.9999999-dev" 207 | }, 208 | { 209 | "package": "symfony/templating", 210 | "version": "dev-master", 211 | "source-reference": "v2.1.0-RC1", 212 | "commit-date": "1343982509" 213 | }, 214 | { 215 | "package": "symfony/translation", 216 | "version": "dev-master", 217 | "alias-pretty-version": "2.1.x-dev", 218 | "alias-version": "2.1.9999999.9999999-dev" 219 | }, 220 | { 221 | "package": "symfony/translation", 222 | "version": "dev-master", 223 | "source-reference": "d7a6083e76a3af3f247bff523bc47b41e7208a87", 224 | "commit-date": "1344955085" 225 | } 226 | ], 227 | "packages-dev": null, 228 | "aliases": [ 229 | 230 | ], 231 | "minimum-stability": "dev", 232 | "stability-flags": [ 233 | 234 | ] 235 | } 236 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./Tests 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Flintstones/Rest/PimpleDecoderProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Flintstones\Rest; 13 | 14 | use FOS\Rest\Decoder\DecoderProviderInterface; 15 | 16 | class PimpleDecoderProvider implements DecoderProviderInterface 17 | { 18 | private $container; 19 | 20 | private $decoders; 21 | 22 | public function __construct(\Pimple $container, array $decoders) 23 | { 24 | $this->container = $container; 25 | $this->decoders = $decoders; 26 | } 27 | 28 | public function supports($format) 29 | { 30 | return isset($this->container[$this->decoders[$format]]); 31 | } 32 | 33 | public function getDecoder($format) 34 | { 35 | if (!$this->supports($format)) { 36 | throw new \InvalidArgumentException(sprintf("Format '%s' is not supported by PimpleDecoderProvider.", $format)); 37 | } 38 | 39 | return $this->container[$this->decoders[$format]]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Flintstones/Rest/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Flintstones\Rest; 13 | 14 | use FOS\RestBundle\EventListener\BodyListener; 15 | use FOS\RestBundle\EventListener\FormatListener; 16 | use FOS\Rest\Util\FormatNegotiator; 17 | use FOS\Rest\Decoder\JsonDecoder; 18 | use FOS\Rest\Decoder\XmlDecoder; 19 | 20 | use Silex\Application; 21 | use Silex\ServiceProviderInterface; 22 | 23 | use Symfony\Component\HttpKernel\KernelEvents as HttpKernelEvents; 24 | use Symfony\Component\Serializer\Serializer; 25 | use Symfony\Component\Serializer\Encoder\JsonEncoder; 26 | use Symfony\Component\Serializer\Encoder\XmlEncoder; 27 | 28 | class ServiceProvider implements ServiceProviderInterface 29 | { 30 | public function register(Application $app) 31 | { 32 | $app['rest.serializer'] = $app->share(function () { 33 | $encoders = array ( 34 | 'json' => new JsonEncoder(), 35 | 'xml' => new XmlEncoder() 36 | ); 37 | $serializer = new Serializer(array(), $encoders); 38 | return $serializer; 39 | }); 40 | 41 | if (!isset($app['rest.priorities'])) { 42 | $app['rest.priorities'] = array('json', 'xml'); 43 | } 44 | 45 | $app['rest.format_negotiator'] = function ($app) { 46 | return new FormatNegotiator($app['request'], $app['rest.priorities']); 47 | }; 48 | 49 | $app['rest.decoder.json'] = function ($app) { 50 | return new JsonDecoder(); 51 | }; 52 | 53 | $app['rest.decoder.xml'] = function ($app) { 54 | return new XmlDecoder(); 55 | }; 56 | 57 | $app['rest.decoders'] = isset($app['rest.decoders']) ? $app['rest.decoders'] : array( 58 | 'json' => 'rest.decoder.json', 59 | 'xml' => 'rest.decoder.xml', 60 | ); 61 | 62 | if (isset($app['rest.fos.class_path'])) { 63 | $app['autoloader']->registerNamespace('FOS\RestBundle', $app['rest.fos.class_path']); 64 | } 65 | 66 | if (isset($app['rest.serializer.class_path'])) { 67 | $app['autoloader']->registerNamespace('Symfony\Component\Serializer', $app['rest.serializer.class_path']); 68 | } 69 | 70 | $listener = new BodyListener(new PimpleDecoderProvider($app, $app['rest.decoders'])); 71 | $app['dispatcher']->addListener(HttpKernelEvents::REQUEST, array($listener, 'onKernelRequest')); 72 | 73 | $listener = new FormatListener($app['rest.format_negotiator'], 'html', $app['rest.priorities']); 74 | $app['dispatcher']->addListener(HttpKernelEvents::CONTROLLER, array($listener, 'onKernelController'), 10); 75 | } 76 | 77 | /** 78 | * Bootstraps the application. 79 | * 80 | * This method is called after all services are registers 81 | * and should be used for "dynamic" configuration (whenever 82 | * a service must be requested). 83 | */ 84 | public function boot(Application $app) { 85 | // TODO: Implement boot() method. 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Flintstones/Rest/Tests/ServiceProviderTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Flintstones\Rest\Tests; 13 | 14 | use Flintstones\Rest\ServiceProvider as RestServiceProvider; 15 | 16 | use Silex\Application; 17 | 18 | use Symfony\Component\HttpFoundation\Request; 19 | use Symfony\Component\HttpKernel\HttpKernelInterface; 20 | 21 | /** 22 | * RestExtension test cases. 23 | * 24 | * @author Igor Wiedler 25 | */ 26 | class ServiceProviderTest extends \PHPUnit_Framework_TestCase 27 | { 28 | public function setUp() 29 | { 30 | if (!is_file(__DIR__.'/../../../../vendor/friendsofsymfony/rest-bundle/FOS/RestBundle/FOSRestBundle.php')) { 31 | $this->markTestSkipped('FOS\RestBundle submodule was not installed.'); 32 | } 33 | } 34 | 35 | public function testRegister() 36 | { 37 | $app = new Application(); 38 | 39 | $app->register(new RestServiceProvider(), array( 40 | 'rest.fos.class_path' => __DIR__.'/../../../../vendor', 41 | 'rest.serializer.class_path' => __DIR__.'/../../../../vendor', 42 | )); 43 | 44 | $this->assertInstanceOf('Symfony\Component\Serializer\Serializer', $app['rest.serializer']); 45 | 46 | return $app; 47 | } 48 | 49 | /** 50 | * @depends testRegister 51 | */ 52 | public function testDecodingOfRequestBody(Application $app) 53 | { 54 | $app->put('/api/user/{id}', function ($id) use ($app) { 55 | return $app['request']->get('name'); 56 | }); 57 | 58 | $request = Request::create('/api/user/1', 'put', array(), array(), array(), array(), '{"name":"igor"}'); 59 | $request->headers->set('Content-Type', 'application/json'); 60 | $response = $app->handle($request, HttpKernelInterface::MASTER_REQUEST, false); 61 | 62 | $this->assertEquals('igor', $response->getContent()); 63 | } 64 | 65 | /** 66 | * @depends testRegister 67 | */ 68 | public function testFormatDetection(Application $app) 69 | { 70 | $app->get('/api/user/{id}', function ($id) use ($app) { 71 | return $app['request']->getRequestFormat(); 72 | }); 73 | 74 | $request = Request::create('/api/user/1'); 75 | $request->headers->set('Accept', 'application/json'); 76 | $response = $app->handle($request, HttpKernelInterface::MASTER_REQUEST, false); 77 | 78 | $this->assertEquals('json', $response->getContent()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('FlintstonesRest', __DIR__.'/../src'); 5 | $loader->add('Flintstones\Tests\Rest', __DIR__); 6 | --------------------------------------------------------------------------------