Resource

A BEAR.Sunday application is RESTful and is made up of a collection of resources connected by links.

Object as a service

An HTTP method is mapped to a PHP method in the ResourceObject class. It transfers its resource state as a resource representation from stateless request. (Representational State Transfer)

Here are some examples of a resource object:

class Index extends ResourceObject
{
    public $code = 200;
    public $headers = [];

    public function onGet(int $a, int $b) : ResourceObject
    {
        $this->body = [
            'result' => $a + $b // $_GET['a'] + $_GET['b']
        ] ;

        return $this;
    }
}
class Todo extends ResourceObject
{
    public function onPost(string $id, string $todo) : ResourceOjbect
    {
        // status code
        $this->code = 201;
        // location header for created resource
        $this->headers['Location'] = '/todo/new_id';

        return $this;
    }
}

The PHP resource class has URIs such as app://self/blog/posts/?id=3, page://self/index similar to the URI of the web, and conforms to the HTTP method onGet, onPost, onPut, onPatch, onDelete interface.

$_GET for onGet and $_POST for onPost are passed to the arguments of the method depending on the variable name, and the methods of onPut, onPatch, onDelete are content The value that can be handled according to content-type(x-www-form-urlencoded or application/json) is an argument.

The resource state (code,headers orbody) is handled by these method using the given parameters. Then the resource class returns itself($this).

Syntax sugar

Access to the body property has some syntactic sugar.

$this['price'] = 10;
// is same as
$this->body['price'] = 10;

Scheme

The equivalent to a MVC model is an app resource. A resource functions as an internal API, but as it is designed using REST it also works as an external API transport. The page resource carries out a similar role as a page controller which is also a resource. A page resource then can consume application resources and builds itself based on the called URI.

URI Class
page://self/index Koriym\Todo\Resource\Page\Index
app://self/blog/posts Koriym\Todo\Resource\App\Blog\Posts

Method

Resources have 6 interfaces conforming to HTTP methods.

GET

Reads resources. This method does not provide any changing of the resource state. A safe method with no possible side affects.

PUT

Performs creation and updates of a resource. This method has the benefit that running it once or many more times will have no more effect. This is referred to as Idempotence.

PATCH

Performs resource updates, but unlike PUT, it applies a delta rather than replacing the entire resource.

POST

Performs resource creation. If you run a request multiple times the resource will be created as many times. A method with no idempotence.

DELETE

Resource deletion. Has idempotence just like PUT.

OPTIONS

Get information on parameters and responses required for resource request. It is as secure as GET method.

Rendering

The request method of the ResourceObject class (such as onGet) has no interest in expressions such as whether the resource is represented in HTML or JSON. Depending on the context, the resource renderer injected into ResourceObject renders it to JSON or HTML and makes it a resource representation (view).

Rendering is done when a resource is string evaluated.


$weekday = $api->resource->uri('app://self/weekday')(['year' => 2000, 'month'=>1, 'day'=>1]);
var_dump($weekday->body); // as array
//array(1) {
//    ["weekday"]=>
//  string(3) "Sat"
//}

echo $weekday; // as string
//{
//    "weekday": "Sat",
//    "_links": {
//    "self": {
//        "href": "/weekday/2000/1/1"
//        }
//    }
//}

It is injected according to the context so you do not usually need to be aware. When a resource specific expression is required, we inject our own renderer as follows.

class Index
{
    // ...
    /**
     * @Inject
     * @Named("my_renderer")
     */
    public function setRenderer(RenderInterface $renderer)
    {
        parent::setRenderer($renderer);
    }
}

or

class Index
{
    /**
     * @Inject
     */
    public function setRenderer(RenderInterface $renderer)
    {
        unset($renderer);
        $this->renderer = new class implements RenderInterface {
            public function render(ResourceObject $ro)
            {
                $ro->headers['content-type'] = 'application/json;';
                $ro->view = json_encode($ro->body);

                return $ro->view;
            }
        };
    }
}

Transfer

The transponder forwards the representation (view) to the client (console or web client). Transfer is mostly done simply by simple header() function or echo, but it can be transferred with stream output.

Like a renderer, you do not have to be aware of it normally.

When doing a resource specific specific transfer, override the following method.

class Index
{
    // ...
    public function transfer(TransferInterface $responder, array $server)
    {
        $responder($this, $server);
    }
}

In this way, each class has a function to change its own resource state by request, transfer it by rendering it.

Client

You need a Resource Client to request resource. In the following example a ResourceInject trait is used to inject a Resource Client.


use BEAR\Sunday\Inject\ResourceInject;

class Index extends ResourceObject
{
    use ResourceInject;

    public function onGet()
    {
        $this['post'] = $this
            ->resource
            ->get
            ->uri('app://self/blog/posts')
            ->withQuery(['id' => 1])
            ->eager
            ->request();
        ];
    }
}

This code invokes a GET request to app://self/blog/posts App resource with the query ?id=1 . If you do not specify an eager option with the request, it is just hold the request. Request invocation will then be made when values are lazily output in the representation.

$posts = $this->resource->get->uri('app://self/posts')->request(); //lazy
$posts = $this->resource->get->uri('app://self/posts')->eager->request(); // eager

A request() method without eager returns an invokable request object, which can be invoked by calling $posts(). You can assign this value to a template engine or embed it in another resource. It will then be lazily evaluated.

Syntax Sugar

Syntax sugar can be used for eager request. The following requests are all the same. (php7)

$this->resource->get->uri('app://self/user')->withQuery(['id' => 1])->eager->request()->body;
$this->resource->get->uri('app://self/user')(['id' => 1])->body;
$this->resource->uri('app://self/user')(['id' => 1])->body; // "get" can be omitted.
$this->resource->uri('app://self/user?id=1')()->body;

For PHP7.0 and up, You can code examples in the client like this

<?php
use BEAR\Sunday\Inject\ResourceInject;

class Index extends ResourceObject
{
    use ResourceInject;

    public function onGet() : ResourceOjbect
    {
        $this->body = [
            'post' => $this->uri('app://self/blog/posts')(['id' => 1])
        ];
    }
}

Resources can be linked in various way.

$blog = $this
    ->resource
    ->get
    ->uri('app://self/user')
    ->withQuery(['id' => 1])
    ->linkSelf("blog")
    ->eager
    ->request()
    ->body;

Three type of links are provided.

  • linkSelf($rel) Replace with the linked resource.
  • linkNew($rel) Add linked resources to the base resource.
  • linkCrawl($rel) Crawl the link to build a resource tree.
/**
 * @Link(rel="profile", href="/profile{?id}")
 */
public function onGet($id) : ResourceOjbect

Set the link with rel key name and href resource URI.

  • NOTE: When using the hal context, @Link is used for a HAL link.
use BEAR\Resource\Annotation\Link;

/**
 * @Link(crawl="post-tree", rel="post", href="app://self/post?author_id={id}")
 */
public function onGet($id = null)

A crawl tagged link will then be crawled with linkCrawl.

Find out more about the @Link annotation at BEAR.Resource README.

Embed Resource

You can embed another resource by using src.

use BEAR\Resource\Annotation\Embed;

class News
{
    /**
     * @Embed(rel="sports", src="/news/sports")
     * @Embed(rel="weater", src="/news/weather")
     */
    public function onGet() : ResourceOjbect

The resource request is embeded. The request is invoked when rendering. You can add parameters or replace with addQuery() or withQuery()

use BEAR\Resource\Annotation\Embed;

class News
{
    /**
     * @Embed(rel="website", src="/website{?id}")
     */
    public function onGet(string $id) : ResourceOjbect
    {
        // ...
        $this['website']->addQuery(['title' => $title]); // add parameters
  • NOTE: In the HAL renderer, this is used as __embed.

Bind parameters

You can bind method parameters to an “external value”. The external value might be a web context or any other resource state.

Web context parameter

For instance, Instead you “pull” $_GET or any global the web context values, You can bind PHP super global values to method parameters.

use Ray\WebContextParam\Annotation\QueryParam;

class News
{
    /**
     * @QueryParam("id")
     */
    public function foo(strin $id) : ResoureObject
    {
      // $id = $_GET['id'];

The above example is a case where a key name and the parameter name are the same. You can specify key and param values when they don’t match.

use Ray\WebContextParam\Annotation\CookieParam;

class News
{
    /**
     * @CookieParam(key="id", param="tokenId")
     */
    public function foo(string $tokenId) : ResoureObject
    {
      // $tokenId = $_COOKIE['id'];

Full List


use Ray\WebContextParam\Annotation\QueryParam;
use Ray\WebContextParam\Annotation\CookieParam;
use Ray\WebContextParam\Annotation\EnvParam;
use Ray\WebContextParam\Annotation\FormParam;
use Ray\WebContextParam\Annotation\ServerParam;

class News
{
    /**
     * @QueryParam(key="id", param="userId")
     * @CookieParam(key="id", param="tokenId")
     * @EnvParam("app_mode")
     * @FormParam("token")
     * @ServerParam(key="SERVER_NAME", param="server")
     */
    public function foo(
        string $userId,           // $_GET['id'];
        string $tokenId = "0000", // $_COOKIE['id'] or "0000" when unset;
        string $app_mode,         // $_ENV['app_mode'];
        string $token,            // $_POST['token'];
        string $server            // $_SERVER['SERVER_NAME'];
    ) : ResourceOjbect {

This bind parameter is also very useful for testing.

Resource Parameter

We can bind the status of another resource to a parameter with the @ResourceParam annotation.

use BEAR\Resource\Annotation\ResourceParam;

class News
{
    /**
     * @ResourceParam(param=“name”, uri="app://self//login#nickname")
     */
    public function onGet(string $name) : ResourceOjbect
    {

In this example, the nickname property of app://self//login is bound to $name.

Resource cache

@Cacheable

use BEAR\RepositoryModule\Annotation\Cacheable;

/**
 * @Cacheable
 */
class User extends ResourceObject

@Cacheable annotated resource objects are cached without a time limit. The cache will be updated by any non-GET request on the same class with no expiry time (unless you specify one). A parameter is inspected to determine identity of the resource.

@Cacheable annotated resource objects will have Last-Modified and ETag headers added automatically.

use BEAR\RepositoryModule\Annotation\Cacheable;

/**
 * @Cacheable
 */
class Todo
{
    public function onGet(string $id) : ResoureObject
    {
        // read
    }

    public function onPost(string $id, string $name) : ResoureObject
    {
        // update
    }
}

For example, when a request is made to ->post(10, 'shopping'), the ?id=10 cache will be updated.

Set update to false if you don’t want to have this auto update.

/**
 * @Cacheable(update=false)
 */

You can specify a cache life time as short, medium or long on the expiry property.

/**
 * @Cacheable(expiry="short")
 */

@Purge @Refresh

You can also update caches with the @Purge and @Refresh annotation.

use BEAR\RepositoryModule\Annotation\Purge;
use BEAR\RepositoryModule\Annotation\Refresh;

class News
{
  /**
   * @Purge(uri="app://self/user/friend?user_id={id}")
   * @Refresh(uri="app://self/user/profile?user_id={id}")
   */
   public function onPut(string $id, string $name, int $age) : ResoureObject

You can update the cache for another resource class or even multiple resources at once. @Purge deletes a cache where @Refresh will recreate cache data.

Best Practice

In the real world of REST, resources are connected with other resources. The use of the link makes the code simpler and makes it easier to read and test and change.

@Embed

Embed resources with @Embed instead of get the state of other resources.

// OK but not the best
class Index extends ResourceObject
{
    use ResourceInject;

    public function onGet(string $status) : ResoureObject
    {
        $this['todos'] = $this->resource
            ->get
            ->uri('app://self/todos')
            ->withQuery(['status' => $status])
            ->eager
            ->request();

        return $this;
    }
}

// Better
class Index extends ResourceObject
{
    /**
     * @Embed(rel="todos", src="app://self/todos{?status}")
     */
    public function onGet(string $status) : ResourceObject
    {
        return $this;
    }
}

When changing the state of another resource we will follow the next action indicated with @Link using href() (href = hyper reference).

// OK but not the best
class Todo extends ResourceObject
{
    use ResourceInject;

    public function onPost(string $title) : ResourceObject
    {
        $this->resource
            ->post
            ->uri('app://self/todo')
            ->withQuery(['title' => $title])
            ->eager
            ->request();
        $this->code = 301;
        $this->headers[ResponseHeader::LOCATION] = '/';

        return $this;
    }
}

// Better
class Todo extends ResourceObject
{
    use ResourceInject;

    /**
     * @Link(rel="create", href="app://self/todo", method="post")
     */
    public function onPost(string $title) : ResourceObject
    {
        $this->resource->href('create', ['title' => $title]);
        $this->code = 301;
        $this->headers[ResponseHeader::LOCATION] = '/';

        return $this;
    }
}

@ResourceParam

If you need other resource results to request other resources, use @ResourceParam.

// OK but not the best
class User extends ResourceObject
{
    use ResourceInject;

    public function onGet(string $id) : ResoureObject
    {
        $nickname = $this->resource
            ->get
            ->uri('app://self/login-user')
            ->withQuery(['id' => $id])
            ->eager
            ->request()
            ->body['nickname'];
        $this['profile'] = $this->resource
            ->get
            ->uri('app://self/profile')
            ->withQuery(['name' => $nickname])
            ->eager
            ->request()
            ->body;

        return $this;
    }
}

// Better
class User extends ResourceObject
{
    use ResourceInject;

    /**
     * @ResourceParam(param=“name”, uri="app://self//login-user#nickname")
     */
    public function onGet(string $id, string $name) : ResoureObject
    {
        $this['profile'] = $this->resource
            ->get
            ->uri('app://self/profile')
            ->withQuery(['name' => $name])
            ->eager
            ->request()
            ->body;

        return $this;
    }
}

// Best
class User extends ResourceObject
{
    /**
     * @ResourceParam(param=“name”, uri="app://self//login-user#nickname")
     * @Embed(rel="profile", src="app://self/profile")
     */
    public function onGet(string $id, string $name) : ResoureObject
    {
        $this['profile']->addQuery(['name'=>$name]);

        return $this;
    }
}