PSX Framework

About

PSX is a framework written in PHP to create RESTful APIs. It focuses on providing all tools you need to build a clean and extensible API. PSX does not restrict you to any libraries instead you can use the tools you like. By default it uses well known opensource libraries like Monolog, Doctrine DBAL and the Symfony Event Dispatcher. The following chapter shows some features of PSX which should give you an first impression about PSX. Talk is cheap show me the code!

Schema definition

PHP

   1 <?php
   2 
   3 class NewsSchema extends PSX\Data\SchemaAbstract
   4 {
   5     public function getDefinition()
   6     {
   7         $sb = $this->getSchemaBuilder('news');
   8         $sb->string('title')
   9             ->setDescription('Title description')
  10             ->setPattern('[A-z]{3,16}');
  11         $sb->complexType($this->getSchema('Acme\AuthorSchema'));
  12         $sb->string('content')
  13             ->setDescription('Content description')
  14             ->setMinLength(3)
  15             ->setMaxLength(512)
  16             ->setRequired(true);
  17 
  18         return $sb->getProperty();
  19     }
  20 }

JSON

   1 {
   2     "title": "acme news",
   3     "author": {
   4         "name": "foo"
   5     },
   6     "content": "lorem ipsum"
   7 }

PSX gives you the possibility to define an clear schema for your request and response data. It is possible to parse and validate data based on the defined schema. The schema can also be used to generate API documentation or other schema formats like XSD or JsonSchema. In the example we define an schema which would match the given JSON request.

Request parsing

PHP

   1 <?php
   2 
   3 class Controller extends ControllerAbstract
   4 {
   5     /**
   6      * @Inject
   7      * @var PSX\Data\Schema\SchemaManager
   8      */
   9     protected $schemaManager;
  10 
  11     public function doInsert()
  12     {
  13         $schema = $this->schemaManager->getSchema('Acme\NewsSchema');
  14         $news   = $this->import($schema);
  15 
  16         // @TODO do something with the news
  17         // $news->getTitle();
  18         // $news->getAuthor()->getName();
  19         // $news->getContent();
  20     }
  21 }

Based on the defined schema it is possible to parse incomming request data into an object graph. The returned object has the same structure as the defined schema.

In the example we import the request data based on the schema definition which also validates the data. The request can be either JSON or XML.

API testing

PHP

   1 <?php
   2 
   3 class HelloWorldApiTest extends ControllerTestCase
   4 {
   5     public function testHelloWorld()
   6     {
   7         // create http request
   8         $request  = new GetRequest('http://localhost.com/foo');
   9         $request->addHeader('Accept', 'application/json');
  10 
  11         // create http response
  12         $response = new Response();
  13         $body     = new TempStream(fopen('php://memory', 'r+'));
  14         $response->setBody($body);
  15 
  16         // send request the response gets written to the body
  17         $controller = $this->loadController($request, $response);
  18         $data       = json_decode((string) $body);
  19 
  20         $this->assertArrayHasKey('hello', $data);
  21         $this->assertEquals($data['hello'], 'world');
  22     }
  23 
  24     protected function getPaths()
  25     {
  26         return array(
  27             '/foo' => 'Acme\TestViewController::doIndex',
  28         );
  29     }
  30 }

Because PSX is build around an HTTP (PSR-7) request and response object we can easily test our API code. We dont need to start an webserver or mock the request we can simply call our controller from the test. Internally this is the same code as when we make an call from an webserver except that we manually create the HTTP request and response.

In this way we can easily make an integration test for an controller by looking at the actually response. In our example we call the doIndex method of the controller and check whether the response is an JSON object {"hello": "world"}.

Dependency managment

PHP

   1 <?php
   2 
   3 class FooController extends PSX\ControllerAbstract
   4 {
   5     /**
   6      * @Inject
   7      * @var Doctrine\DBAL\Connection
   8      */
   9     protected $connection;
  10 }

PHP

   1 <?php
   2 
   3 class DefaultContainer extends Container
   4 {
   5     /**
   6      * @return Doctrine\DBAL\Connection
   7      */
   8     public function getConnection()
   9     {
  10         $config = new Configuration();
  11         $params = array(
  12             'dbname'   => $this->get('config')->get('psx_sql_db'),
  13             'user'     => $this->get('config')->get('psx_sql_user'),
  14             'password' => $this->get('config')->get('psx_sql_pw'),
  15             'host'     => $this->get('config')->get('psx_sql_host'),
  16             'driver'   => 'pdo_mysql',
  17         );
  18 
  19         return DriverManager::getConnection($params, $config);
  20     }
  21 }

PSX comes with an fast DI container which implements the Symfony DI container interface. Instead of configuration files it uses simple traits where each method is an service definition.

Inside an controller/command it is not possible to access the DI container instead each dependency must be specified as property with an @Inject annotation. PSX injects the dependency into this property. This gives an clear overview of the dependencies for each controller/command which eventually should help you to decouple your application code from the framework.

Routing

Routing

   1 GET      /news             Acme\News\Application\Index::doIndex
   2 GET      /news/:news_id    Acme\News\Application\Index::doDetail
   3 GET      /bar/$foo<[0-9]+> Acme\News\Application\Article
   4 GET      /download/*file   Acme\News\Application\Download
   5 GET|POST /bar              Acme\News\Application\BarApi

PHP

   1 <?php
   2 
   3 class Index extends ControllerAbstract
   4 {
   5     public function doDetail()
   6     {
   7         $newsId = $this->getUriFragment('news_id');
   8 
   9         // @TODO work with the news id
  10     }
  11 }

PSX uses a simple routing file which was inspired by the Java Play-Framework. We can specify the allowed request methods, the path and the controller which should be called. In the example we access the dynamic part of the path.

Response generation

PHP

   1 <?php
   2 
   3 class Index extends ControllerAbstract
   4 {
   5     public function doIndex()
   6     {
   7         $atom = new Atom();
   8         $atom->setTitle('lorem ipsum');
   9 
  10         // @TODO add more atom entries
  11         $entry = new Atom\Entry();
  12         $entry->setTitle('foobar');
  13 
  14         $atom->add($entry);
  15 
  16         $this->setBody($atom);
  17     }
  18 }

PSX offers built in data models to generate Atom, RSS and ActivityStream responses. In the example we generate an simple Atom feed.

Controller

PHP

   1 <?php
   2 
   3 class FooController extends ControllerAbstract
   4 {
   5     public function doIndex()
   6     {
   7         // get request method i.e. POST
   8         $requestMethod = $this->getMethod();
   9         
  10         // get Content-Type header
  11         $contentType = $this->getHeader('Content-Type');
  12         
  13         // get GET parameter foo
  14         $requestMethod = $this->getParameter('foo');
  15         
  16         // if its an XML Content-Type returns an DOMDocument. On
  17         // an JSON or x-www-form-urlencoded Content-Type an array
  18         $body = $this->getBody():
  19         
  20         // set an response code
  21         $this->setResponseCode(200);
  22         
  23         // write an response
  24         $this->setBody('foobar');
  25     }
  26 }

PSX provides an simple controller class which gives you the possibility to read from the request and write to the response. Because the controller is the connection between your application and the framework PSX tries to provide the most polished and stable API for your application. Inside the controller you can specify your dependecies via the @Inject annotation. The example shows some important methods inside an controller.

Command

PHP

   1 <?php
   2 
   3 class FooCommand extends CommandAbstract
   4 {
   5     /**
   6      * @Inject
   7      * @var Doctrine\DBAL\Connection
   8      */
   9     protected $connection;
  10 
  11     public function onExecute(Parameters $parameters, OutputInterface $output)
  12     {
  13         $this->connection->insert('acme_news', array(
  14             'title' => $parameters->get('title')
  15         ));
  16 
  17         $output->writeln('Inserted a news');
  18     }
  19 
  20     public function getParameters()
  21     {
  22         return $this->getParameterBuilder()
  23             ->setDescription('Inserts an news entry')
  24             ->addOption('title', Parameter::TYPE_REQUIRED, 'The title of the news')
  25             ->getParameters();
  26     }
  27 }

PSX offers a command system which helps to encapsulate code into micro services. An command is like an controller but without the request/response context. Each command can specify parameters which are needed to complete the task. Like in an controller you can specify your dependecies via the @Inject annotation.

The idea is that an command can be executed from any place i.e. within an Controller/Command, CLI, message queue or any other environment. Although it is possible to call an command from CLI you can not ask for user input inside an command. All needed values must be provided as parameters. This ensures that we always can call an command from other environments. The example shows an command which inserts an news entry.

Download

This is the official release of PSX. PSX tries to follow the semantic versioning. The public API documentation is shipped with each release in the doc folder. You can also browse all releases at GitHub. The preferred way of installing PSX is through composer

Composer

php composer.phar create-project psx/sample .

Documentation

Manual
The official manual of PSX
API
The official API of PSX
Test coverage
Shows how many code is covered by tests
Github
The repository of PSX