Completed
Push — craft2 ( b7b85f...2d832c )
by Bart
02:18
created

App   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 364
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 2
dl 0
loc 364
rs 9.6
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
B init() 0 59 4
A getLanguage() 0 4 1
A setLanguage() 0 4 1
A getTimeZone() 0 4 1
A on() 0 17 3
A isConsole() 0 4 1
A getTheme() 0 4 1
A getComponent() 0 16 4
A setComponents() 0 9 2
A handleError() 0 12 4
A createCommandRunner() 0 4 1
A _attachEventListeners() 0 14 5
A _setEditionComponents() 0 13 4
A _setSchematicComponents() 0 53 2
A _installCraft() 0 13 1
1
<?php
2
3
namespace NerdsAndCompany\Schematic\Console;
4
5
use Craft\Craft;
6
use Craft\Logger;
7
use CConsoleApplication as Base;
8
use NerdsAndCompany\Schematic\Behaviors\Schematic;
9
use NerdsAndCompany\Schematic\Services as Service;
10
11
/**
12
 * Schematic Console App.
13
 *
14
 * Sync Craft Setups.
15
 *
16
 * @author    Nerds & Company
17
 * @copyright Copyright (c) 2015-2017, Nerds & Company
18
 * @license   MIT
19
 *
20
 * @see      http://www.nerds.company
21
 */
22
class App extends Base
23
{
24
    // Properties
25
    // =========================================================================
26
27
    /**
28
     * @var
29
     */
30
    public $componentAliases;
31
32
    /**
33
     * @var
34
     */
35
    private $_pendingEvents;
36
37
    /**
38
     * @var
39
     */
40
    private $_editionComponents;
41
42
    // Public Methods
43
    // =========================================================================
44
45
    /**
46
     * Initializes the console app by creating the command runner.
47
     */
48
    public function init()
49
    {
50
        // Set default timezone to UTC
51
        date_default_timezone_set('UTC');
52
53
        // Import all the built-in components
54
        foreach ($this->componentAliases as $alias) {
55
            Craft::import($alias);
56
        }
57
58
        // Attach our Craft app behavior.
59
        $this->attachBehavior('SchematicBehavior', new Schematic());
60
61
        // Attach our own custom Logger
62
        Craft::setLogger(new Logger());
63
64
        // If there is a custom appId set, apply it here.
65
        if ($appId = $this->config->get('appId')) {
66
            $this->setId($appId);
67
        }
68
69
        // Initialize Cache and LogRouter right away (order is important)
70
        $this->getComponent('cache');
71
        $this->getComponent('log');
72
73
        // So we can try to translate Yii framework strings
74
        $this->coreMessages->attachEventHandler('onMissingTranslation', ['Craft\LocalizationHelper', 'findMissingTranslation']);
75
76
        // Set our own custom runtime path.
77
        $this->setRuntimePath(Craft::app()->path->getRuntimePath());
78
79
        // No need for these.
80
        Craft::app()->log->removeRoute('WebLogRoute');
81
        Craft::app()->log->removeRoute('ProfileLogRoute');
82
83
        // Set the edition components
84
        $this->_setEditionComponents();
85
86
        // Install Craft if needed
87
        if (!$this->isInstalled()) {
88
            $this->_installCraft();
89
        }
90
91
        // Set the schematic components
92
        $this->_setSchematicComponents();
93
94
        // Call parent::init() before the plugin console command logic so the command runner gets initialized
95
        parent::init();
96
97
        // Load the plugins
98
        Craft::app()->plugins->loadPlugins();
99
100
        // Validate some basics on the database configuration file.
101
        Craft::app()->validateDbConfigFile();
102
103
        // Add commands
104
        Craft::app()->commandRunner->commands = [];
105
        Craft::app()->commandRunner->addCommands(__DIR__.'/../ConsoleCommands/');
106
    }
107
108
    /**
109
     * Returns the target application language.
110
     *
111
     * @return string
112
     */
113
    public function getLanguage()
114
    {
115
        return $this->asa('SchematicBehavior')->getLanguage();
116
    }
117
118
    /**
119
     * Sets the target application language.
120
     *
121
     * @param string $language
122
     */
123
    public function setLanguage($language)
124
    {
125
        $this->asa('SchematicBehavior')->setLanguage($language);
126
    }
127
128
    /**
129
     * Returns the system time zone.  Note that this method cannot be in {@link AppBehavior}, because Yii will check
130
     * {@link \CApplication::getTimeZone()} instead.
131
     *
132
     * @return string
133
     */
134
    public function getTimeZone()
135
    {
136
        return $this->asa('SchematicBehavior')->getTimezone();
137
    }
138
139
    /**
140
     * Attaches an event handler, or remembers it for later if the component has not been initialized yet.
141
     *
142
     * The event should be identified in a `serviceHandle.eventName` format. For example, if you want to add an event
143
     * handler for {@link EntriesService::onSaveEntry()}, you would do this:
144
     *
145
     * ```php
146
     * Craft::app()->on('entries.saveEntry', function(Event $event) {
147
     *     // ...
148
     * });
149
     * ```
150
     *
151
     * Note that the actual event name (`saveEntry`) does not need to include the “`on`”.
152
     *
153
     * By default, event handlers will not get attached if Craft is current in the middle of updating itself or a
154
     * plugin. If you want the event to fire even in that condition, pass `true` to the $evenDuringUpdates argument.
155
     *
156
     * @param string $event   The event to listen for
157
     * @param mixed  $handler The event handler
158
     */
159
    public function on($event, $handler)
160
    {
161
        list($componentId, $eventName) = explode('.', $event, 2);
162
163
        $component = $this->getComponent($componentId, false);
164
165
        // Normalize the event name
166
        if (strncmp($eventName, 'on', 2) !== 0) {
167
            $eventName = 'on'.ucfirst($eventName);
168
        }
169
170
        if ($component) {
171
            $component->$eventName = $handler;
172
        } else {
173
            $this->_pendingEvents[$componentId][$eventName][] = $handler;
174
        }
175
    }
176
177
    /**
178
     * Returns whether we are executing in the context on a console app.
179
     *
180
     * @return bool
181
     */
182
    public function isConsole()
183
    {
184
        return true;
185
    }
186
187
    /**
188
     * Returns the target application theme.
189
     *
190
     * @return string
191
     */
192
    public function getTheme()
193
    {
194
        return null;
195
    }
196
197
198
    /**
199
     * Override getComponent() so we can attach any pending events if the component is getting initialized as well as
200
     * do some special logic around creating the `Craft::app()->db` application component.
201
     *
202
     * @param string $id
203
     * @param bool   $createIfNull
204
     *
205
     * @return mixed
206
     */
207
    public function getComponent($id, $createIfNull = true)
208
    {
209
        $component = parent::getComponent($id, false);
210
211
        if (!$component && $createIfNull) {
212
            if ($id === 'db') {
213
                $dbConnection = $this->asa('SchematicBehavior')->createDbConnection();
214
                $this->setComponent('db', $dbConnection);
215
            }
216
217
            $component = parent::getComponent($id, true);
218
            $this->_attachEventListeners($id);
219
        }
220
221
        return $component;
222
    }
223
224
    /**
225
     * Sets the application components.
226
     *
227
     * @param      $components
228
     * @param bool $merge
229
     */
230
    public function setComponents($components, $merge = true)
231
    {
232
        if (isset($components['editionComponents'])) {
233
            $this->_editionComponents = $components['editionComponents'];
234
            unset($components['editionComponents']);
235
        }
236
237
        parent::setComponents($components, $merge);
238
    }
239
240
    /**
241
     * @todo Remove for Craft 3
242
     *
243
     * @param int    $code    The level of the error raised
244
     * @param string $message The error message
245
     * @param string $file    The filename that the error was raised in
246
     * @param int    $line    The line number the error was raised at
247
     */
248
    public function handleError($code, $message, $file, $line)
249
    {
250
        // PHP 7 turned some E_STRICT messages to E_WARNINGs. Code 2 is for all warnings
251
        // and since there are no messages specific codes we have to parse the string for what
252
        // we're looking for. Lame, but it works since all PHP error messages are always in English.
253
        // https://stackoverflow.com/questions/11556375/is-there-a-way-to-localize-phps-error-output
254
        if (version_compare(PHP_VERSION, '7', '>=') && $code === 2 && strpos($message, 'should be compatible with') !== false) {
255
            return;
256
        }
257
258
        parent::handleError($code, $message, $file, $line);
259
    }
260
261
    // Protected Methods
262
    // =========================================================================
263
264
    /**
265
     * @return ConsoleCommandRunner
266
     */
267
    protected function createCommandRunner()
268
    {
269
        return new CommandRunner();
270
    }
271
272
    // Private Methods
273
    // =========================================================================
274
275
    /**
276
     * Attaches any pending event listeners to the newly-initialized component.
277
     *
278
     * @param string $componentId
279
     */
280
    private function _attachEventListeners($componentId)
281
    {
282
        if (isset($this->_pendingEvents[$componentId])) {
283
            $component = $this->getComponent($componentId, false);
284
285
            if ($component) {
286
                foreach ($this->_pendingEvents[$componentId] as $eventName => $handlers) {
287
                    foreach ($handlers as $handler) {
288
                        $component->$eventName = $handler;
289
                    }
290
                }
291
            }
292
        }
293
    }
294
295
    /**
296
     * Sets the edition components.
297
     */
298
    private function _setEditionComponents()
299
    {
300
        // Set the appropriate edition components
301
        if (isset($this->_editionComponents)) {
302
            foreach ($this->_editionComponents as $edition => $editionComponents) {
303
                if ($this->getEdition() >= $edition) {
304
                    $this->setComponents($editionComponents);
305
                }
306
            }
307
308
            unset($this->_editionComponents);
309
        }
310
    }
311
312
    /**
313
     * Sets the schematic components.
314
     */
315
    private function _setSchematicComponents()
316
    {
317
        $components = [
318
            'schematic' => [
319
                'class' => Service\Schematic::class,
320
            ],
321
            'schematic_locales' => [
322
                'class' => Service\Locales::class,
323
            ],
324
            'schematic_assetSources' => [
325
                'class' => Service\AssetSources::class,
326
            ],
327
            'schematic_assetTransforms' => [
328
                'class' => Service\AssetTransforms::class,
329
            ],
330
            'schematic_fields' => [
331
                'class' => Service\Fields::class,
332
            ],
333
            'schematic_globalSets' => [
334
                'class' => Service\GlobalSets::class,
335
            ],
336
            'schematic_plugins' => [
337
                'class' => Service\Plugins::class,
338
            ],
339
            'schematic_sections' => [
340
                'class' => Service\Sections::class,
341
            ],
342
            'schematic_userGroups' => [
343
                'class' => Service\UserGroups::class,
344
            ],
345
            'schematic_users' => [
346
                'class' => Service\Users::class,
347
            ],
348
            'schematic_categoryGroups' => [
349
                'class' => Service\CategoryGroups::class,
350
            ],
351
            'schematic_tagGroups' => [
352
                'class' => Service\TagGroups::class,
353
            ],
354
            'schematic_sources' => [
355
                'class' => Service\Sources::class,
356
            ],
357
        ];
358
359
        // Element index settings are supported from Craft 2.5
360
        if (version_compare(CRAFT_VERSION, '2.5', '>=')) {
361
            $components['schematic_elementIndexSettings'] = [
362
                'class' => Service\ElementIndexSettings::class,
363
            ];
364
        }
365
366
        $this->setComponents($components);
367
    }
368
369
    /**
370
     * Install Craft.
371
     */
372
    private function _installCraft()
373
    {
374
        $options = [
375
            'username' => getenv('CRAFT_USERNAME'),
376
            'email' => getenv('CRAFT_EMAIL'),
377
            'password' => getenv('CRAFT_PASSWORD'),
378
            'siteName' => getenv('CRAFT_SITENAME'),
379
            'siteUrl' => getenv('CRAFT_SITEURL'),
380
            'locale' => getenv('CRAFT_LOCALE'),
381
        ];
382
383
        Craft::app()->install->run($options);
384
    }
385
}
386