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

Environment::parseAgnosticDsn()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 18
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
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
        return $options;
234
    }
235
236
    /**
237 6
     * Sets the console input.
238
     *
239 6
     * @param \Symfony\Component\Console\Input\InputInterface $input
240 6
     * @return \Phinx\Migration\Manager\Environment
241
     */
242
    public function setInput(InputInterface $input)
243
    {
244
        $this->input = $input;
245
246
        return $this;
247
    }
248 8
249
    /**
250 8
     * Gets the console input.
251
     *
252
     * @return \Symfony\Component\Console\Input\InputInterface
253
     */
254
    public function getInput()
255
    {
256
        return $this->input;
257
    }
258 6
259
    /**
260 6
     * Sets the console output.
261
     *
262
     * @param \Symfony\Component\Console\Output\OutputInterface $output Output
263
     * @return \Phinx\Migration\Manager\Environment
264
     */
265
    public function setOutput(OutputInterface $output)
266
    {
267
        $this->output = $output;
268
269 5
        return $this;
270
    }
271 5
272
    /**
273
     * Gets the console output.
274
     *
275
     * @return \Symfony\Component\Console\Output\OutputInterface
276
     */
277
    public function getOutput()
278
    {
279
        return $this->output;
280 6
    }
281
282 6
    /**
283 6
     * Gets all migrated version numbers.
284
     *
285
     * @return array
286
     */
287
    public function getVersions()
288
    {
289
        return $this->getAdapter()->getVersions();
290
    }
291 6
292
    /**
293
     * Get all migration log entries, indexed by version creation time and sorted ascendingly by the configuration's
294
     * version_order option
295
     *
296 6
     * @return array
297 6
     */
298
    public function getVersionLog()
299 6
    {
300 1
        return $this->getAdapter()->getVersionLog();
301 1
    }
302
303 6
    /**
304 6
     * Sets the current version of the environment.
305
     *
306
     * @param int $version Environment Version
307
     * @return \Phinx\Migration\Manager\Environment
308
     */
309
    public function setCurrentVersion($version)
310
    {
311
        $this->currentVersion = $version;
312
313 14
        return $this;
314
    }
315 14
316 14
    /**
317
     * Gets the current version of the environment.
318
     *
319
     * @return int
320
     */
321
    public function getCurrentVersion()
322
    {
323
        // We don't cache this code as the current version is pretty volatile.
324 17
        // TODO - that means they're no point in a setter then?
325
        // maybe we should cache and call a reset() method everytime a migration is run
326 17
        $versions = $this->getVersions();
327 12
        $version = 0;
328
329 11
        if (!empty($versions)) {
330 3
            $version = end($versions);
331 1
        }
332
333
        $this->setCurrentVersion($version);
334 2
335 2
        return $this->currentVersion;
336 2
    }
337 10
338 1
    /**
339
     * Sets the database adapter.
340
     *
341 9
     * @param \Phinx\Db\Adapter\AdapterInterface $adapter Database Adapter
342
     * @return \Phinx\Migration\Manager\Environment
343 9
     */
344
    public function setAdapter(AdapterInterface $adapter)
345
    {
346 8
        $this->adapter = $adapter;
347
348 8
        return $this;
349
    }
350
351
    /**
352
     * Gets the database adapter.
353 8
     *
354 5
     * @return \Phinx\Db\Adapter\AdapterInterface
355 5
     */
356
    public function getAdapter()
357 8
    {
358 5
        if (isset($this->adapter)) {
359 5
            return $this->adapter;
360
        }
361
        if (isset($this->options['connection'])) {
362 8
            if (!($this->options['connection'] instanceof \PDO)) {
363 1
                throw new \RuntimeException('The specified connection is not a PDO instance');
364 1
            }
365 1
366
            $this->options['connection']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
367 8
            $this->options['adapter'] = $this->options['connection']->getAttribute(\PDO::ATTR_DRIVER_NAME);
368
        }
369 8
        if (!isset($this->options['adapter'])) {
370
            throw new \RuntimeException('No adapter was specified for environment: ' . $this->getName());
371
        }
372
373
        $factory = AdapterFactory::instance();
374
        $adapter = $factory
375
            ->getAdapter($this->options['adapter'], $this->options);
376
377
        // Automatically time the executed commands
378 1
        $adapter = $factory->getWrapper('timed', $adapter);
379
380 1
        if (isset($this->options['wrapper'])) {
381 1
            $adapter = $factory
382
                ->getWrapper($this->options['wrapper'], $adapter);
383
        }
384
385
        if ($this->getInput()) {
386
            $adapter->setInput($this->getInput());
387
        }
388
389 1
        if ($this->getOutput()) {
390
            $adapter->setOutput($this->getOutput());
391 1
        }
392
393
        // Use the TablePrefixAdapter if table prefix/suffixes are in use
394
        if ($adapter->hasOption('table_prefix') || $adapter->hasOption('table_suffix')) {
395
            $adapter = AdapterFactory::instance()
396
                ->getWrapper('prefix', $adapter);
397
        }
398
399
        $this->setAdapter($adapter);
400
401
        return $adapter;
402
    }
403
404
    /**
405
     * Sets the schema table name.
406
     *
407
     * @param string $schemaTableName Schema Table Name
408
     * @return \Phinx\Migration\Manager\Environment
409
     */
410
    public function setSchemaTableName($schemaTableName)
411
    {
412
        $this->schemaTableName = $schemaTableName;
413
414
        return $this;
415
    }
416
417
    /**
418
     * Gets the schema table name.
419
     *
420
     * @return string
421
     */
422
    public function getSchemaTableName()
423
    {
424
        return $this->schemaTableName;
425
    }
426
}
427