Completed
Push — master ( 4ad6bd...3873e4 )
by Ingo
11:53
created

TempDatabase::resetDBSchema()   C

Complexity

Conditions 8
Paths 2

Size

Total Lines 41
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 22
nc 2
nop 1
dl 0
loc 41
rs 5.3846
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\ORM\Connect;
4
5
use SilverStripe\Core\ClassInfo;
6
use SilverStripe\Core\Injector\Injectable;
7
use SilverStripe\Core\Injector\Injector;
8
use SilverStripe\Dev\TestOnly;
9
use SilverStripe\ORM\DataExtension;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\ORM\DB;
12
13
class TempDatabase
14
{
15
    use Injectable;
16
17
    /**
18
     * Connection name
19
     *
20
     * @var string
21
     */
22
    protected $name = null;
23
24
    /**
25
     * Create a new temp database
26
     *
27
     * @param string $name DB Connection name to use
28
     */
29
    public function __construct($name = 'default')
30
    {
31
        $this->name = $name;
32
    }
33
34
    /**
35
     * Check if the given name matches the temp_db pattern
36
     *
37
     * @param string $name
38
     * @return bool
39
     */
40
    protected function isDBTemp($name)
41
    {
42
        $prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
43
        $result = preg_match(
44
            sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')),
45
            $name
46
        );
47
        return $result === 1;
48
    }
49
50
    /**
51
     * @return Database
52
     */
53
    protected function getConn()
54
    {
55
        return DB::get_conn($this->name);
56
    }
57
58
    /**
59
     * Returns true if we are currently using a temporary database
60
     *
61
     * @return bool
62
     */
63
    public function isUsed()
64
    {
65
        $selected = $this->getConn()->getSelectedDatabase();
66
        return $this->isDBTemp($selected);
67
    }
68
69
    /**
70
     * Destroy the current temp database
71
     */
72
    public function kill()
73
    {
74
        // Delete our temporary database
75
        if (!$this->isUsed()) {
76
            return;
77
        }
78
79
        // Check the database actually exists
80
        $dbConn = $this->getConn();
81
        $dbName = $dbConn->getSelectedDatabase();
82
        if (!$dbConn->databaseExists($dbName)) {
83
            return;
84
        }
85
86
        // Some DataExtensions keep a static cache of information that needs to
87
        // be reset whenever the database is killed
88
        foreach (ClassInfo::subclassesFor(DataExtension::class) as $class) {
89
            $toCall = array($class, 'on_db_reset');
90
            if (is_callable($toCall)) {
91
                call_user_func($toCall);
92
            }
93
        }
94
95
        // echo "Deleted temp database " . $dbConn->currentDatabase() . "\n";
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% 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...
96
        $dbConn->dropSelectedDatabase();
97
    }
98
99
    /**
100
     * Remove all content from the temporary database.
101
     */
102
    public function clearAllData()
103
    {
104
        if (!$this->isUsed()) {
105
            return;
106
        }
107
108
        $this->getConn()->clearAllData();
109
110
        // Some DataExtensions keep a static cache of information that needs to
111
        // be reset whenever the database is cleaned out
112
        $classes = array_merge(
113
            ClassInfo::subclassesFor(DataExtension::class),
114
            ClassInfo::subclassesFor(DataObject::class)
115
        );
116
        foreach ($classes as $class) {
117
            $toCall = array($class, 'on_db_reset');
118
            if (is_callable($toCall)) {
119
                call_user_func($toCall);
120
            }
121
        }
122
    }
123
124
    /**
125
     * Create temp DB without creating extra objects
126
     *
127
     * @return string DB name
128
     */
129
    public function build()
130
    {
131
        // Disable PHPUnit error handling
132
        $oldErrorHandler = set_error_handler(null);
133
134
        // Create a temporary database, and force the connection to use UTC for time
135
        $dbConn = $this->getConn();
136
        $prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
137
        do {
138
            $dbname = strtolower(sprintf('%stmpdb_%s_%s', $prefix, time(), rand(1000000, 9999999)));
139
        } while ($dbConn->databaseExists($dbname));
140
141
        $dbConn->selectDatabase($dbname, true);
142
143
        $this->resetDBSchema();
144
145
        // Reinstate PHPUnit error handling
146
        set_error_handler($oldErrorHandler);
147
148
        // Ensure test db is killed on exit
149
        register_shutdown_function(function () {
150
            $this->kill();
151
        });
152
153
        return $dbname;
154
    }
155
156
    /**
157
     * Clear all temp DBs on this connection
158
     *
159
     * Note: This will output results to stdout unless suppressOutput
160
     * is set on the current db schema
161
     */
162
    public function deleteAll()
163
    {
164
        $schema = $this->getConn()->getSchemaManager();
165
        foreach ($schema->databaseList() as $dbName) {
166
            if ($this->isDBTemp($dbName)) {
167
                $schema->dropDatabase($dbName);
168
                $schema->alterationMessage("Dropped database \"$dbName\"", 'deleted');
169
                flush();
170
            }
171
        }
172
    }
173
174
    /**
175
     * Reset the testing database's schema.
176
     *
177
     * @param array $extraDataObjects List of extra dataobjects to build
178
     */
179
    public function resetDBSchema(array $extraDataObjects = [])
180
    {
181
        if (!$this->isUsed()) {
182
            return;
183
        }
184
185
        DataObject::reset();
186
187
        // clear singletons, they're caching old extension info which is used in DatabaseAdmin->doBuild()
188
        Injector::inst()->unregisterObjects(DataObject::class);
189
190
        $dataClasses = ClassInfo::subclassesFor(DataObject::class);
191
        array_shift($dataClasses);
192
193
        $schema = $this->getConn()->getSchemaManager();
194
        $schema->quiet();
195
        $schema->schemaUpdate(function () use ($dataClasses, $extraDataObjects) {
196
            foreach ($dataClasses as $dataClass) {
197
                // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
198
                if (class_exists($dataClass)) {
199
                    $SNG = singleton($dataClass);
200
                    if (!($SNG instanceof TestOnly)) {
201
                        $SNG->requireTable();
202
                    }
203
                }
204
            }
205
206
            // If we have additional dataobjects which need schema, do so here:
207
            if ($extraDataObjects) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $extraDataObjects of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
208
                foreach ($extraDataObjects as $dataClass) {
209
                    $SNG = singleton($dataClass);
210
                    if (singleton($dataClass) instanceof DataObject) {
211
                        $SNG->requireTable();
212
                    }
213
                }
214
            }
215
        });
216
217
        ClassInfo::reset_db_cache();
218
        DataObject::singleton()->flushCache();
219
    }
220
}
221