Passed
Pull Request — master (#41)
by Robbie
02:46
created

ResourceFieldPopulator::populateFields()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 4
nop 1
dl 0
loc 29
rs 9.4888
c 0
b 0
f 0
1
<?php
2
namespace SilverStripe\CKANRegistry\Service;
3
4
use GuzzleHttp\Client;
5
use GuzzleHttp\Psr7\Request;
6
use RuntimeException;
7
use SilverStripe\CKANRegistry\Model\Resource;
8
use SilverStripe\CKANRegistry\Model\ResourceField;
9
use SilverStripe\Core\Extensible;
10
use SilverStripe\Core\Injector\Injectable;
11
12
/**
13
 * This service will take a CKAN Resource and populate its Fields `has_many` relationship from the CKAN API
14
 */
15
class ResourceFieldPopulator
16
{
17
    use Extensible;
18
    use Injectable;
19
20
    private static $dependencies = [
0 ignored issues
show
introduced by
The private property $dependencies is not used, and could be removed.
Loading history...
21
        'GuzzleClient' => '%$' . Client::class,
22
    ];
23
24
    /**
25
     * @var Client
26
     */
27
    protected $guzzleClient;
28
29
    /**
30
     * Populate fields from the CKAN API endpoint specified on this given resource.
31
     * Throws an exception when the CKAN API is unreachable or responds with an error or if the resource is not properly
32
     *
33
     * @param Resource $resource
34
     * @throws \GuzzleHttp\Exception\GuzzleException|RuntimeException
35
     */
36
    public function populateFields(Resource $resource)
37
    {
38
        if (!$resource->Endpoint && !$resource->Identifier) {
39
            throw new RuntimeException('Could not fetch fields for a resource that is not fully configured');
40
        }
41
42
        $fieldSpecs = $this->doFieldRequest($resource);
43
44
        $newFields = [];
45
        $fields = $resource->Fields();
46
47
        foreach ($fieldSpecs as $fieldSpec) {
48
            $id = $fieldSpec['id'];
49
50
            // Skip fields that may already exist
51
            if ($fields->find('Name', $id)) {
52
                continue;
53
            }
54
55
            // Create a new field
56
            $newFields[] = $field = ResourceField::create();
57
            $field->Name = $id;
58
            // Attempt to parse a readable name
59
            $field->ReadableName = $this->parseName($id);
60
            $field->Type = $fieldSpec['type'];
61
        }
62
63
        // Add our new fields
64
        $fields->addMany($newFields);
65
    }
66
67
    /**
68
     * Perform a request to the CKAN endpoint provided by the given resource to fetch field definitons
69
     *
70
     * @param Resource $resource
71
     * @return mixed
72
     * @throws \GuzzleHttp\Exception\GuzzleException|RuntimeException
73
     */
74
    protected function doFieldRequest(Resource $resource)
75
    {
76
        $endpoint = sprintf(
77
            '%s/api/3/action/datastore_search?id=%s',
78
            trim($resource->Endpoint, '/'),
79
            $resource->Identifier
80
        );
81
82
        $request = new Request('GET', $endpoint);
83
        $response = $this->getGuzzleClient()->send($request, $this->getClientOptions());
84
85
        $statusCode = $response->getStatusCode();
86
        if ($statusCode < 200 || $statusCode >= 300) {
87
            throw new RuntimeException('CKAN API is not available. Error code ' . $statusCode);
88
        }
89
90
        if (!count(preg_grep('#application/json#', $response->getHeader('Content-Type')))) {
91
            throw new RuntimeException('CKAN API returns an invalid response: Content-Type is not JSON');
92
        }
93
94
        $responseBody = json_decode($response->getBody()->getContents(), true);
95
96
        if (!$responseBody || !isset($responseBody['success']) || !$responseBody['success']) {
97
            throw new RuntimeException('CKAN API returns an invalid response: Responded as invalid');
98
        }
99
100
        return $responseBody['result']['fields'];
101
    }
102
103
    /**
104
     * Parse given column ID for a more readable version
105
     *
106
     * @param $id
107
     * @return string
108
     */
109
    protected function parseName($id)
110
    {
111
        // Parse "camelCase" to "space case"
112
        $name = strtolower(preg_replace('/(?<=[a-z\d])([A-Z])/', ' \1', $id));
113
114
        // Swap out certain characters with spaces
115
        $name = str_replace(['_', '-'], ' ', $name);
116
117
        return ucfirst($name);
118
    }
119
120
    /**
121
     * @return Client
122
     */
123
    protected function getGuzzleClient()
124
    {
125
        return $this->guzzleClient;
126
    }
127
128
    /**
129
     * @param Client $guzzleClient
130
     * @return $this
131
     */
132
    public function setGuzzleClient(Client $guzzleClient)
133
    {
134
        $this->guzzleClient = $guzzleClient;
135
        return $this;
136
    }
137
138
    /**
139
     * Get Guzzle client options
140
     *
141
     * @return array
142
     */
143
    protected function getClientOptions()
144
    {
145
        $options = [
146
            'http_errors' => false,
147
        ];
148
        $this->extend('updateClientOptions', $options);
149
        return $options;
150
    }
151
}
152