Completed
Pull Request — master (#1330)
by Zan
02:30
created

Environment::parseAgnosticDsn()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 4
cts 4
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 13
nc 4
nop 1
crap 5
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Migration\Manager
28
 */
29
namespace Phinx\Migration\Manager;
30
31
use Phinx\Db\Adapter\AdapterFactory;
32
use Phinx\Db\Adapter\AdapterInterface;
33
use Phinx\Migration\MigrationInterface;
34
use Phinx\Seed\SeedInterface;
35
use Symfony\Component\Console\Input\InputInterface;
36
use Symfony\Component\Console\Output\OutputInterface;
37
38
class Environment
39
{
40
    /**
41
     * @var string
42
     */
43
    protected $name;
44
45
    /**
46
     * @var array
47
     */
48
    protected $options;
49
50
    /**
51
     * @var \Symfony\Component\Console\Input\InputInterface
52
     */
53
    protected $input;
54
55
    /**
56
     * @var \Symfony\Component\Console\Output\OutputInterface
57
     */
58
    protected $output;
59
60
    /**
61
     * @var int
62
     */
63
    protected $currentVersion;
64
65
    /**
66
     * @var string
67
     */
68
    protected $schemaTableName = 'phinxlog';
69
70
    /**
71
     * @var \Phinx\Db\Adapter\AdapterInterface
72
     */
73
    protected $adapter;
74
75
    /**
76
     * Class Constructor.
77
     *
78
     * @param string $name Environment Name
79
     * @param array $options Options
80
     * @return \Phinx\Migration\Manager\Environment
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
81
     */
82 404
    public function __construct($name, $options)
83
    {
84 404
        $this->name = $name;
85 404
        $this->options = $options;
86 404
    }
87
88
    /**
89
     * Executes the specified migration on this environment.
90
     *
91
     * @param \Phinx\Migration\MigrationInterface $migration Migration
92
     * @param string $direction Direction
93
     * @return void
94
     */
95 10
    public function executeMigration(MigrationInterface $migration, $direction = MigrationInterface::UP)
96
    {
97 10
        $direction = ($direction === MigrationInterface::UP) ? MigrationInterface::UP : MigrationInterface::DOWN;
98 10
        $migration->setMigratingUp($direction === MigrationInterface::UP);
99
100 10
        $startTime = time();
101 10
        $migration->setAdapter($this->getAdapter());
102
103
        // begin the transaction if the adapter supports it
104 10
        if ($this->getAdapter()->hasTransactions()) {
105 6
            $this->getAdapter()->beginTransaction();
106 6
        }
107
108
        // Run the migration
109 10
        if (method_exists($migration, MigrationInterface::CHANGE)) {
110 5
            if ($direction === MigrationInterface::DOWN) {
111
                // Create an instance of the ProxyAdapter so we can record all
112
                // of the migration commands for reverse playback
113
114
                /** @var \Phinx\Db\Adapter\ProxyAdapter $proxyAdapter */
115 4
                $proxyAdapter = AdapterFactory::instance()
116 4
                    ->getWrapper('proxy', $this->getAdapter());
117 4
                $migration->setAdapter($proxyAdapter);
118
                /** @noinspection PhpUndefinedMethodInspection */
119 4
                $migration->change();
0 ignored issues
show
Bug introduced by
The method change() does not seem to exist on object<Phinx\Migration\MigrationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
120 4
                $proxyAdapter->executeInvertedCommands();
121 4
                $migration->setAdapter($this->getAdapter());
122 4
            } else {
123
                /** @noinspection PhpUndefinedMethodInspection */
124 4
                $migration->change();
0 ignored issues
show
Bug introduced by
The method change() does not seem to exist on object<Phinx\Migration\MigrationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
125
            }
126 5
        } else {
127 5
            $migration->{$direction}();
128
        }
129
130
        // commit the transaction if the adapter supports it
131 10
        if ($this->getAdapter()->hasTransactions()) {
132 6
            $this->getAdapter()->commitTransaction();
133 6
        }
134
135
        // Record it in the database
136 10
        $this->getAdapter()->migrated($migration, $direction, date('Y-m-d H:i:s', $startTime), date('Y-m-d H:i:s', time()));
137 10
    }
138
139
    /**
140
     * Executes the specified seeder on this environment.
141
     *
142
     * @param \Phinx\Seed\SeedInterface $seed
143
     * @return void
144
     */
145
    public function executeSeed(SeedInterface $seed)
146
    {
147
        $seed->setAdapter($this->getAdapter());
148
149
        // begin the transaction if the adapter supports it
150
        if ($this->getAdapter()->hasTransactions()) {
151
            $this->getAdapter()->beginTransaction();
152
        }
153
154
        // Run the seeder
155
        if (method_exists($seed, SeedInterface::RUN)) {
156
            $seed->run();
157
        }
158
159
        // commit the transaction if the adapter supports it
160
        if ($this->getAdapter()->hasTransactions()) {
161
            $this->getAdapter()->commitTransaction();
162
        }
163
    }
164
165
    /**
166
     * Sets the environment's name.
167
     *
168
     * @param string $name Environment Name
169
     * @return \Phinx\Migration\Manager\Environment
170
     */
171 1
    public function setName($name)
172
    {
173 1
        $this->name = $name;
174 1
175
        return $this;
176
    }
177
178
    /**
179
     * Gets the environment name.
180
     *
181
     * @return string
182 3
     */
183
    public function getName()
184 3
    {
185
        return $this->name;
186
    }
187
188
    /**
189
     * Sets the environment's options.
190
     *
191
     * @param array $options Environment Options
192
     * @return \Phinx\Migration\Manager\Environment
193 6
     */
194
    public function setOptions($options)
195 6
    {
196 6
        $this->options = $options;
197
198
        return $this;
199
    }
200
201
    /**
202
     * Gets the environment's options.
203
     *
204 2
     * @return array
205
     */
206 2
    public function getOptions()
207
    {
208
        return $this->parseAgnosticDsn($this->options);
209
    }
210
211
    /**
212
     * Parse a database-agnostic DSN into individual options.
213
     *
214
     * @param array $options Options
215 7
     * @return array
216
     */
217 7
    protected function parseAgnosticDsn(array $options)
218 7
    {
219
        if (isset($options['dsn']) && is_string($options['dsn'])) {
220
            $regex = '#^(?P<adapter>[^\\:]+)\\://(?:(?P<user>[^\\:@]+)(?:\\:(?P<pass>[^@]*))?@)?'
221
                   . '(?P<host>[^\\:@/]+)(?:\\:(?P<port>[1-9]\\d*))?/(?P<name>[^\?]+)(?:\?(?P<query>.*))?$#';
222
            if (preg_match($regex, trim($options['dsn']), $parsedOptions)) {
223
                $additionalOpts = [];
224
                if (isset($parsedOptions['query'])) {
225
                    parse_str($parsedOptions['query'], $additionalOpts);
226 9
                }
227
                $validOptions = ['adapter', 'user', 'pass', 'host', 'port', 'name'];
228 9
                $parsedOptions = array_filter(array_intersect_key($parsedOptions, array_flip($validOptions)));
229
                $options = array_merge($additionalOpts, $parsedOptions, $options);
230
                unset($options['dsn']);
231
            }
232
        }
233
234
        return $options;
235
    }
236
237 6
    /**
238
     * Sets the console input.
239 6
     *
240 6
     * @param \Symfony\Component\Console\Input\InputInterface $input
241
     * @return \Phinx\Migration\Manager\Environment
242
     */
243
    public function setInput(InputInterface $input)
244
    {
245
        $this->input = $input;
246
247
        return $this;
248 8
    }
249
250 8
    /**
251
     * Gets the console input.
252
     *
253
     * @return \Symfony\Component\Console\Input\InputInterface
254
     */
255
    public function getInput()
256
    {
257
        return $this->input;
258 6
    }
259
260 6
    /**
261
     * Sets the console output.
262
     *
263
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
264
     * @return \Phinx\Migration\Manager\Environment
265
     */
266
    public function setOutput(OutputInterface $output)
267
    {
268
        $this->output = $output;
269 5
270
        return $this;
271 5
    }
272
273
    /**
274
     * Gets the console output.
275
     *
276
     * @return \Symfony\Component\Console\Output\OutputInterface
277
     */
278
    public function getOutput()
279
    {
280 6
        return $this->output;
281
    }
282 6
283 6
    /**
284
     * Gets all migrated version numbers.
285
     *
286
     * @return array
287
     */
288
    public function getVersions()
289
    {
290
        return $this->getAdapter()->getVersions();
291 6
    }
292
293
    /**
294
     * Get all migration log entries, indexed by version creation time and sorted ascendingly by the configuration's
295
     * version_order option
296 6
     *
297 6
     * @return array
298
     */
299 6
    public function getVersionLog()
300 1
    {
301 1
        return $this->getAdapter()->getVersionLog();
302
    }
303 6
304 6
    /**
305
     * Sets the current version of the environment.
306
     *
307
     * @param int $version Environment Version
308
     * @return \Phinx\Migration\Manager\Environment
309
     */
310
    public function setCurrentVersion($version)
311
    {
312
        $this->currentVersion = $version;
313 14
314
        return $this;
315 14
    }
316 14
317
    /**
318
     * Gets the current version of the environment.
319
     *
320
     * @return int
321
     */
322
    public function getCurrentVersion()
323
    {
324 17
        // We don't cache this code as the current version is pretty volatile.
325
        // TODO - that means they're no point in a setter then?
326 17
        // maybe we should cache and call a reset() method everytime a migration is run
327 12
        $versions = $this->getVersions();
328
        $version = 0;
329 11
330 3
        if (!empty($versions)) {
331 1
            $version = end($versions);
332
        }
333
334 2
        $this->setCurrentVersion($version);
335 2
336 2
        return $this->currentVersion;
337 10
    }
338 1
339
    /**
340
     * Sets the database adapter.
341 9
     *
342
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter Database Adapter
343 9
     * @return \Phinx\Migration\Manager\Environment
344
     */
345
    public function setAdapter(AdapterInterface $adapter)
346 8
    {
347
        $this->adapter = $adapter;
348 8
349
        return $this;
350
    }
351
352
    /**
353 8
     * Gets the database adapter.
354 5
     *
355 5
     * @return \Phinx\Db\Adapter\AdapterInterface
356
     */
357 8
    public function getAdapter()
358 5
    {
359 5
        if (isset($this->adapter)) {
360
            return $this->adapter;
361
        }
362 8
        if (isset($this->options['connection'])) {
363 1
            if (!($this->options['connection'] instanceof \PDO)) {
364 1
                throw new \RuntimeException('The specified connection is not a PDO instance');
365 1
            }
366
367 8
            $this->options['connection']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
368
            $this->options['adapter'] = $this->options['connection']->getAttribute(\PDO::ATTR_DRIVER_NAME);
369 8
        }
370
        if (!isset($this->options['adapter'])) {
371
            throw new \RuntimeException('No adapter was specified for environment: ' . $this->getName());
372
        }
373
374
        $factory = AdapterFactory::instance();
375
        $adapter = $factory
376
            ->getAdapter($this->options['adapter'], $this->options);
377
378 1
        // Automatically time the executed commands
379
        $adapter = $factory->getWrapper('timed', $adapter);
380 1
381 1
        if (isset($this->options['wrapper'])) {
382
            $adapter = $factory
383
                ->getWrapper($this->options['wrapper'], $adapter);
384
        }
385
386
        if ($this->getInput()) {
387
            $adapter->setInput($this->getInput());
388
        }
389 1
390
        if ($this->getOutput()) {
391 1
            $adapter->setOutput($this->getOutput());
392
        }
393
394
        // Use the TablePrefixAdapter if table prefix/suffixes are in use
395
        if ($adapter->hasOption('table_prefix') || $adapter->hasOption('table_suffix')) {
396
            $adapter = AdapterFactory::instance()
397
                ->getWrapper('prefix', $adapter);
398
        }
399
400
        $this->setAdapter($adapter);
401
402
        return $adapter;
403
    }
404
405
    /**
406
     * Sets the schema table name.
407
     *
408
     * @param string $schemaTableName Schema Table Name
409
     * @return \Phinx\Migration\Manager\Environment
410
     */
411
    public function setSchemaTableName($schemaTableName)
412
    {
413
        $this->schemaTableName = $schemaTableName;
414
415
        return $this;
416
    }
417
418
    /**
419
     * Gets the schema table name.
420
     *
421
     * @return string
422
     */
423
    public function getSchemaTableName()
424
    {
425
        return $this->schemaTableName;
426
    }
427
}
428