Completed
Push — master ( df4948...3b59f8 )
by Michael
02:51
created

Creator::processRowset()   D

Complexity

Conditions 9
Paths 18

Size

Total Lines 37
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 3
Metric Value
c 5
b 0
f 3
dl 0
loc 37
rs 4.909
cc 9
eloc 27
nc 18
nop 3
1
<?php
2
/**
3
 * Contains Creator class.
4
 *
5
 * PHP version 5.4
6
 *
7
 * LICENSE:
8
 * This file is part of Yet Another Php Eve Api Library also know as Yapeal which can be used to access the Eve Online
9
 * API data and place it into a database.
10
 * Copyright (C) 2015-2016 Michael Cummings
11
 *
12
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
13
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
14
 * any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
17
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
18
 * details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
21
 * <http://www.gnu.org/licenses/>.
22
 *
23
 * You should be able to find a copy of this license in the LICENSE.md file. A copy of the GNU GPL should also be
24
 * available in the GNU-GPL.md file.
25
 *
26
 * @copyright 2015-2016 Michael Cummings
27
 * @license   http://www.gnu.org/copyleft/lesser.html GNU LGPL
28
 * @author    Michael Cummings <[email protected]>
29
 */
30
namespace Yapeal\Sql;
31
32
use SimpleXMLElement;
33
use SimpleXMLIterator;
34
use Twig_Environment;
35
use Yapeal\Console\Command\EveApiCreatorTrait;
36
use Yapeal\Event\EveApiEventInterface;
37
use Yapeal\Event\MediatorInterface;
38
use Yapeal\Exception\YapealException;
39
use Yapeal\Log\Logger;
40
use Yapeal\Xml\EveApiReadWriteInterface;
41
42
/**
43
 * Class Creator
44
 */
45
class Creator
46
{
47
    use EveApiCreatorTrait;
48
    /**
49
     * Creator constructor.
50
     *
51
     * @param Twig_Environment $twig
52
     * @param string           $dir
53
     * @param string           $platform
54
     */
55
    public function __construct(Twig_Environment $twig, $dir = __DIR__, $platform = 'MySql')
56
    {
57
        $this->setDir($dir);
58
        $this->setPlatform($platform);
59
        $this->setTwig($twig);
60
    }
61
    /**
62
     * @param EveApiEventInterface $event
63
     * @param string               $eventName
64
     * @param MediatorInterface    $yem
65
     *
66
     * @return EveApiEventInterface
67
     * @throws YapealException
68
     * @throws \DomainException
69
     * @throws \InvalidArgumentException
70
     * @throws \LogicException
71
     */
72
    public function createSql(EveApiEventInterface $event, $eventName, MediatorInterface $yem)
73
    {
74
        $this->setYem($yem);
75
        $data = $event->getData();
76
        $this->getYem()
77
            ->triggerLogEvent(
78
                'Yapeal.Log.log',
79
                Logger::DEBUG,
80
                $this->getReceivedEventMessage($data, $eventName, __CLASS__)
81
            );
82
        // Only work with raw unaltered XML data.
83
        if (false !== strpos($data->getEveApiXml(), '<?yapeal.parameters.json')) {
84
            return $event->setHandledSufficiently();
85
        }
86
        $this->sectionName = $data->getEveApiSectionName();
87
        $this->apiName = $data->getEveApiName();
88
        $outputFile = sprintf(
89
            '%1$s%2$s/Create%3$s.sql',
90
            $this->getDir(),
91
            ucfirst($this->sectionName),
92
            ucfirst($this->apiName)
93
        );
94
        // Nothing to do if NOT overwriting and file exists.
95
        if (false === $this->isOverwrite() && is_file($outputFile)) {
96
            return $event;
97
        }
98
        $sxi = new SimpleXMLIterator($data->getEveApiXml());
99
        $this->tables = [];
100
        $this->processValueOnly($sxi, $this->apiName);
101
        $this->processRowset($sxi, $this->apiName);
102
        $tCount = count($this->tables);
103
        if (0 === $tCount) {
104
            $mess = 'No SQL tables to create for';
105
            $this->getYem()
106
                ->triggerLogEvent('Yapeal.Log.log', Logger::NOTICE, $this->createEveApiMessage($mess, $data));
107
        }
108
        ksort($this->tables);
109
//        $tableNames = array_keys($this->tables);
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
110
        list($mSec, $sec) = explode(' ', microtime());
111
        $vars = [
112
            'className' => lcfirst($this->apiName),
113
            'tables' => $this->tables,
114
            'sectionName' => lcfirst($this->sectionName),
115
            'version' => gmdate('YmdHis', $sec) . substr($mSec, 1, 4)
116
        ];
117
        try {
118
            $contents = $this->getTwig()
119
                ->render($this->getSqlTemplateName($data, $this->getPlatform()), $vars);
120
        } catch (\Twig_Error $exp) {
121
            $this->getYem()
122
                ->triggerLogEvent(
123
                    'Yapeal.Log.log',
124
                    Logger::WARNING,
125
                    $this->getFailedToWriteFile($data, $eventName, $outputFile)
126
                );
127
            $this->getYem()
128
                ->triggerLogEvent('Yapeal.Log.log', Logger::ERROR, 'Twig error', ['exception' => $exp]);
129
            return $event;
130
        }
131
        if (false === $this->saveToFile($outputFile, $contents)) {
132
            $this->getYem()
133
                ->triggerLogEvent(
134
                    $eventName,
135
                    Logger::WARNING,
136
                    $this->getFailedToWriteFile($data, $eventName, $outputFile)
137
                );
138
            return $event;
139
        }
140
        return $event->setHandledSufficiently();
141
    }
142
    /**
143
     * @param string $platform
144
     *
145
     * @return self Fluent interface.
146
     */
147
    public function setPlatform($platform)
148
    {
149
        $this->platform = $platform;
150
        return $this;
151
    }
152
    /**
153
     * @return string
154
     */
155
    protected function getPlatform()
156
    {
157
        return $this->platform;
158
    }
159
    /**
160
     * @param string[] $keyNames
161
     *
162
     * @return string
163
     */
164
    protected function getSqlKeys(array $keyNames = [])
165
    {
166
        if ($this->hasOwner()) {
167
            array_unshift($keyNames, 'ownerID');
168
        }
169
        return array_unique($keyNames);
170
    }
171
    /**
172
     * @param EveApiReadWriteInterface $data
173
     * @param string                   $platform
174
     * @param string                   $suffix
175
     *
176
     * @return string
177
     * @throws YapealException
178
     * @throws \LogicException
179
     */
180
    protected function getSqlTemplateName(EveApiReadWriteInterface $data, $platform = 'MySql', $suffix = 'twig')
181
    {
182
        // Section/Api.Platform.Suffix, Section/Api.Suffix, Section/Platform.Suffix,
183
        // Api.Platform.Suffix, Api.Suffix, Platform.Suffix, sql.Suffix
184
        $names = explode(
185
            ',',
186
            sprintf(
187
                '%1$s/%2$s.%3$s.%4$s,%1$s/%2$s.%4$s,%1$s/%3$s.%4$s,' . '%2$s.%3$s.%4$s,%2$s.%4$s,%3$s.%4$s,sql.%4$s',
188
                ucfirst($data->getEveApiSectionName()),
189
                $data->getEveApiName(),
190
                $platform,
191
                $suffix
192
            )
193
        );
194
        foreach ($names as $fileName) {
195
            if (is_file($this->getDir() . $fileName)) {
196
                return $fileName;
197
            }
198
        }
199
        $mess = sprintf(
200
            'Failed to find usable sql template file for EveApi %1$s/%2$s with platform of %3$s',
201
            ucfirst($data->getEveApiSectionName()),
202
            $data->getEveApiName(),
203
            $platform
204
        );
205
        throw new YapealException($mess);
206
    }
207
    /**
208
     * Used to determine if API is in section that has an owner.
209
     *
210
     * @return bool
211
     */
212
    protected function hasOwner()
213
    {
214
        return in_array(strtolower($this->sectionName), ['account', 'char', 'corp'], true);
215
    }
216
    /**
217
     * Used to infer(choose) type from element or attribute's name.
218
     *
219
     * @param string $name     Name of the element or attribute.
220
     * @param bool   $forValue Determines if returned type is going to be used for element or an attribute.
221
     *
222
     * @return string Returns the inferred type from the name.
223
     */
224
    protected function inferTypeFromName($name, $forValue = false)
225
    {
226
        if ('ID' === substr($name, -2)) {
227
            return 'BIGINT(20) UNSIGNED NOT NULL';
228
        }
229
        $name = strtolower($name);
230
        foreach ([
231
                     'descr' => 'TEXT NOT NULL',
232
                     'name' => 'CHAR(100) NOT NULL',
233
                     'balance' => 'DECIMAL(17, 2) NOT NULL',
234
                     'isk' => 'DECIMAL(17, 2) NOT NULL',
235
                     'tax' => 'DECIMAL(17, 2) NOT NULL',
236
                     'timeefficiency' => 'TINYINT(3) UNSIGNED NOT NULL',
237
                     'date' => 'DATETIME NOT NULL DEFAULT \'1970-01-01 00:00:01\'',
238
                     'time' => 'DATETIME NOT NULL DEFAULT \'1970-01-01 00:00:01\'',
239
                     'until' => 'DATETIME NOT NULL DEFAULT \'1970-01-01 00:00:01\'',
240
                     'errorcode' => 'SMALLINT(4) UNSIGNED NOT NULL',
241
                     'level' => 'SMALLINT(4) UNSIGNED NOT NULL',
242
                     'logoncount' => 'BIGINT(20) UNSIGNED NOT NULL',
243
                     'logonminutes' => 'BIGINT(20) UNSIGNED NOT NULL',
244
                     'trainingend' => 'DATETIME NOT NULL DEFAULT \'1970-01-01 00:00:01\'',
245
                     'skillintraining' => 'BIGINT(20) UNSIGNED NOT NULL',
246
                     'trainingdestinationsp' => 'BIGINT(20) UNSIGNED NOT NULL',
247
                     'trainingstartsp' => 'BIGINT(20) UNSIGNED NOT NULL'
248
                 ] as $search => $replace) {
249
            if (false !== strpos($name, $search)) {
250
                return $replace;
251
            }
252
        }
253
        return $forValue ? 'TEXT NOT NULL' : 'VARCHAR(255) DEFAULT \'\'';
254
    }
255
    /**
256
     * @param SimpleXMLIterator $sxi
257
     * @param string            $apiName
258
     * @param string            $xPath
259
     */
260
    protected function processRowset(SimpleXMLIterator $sxi, $apiName, $xPath = '//result/rowset')
261
    {
262
        $items = $sxi->xpath($xPath);
263
        if (0 === count($items)) {
264
            return;
265
        }
266
        foreach ($items as $ele) {
267
            $rsName = ucfirst((string)$ele['name']);
268
            $colNames = explode(',', (string)$ele['columns']);
269
            $keyNames = explode(',', (string)$ele['key']);
270
            $columns = [];
271
            foreach ($keyNames as $keyName) {
272
                $columns[$keyName] = $this->inferTypeFromName($keyName);
273
            }
274
            foreach ($colNames as $colName) {
275
                $columns[$colName] = $this->inferTypeFromName($colName);
276
            }
277
            if ($this->hasOwner()) {
278
                $columns['ownerID'] = 'BIGINT(20) UNSIGNED NOT NULL';
279
            }
280
            uksort($columns, function ($a, $b) {
281
                $a = strtolower($a);
282
                $b = strtolower($b);
283
                if ($a < $b) {
284
                    return -1;
285
                } elseif ($a > $b) {
286
                    return 1;
287
                }
288
                return 0;
289
            });
290
            if (0 === count($this->tables)) {
291
                $this->tables[$apiName] = ['columns' => $columns, 'keys' => $this->getSqlKeys($keyNames)];
292
            } else {
293
                $this->tables[$rsName] = ['columns' => $columns, 'keys' => $this->getSqlKeys($keyNames)];
294
            }
295
        }
296
    }
297
    /**
298
     * @param SimpleXMLIterator $sxi
299
     * @param string            $tableName
300
     * @param string            $xpath
301
     */
302
    protected function processValueOnly(
303
        SimpleXMLIterator $sxi,
304
        $tableName,
305
        $xpath = '//result/child::*[not(*|@*|self::dataTime)]'
306
    ) {
307
        $items = $sxi->xpath($xpath);
308
        if (0 === count($items)) {
309
            return;
310
        }
311
        $columns = [];
312
        /**
313
         * @var SimpleXMLElement $ele
314
         */
315
        foreach ($items as $ele) {
316
            $name = (string)$ele->getName();
317
            $columns[$name] = $this->inferTypeFromName($name, true);
318
        }
319
        if ($this->hasOwner()) {
320
            $columns['ownerID'] = 'BIGINT(20) UNSIGNED NOT NULL';
321
        }
322
        uksort($columns, function ($a, $b) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $a. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $b. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
323
            $a = strtolower($a);
324
            $b = strtolower($b);
325
            if ($a < $b) {
326
                return -1;
327
            } elseif ($a > $b) {
328
                return 1;
329
            }
330
            return 0;
331
        });
332
        $keys = $this->getSqlKeys();
333
        if (0 !== count($keys)) {
334
            $this->tables[$tableName] = ['columns' => $columns, 'keys' => $keys];
335
        } else {
336
            $this->tables[$tableName] = ['columns' => $columns];
337
        }
338
    }
339
    /**
340
     * @var string $apiName
341
     */
342
    protected $apiName;
343
    /**
344
     * @var string $platform Sql connection platform being used.
345
     */
346
    protected $platform;
347
    /**
348
     * @var string $sectionName
349
     */
350
    protected $sectionName;
351
    /**
352
     * @var integer $tableCount
353
     */
354
    protected $tableCount = 0;
355
    /**
356
     * @var array $tables
357
     */
358
    protected $tables;
359
}
360