Github Demo

In cases where you pull API data from foreign webservices as JSON, you convert it to array form and use that everywhere. This can get quite messy, with lots of nesting and complexity.

Using DTOs you simplify it to

  • The data that you need.
  • Assert the data exists and handle it from there on with the typesafety and autocomplete you are used to from other areas.

Lets take a look into a GitHub PR

We use this demo data to simulate a request to GitHub API to get a single PR. And we use some simple DTOs here for the fields we need in the template:

<?xml version="1.0"?>
<dtos
    xmlns="cakephp-dto"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="cakephp-dto https://github.com/dereuromark/cakephp-dto">

    <!-- Generate via: bin/cake dto generate -p Sandbox -->

    <!-- We only outline the fields we need/want -->
    
    <dto name="Github/PullRequest">
        <field name="url" type="string" required="true"/>
        <field name="number" type="int" required="true"/>
        <field name="state" type="string" required="true"/>
        <field name="title" type="string" required="true"/>
        <field name="body" type="string" required="true"/>
        <field name="user" type="Github/User" required="true"/>
        <field name="createdAt" type="\Cake\I18n\FrozenTime" required="true"/>
        <field name="labels" type="Github/Label[]" associative="true" key="name"/>
        <field name="head" type="Github/Head"/>
        <field name="base" type="Github/Base"/>
    </dto>

    <dto name="Github/User">
        <field name="login" type="string" required="true"/>
        <field name="html_url" type="string" required="true"/>
        <field name="type" type="string" required="true"/>
    </dto>

    <dto name="Github/Label">
        <field name="name" type="string"/>
        <field name="color" type="string"/>
    </dto>

    <dto name="Github/Head">
        <field name="ref" type="string" required="true"/>
        <field name="sha" type="string" required="true"/>
        <field name="user" type="Github/User" required="true"/>
        <field name="repo" type="Github/Repo" required="true"/>
    </dto>

    <dto name="Github/Base">
        <field name="ref" type="string" required="true"/>
        <field name="sha" type="string" required="true"/>
        <field name="user" type="Github/User" required="true"/>
        <field name="repo" type="Github/Repo" required="true"/>
    </dto>

    <dto name="Github/Repo">
        <field name="name" type="string" required="true"/>
        <field name="html_url" type="string" required="true"/>
        <field name="private" type="bool" required="true"/>
        <field name="owner" type="Github/User" required="true"/>
    </dto>

</dtos>

All we now need to do is to convert the raw JSON/array into our nested DTO object:

$pullRequestDto = PullRequestDto::create($gitHubApiData, true, PullRequestDto::TYPE_UNDERSCORED);

We use "ignoreMissing" as true as we do not assign all array values, and we also need to tell it to expect an underscored form.

dd($pullRequestDto) result

Using debug() or dd() we can easily show what the DTO contains:

object(Sandbox\Dto\Github\PullRequestDto) {

    'data' => [
        'url' => 'https://api.github.com/repos/octocat/Hello-World/pulls/1347',
        'number' => (int) 1347,
        'state' => 'open',
        'title' => 'new-feature',
        'body' => 'Please pull these awesome changes',
        'user' => [
            'login' => 'octocat',
            'html_url' => 'https://github.com/octocat',
            'type' => 'User'
        ],
        'createdAt' => '2011-01-26T19:01:12Z',
        'labels' => [
            'bug' => [
                'name' => 'bug',
                'color' => 'f29513'
            ]
        ],
        'head' => [
            'ref' => 'new-topic',
            'sha' => '6dcb09b5b57875f334f61aebed695e2e4193db5e',
            'user' => [
                'login' => 'octocat',
                'html_url' => 'https://github.com/octocat',
                'type' => 'User'
            ],
            'repo' => [
                'name' => 'Hello-World',
                'html_url' => 'https://github.com/octocat/Hello-World',
                'private' => false,
                'owner' => [
                    'login' => 'octocat',
                    'html_url' => 'https://github.com/octocat',
                    'type' => 'User'
                ]
            ]
        ],
        'base' => [
            'ref' => 'master',
            'sha' => '6dcb09b5b57875f334f61aebed695e2e4193db5e',
            'user' => [
                'login' => 'octocat',
                'html_url' => 'https://github.com/octocat',
                'type' => 'User'
            ],
            'repo' => [
                'name' => 'Hello-World',
                'html_url' => 'https://github.com/octocat/Hello-World',
                'private' => false,
                'owner' => [
                    'login' => 'octocat',
                    'html_url' => 'https://github.com/octocat',
                    'type' => 'User'
                ]
            ]
        ]
    ],
    'touched' => [
        (int) 0 => 'url',
        (int) 1 => 'number',
        (int) 2 => 'state',
        (int) 3 => 'title',
        (int) 4 => 'user',
        (int) 5 => 'body',
        (int) 6 => 'labels',
        (int) 7 => 'createdAt',
        (int) 8 => 'head',
        (int) 9 => 'base'
    ],
    'extends' => 'CakeDto\Dto\AbstractDto',
    'immutable' => false

}

Now let's use it

In the template we now have fully annotated fields and can very quickly type what we want to print out.

<?php
/**
 * @var \App\View\AppView $this
 * @var \Sandbox\Dto\Github\PullRequestDto $pullRequestDto
 */
?>
<p>
    <b>PR: <?php echo h($pullRequestDto->getNumber()); ?> (<?php echo h($pullRequestDto->getTitle()); ?>)</b>
    <?php foreach ($pullRequestDto->getLabels() as $label) { ?>
        <span class="badge" style="background-color: #<?php echo h($label->getColor()); ?>"><?php echo h($label->getName()); ?></span>
    <?php } ?>
</p>
<p>
    Head: <?php echo h($pullRequestDto->getHead()->getRef()) . ':' . $pullRequestDto->getHead()->getSha(); ?>
        by <?php echo h($pullRequestDto->getHead()->getUser()->getLogin()) ?>
    <br>
    Base: <?php echo h($pullRequestDto->getBase()->getRef()) . ':' . $pullRequestDto->getBase()->getSha(); ?>
        by <?php echo h($pullRequestDto->getBase()->getUser()->getLogin()) ?>
</p>

Resulting output

PR: 1347 (new-feature) bug

Head: new-topic:6dcb09b5b57875f334f61aebed695e2e4193db5e by octocat
Base: master:6dcb09b5b57875f334f61aebed695e2e4193db5e by octocat


As you can see (and try out):

  • Typing out the existing fields is super fast and easy (no tedious lookup on the nested array).
  • Full return-type-hinting (string, int, bool, object, ...) will let you know if you use sth wrongly without having to run it first (string operation on a non-string etc).

Send your feedback or bugreport!