1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SilverStripe\Dev\Install; |
4
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
6
|
|
|
use Psr\SimpleCache\CacheInterface; |
7
|
|
|
use SilverStripe\Core\Injector\Injector; |
8
|
|
|
use SilverStripe\Dev\Deprecation; |
9
|
|
|
use SilverStripe\Core\Flushable; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* This class keeps track of the available database adapters |
13
|
|
|
* and provides a meaning of registering community built |
14
|
|
|
* adapters in to the installer process. |
15
|
|
|
* |
16
|
|
|
* @author Tom Rix |
17
|
|
|
*/ |
18
|
|
|
class DatabaseAdapterRegistry implements Flushable |
19
|
|
|
{ |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Default database connector registration fields |
23
|
|
|
* |
24
|
|
|
* @var array |
25
|
|
|
*/ |
26
|
|
|
private static $default_fields = array( |
27
|
|
|
'server' => array( |
28
|
|
|
'title' => 'Database server', |
29
|
|
|
'envVar' => 'SS_DATABASE_SERVER', |
30
|
|
|
'default' => 'localhost' |
31
|
|
|
), |
32
|
|
|
'username' => array( |
33
|
|
|
'title' => 'Database username', |
34
|
|
|
'envVar' => 'SS_DATABASE_USERNAME', |
35
|
|
|
'default' => 'root' |
36
|
|
|
), |
37
|
|
|
'password' => array( |
38
|
|
|
'title' => 'Database password', |
39
|
|
|
'envVar' => 'SS_DATABASE_PASSWORD', |
40
|
|
|
'default' => 'password' |
41
|
|
|
), |
42
|
|
|
'database' => array( |
43
|
|
|
'title' => 'Database name', |
44
|
|
|
'default' => 'SS_mysite', |
45
|
|
|
'attributes' => array( |
46
|
|
|
"onchange" => "this.value = this.value.replace(/[\/\\:*?"<>|. \t]+/g,'');" |
47
|
|
|
) |
48
|
|
|
), |
49
|
|
|
); |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Internal array of registered database adapters |
53
|
|
|
* |
54
|
|
|
* @var array |
55
|
|
|
*/ |
56
|
|
|
private static $adapters = array(); |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Add new adapter to the registry |
60
|
|
|
* |
61
|
|
|
* @param array $config Associative array of configuration details. This must include: |
62
|
|
|
* - title |
63
|
|
|
* - class |
64
|
|
|
* - helperClass |
65
|
|
|
* - supported |
66
|
|
|
* This SHOULD include: |
67
|
|
|
* - fields |
68
|
|
|
* - helperPath (if helperClass can't be autoloaded via psr-4/-0) |
69
|
|
|
* - missingExtensionText |
70
|
|
|
* - module OR missingModuleText |
71
|
|
|
*/ |
72
|
|
|
public static function register($config) |
73
|
|
|
{ |
74
|
|
|
// Validate config |
75
|
|
|
$missing = array_diff(['title', 'class', 'helperClass', 'supported'], array_keys($config)); |
76
|
|
|
if ($missing) { |
|
|
|
|
77
|
|
|
throw new InvalidArgumentException( |
78
|
|
|
"Missing database helper config keys: '" . implode("', '", $missing) . "'" |
79
|
|
|
); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
// Guess missing module text if not given |
83
|
|
|
if (empty($config['missingModuleText'])) { |
84
|
|
|
if (empty($config['module'])) { |
85
|
|
|
$moduleText = 'Module for database connector ' . $config['title'] . 'is missing.'; |
86
|
|
|
} else { |
87
|
|
|
$moduleText = "The SilverStripe module '" . $config['module'] . "' is missing."; |
88
|
|
|
} |
89
|
|
|
$config['missingModuleText'] = $moduleText |
90
|
|
|
. ' Please install it via composer or from http://addons.silverstripe.org/.'; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
// Set missing text |
94
|
|
|
if (empty($config['missingExtensionText'])) { |
95
|
|
|
$config['missingExtensionText'] = 'The PHP extension is missing, please enable or install it.'; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
// set default fields if none are defined already |
99
|
|
|
if (!isset($config['fields'])) { |
100
|
|
|
$config['fields'] = self::$default_fields; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
self::$adapters[$config['class']] = $config; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Unregisters a database connector by classname |
108
|
|
|
* |
109
|
|
|
* @param string $class |
110
|
|
|
*/ |
111
|
|
|
public static function unregister($class) |
112
|
|
|
{ |
113
|
|
|
unset(self::$adapters[$class]); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Detects all _register_database.php files and invokes them. |
118
|
|
|
* Searches through vendor/*\/* folders only, |
119
|
|
|
* does not support "legacy" folder location in webroot |
120
|
|
|
*/ |
121
|
|
|
public static function autodiscover() |
122
|
|
|
{ |
123
|
|
|
// Search through all composer packages in vendor |
124
|
|
|
foreach (glob(BASE_PATH . '/vendor/*', GLOB_ONLYDIR) as $vendor) { |
125
|
|
|
foreach (glob($vendor . '/*', GLOB_ONLYDIR) as $directory) { |
126
|
|
|
if (file_exists($directory . '/_register_database.php')) { |
127
|
|
|
include_once($directory . '/_register_database.php'); |
128
|
|
|
} |
129
|
|
|
} |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Detects all _configure_database.php files and invokes them |
135
|
|
|
* Called by ConfigureFromEnv.php. |
136
|
|
|
* Searches through vendor/ folder only, |
137
|
|
|
* does not support "legacy" folder location in webroot |
138
|
|
|
* |
139
|
|
|
* @param array $config Config to update. If not provided fall back to global $databaseConfig. |
140
|
|
|
* In 5.0.0 this will be mandatory and the global will be removed. |
141
|
|
|
*/ |
142
|
|
|
public static function autoconfigure(&$config = null) |
143
|
|
|
{ |
144
|
|
|
if (!isset($config)) { |
145
|
|
|
Deprecation::notice('5.0', 'Configuration via global is deprecated'); |
146
|
|
|
global $databaseConfig; |
147
|
|
|
} else { |
148
|
|
|
$databaseConfig = $config; |
149
|
|
|
} |
150
|
|
|
foreach (static::getConfigureDatabasePaths() as $configureDatabasePath) { |
151
|
|
|
include_once $configureDatabasePath; |
152
|
|
|
} |
153
|
|
|
// Update modified variable |
154
|
|
|
$config = $databaseConfig; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Including _configure_database.php is a legacy method of configuring a database |
159
|
|
|
* It's still used by https://github.com/silverstripe/silverstripe-sqlite3 |
160
|
|
|
*/ |
161
|
|
|
protected static function getConfigureDatabasePaths(): array |
162
|
|
|
{ |
163
|
|
|
// autoconfigure() will get called before flush() on ?flush, so manually flush just to ensure no weirdness |
164
|
|
|
if (isset($_GET['flush'])) { |
165
|
|
|
static::flush(); |
166
|
|
|
} |
167
|
|
|
$cache = static::getCache(); |
168
|
|
|
$key = __FUNCTION__; |
169
|
|
|
if ($cache->has($key)) { |
170
|
|
|
return (array) $cache->get($key); |
171
|
|
|
} else { |
172
|
|
|
try { |
173
|
|
|
$paths = glob(BASE_PATH . '/vendor/*/*/_configure_database.php'); |
174
|
|
|
} catch (Exception $e) { |
|
|
|
|
175
|
|
|
$paths = []; |
176
|
|
|
} |
177
|
|
|
$cache->set($key, $paths); |
178
|
|
|
return $paths; |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
public static function getCache(): CacheInterface |
183
|
|
|
{ |
184
|
|
|
return Injector::inst()->get(CacheInterface::class . '.DatabaseAdapterRegistry'); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
public static function flush() |
188
|
|
|
{ |
189
|
|
|
static::getCache()->clear(); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Return all registered adapters |
194
|
|
|
* |
195
|
|
|
* @return array |
196
|
|
|
*/ |
197
|
|
|
public static function get_adapters() |
198
|
|
|
{ |
199
|
|
|
return self::$adapters; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Returns registry data for a class |
204
|
|
|
* |
205
|
|
|
* @param string $class |
206
|
|
|
* @return array List of adapter properties |
207
|
|
|
*/ |
208
|
|
|
public static function get_adapter($class) |
209
|
|
|
{ |
210
|
|
|
if (isset(self::$adapters[$class])) { |
211
|
|
|
return self::$adapters[$class]; |
212
|
|
|
} |
213
|
|
|
return null; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Retrieves default field configuration |
218
|
|
|
* |
219
|
|
|
* @return array |
220
|
|
|
*/ |
221
|
|
|
public static function get_default_fields() |
222
|
|
|
{ |
223
|
|
|
return self::$default_fields; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Build configuration helper for a given class |
228
|
|
|
* |
229
|
|
|
* @param string $databaseClass Name of class |
230
|
|
|
* @return DatabaseConfigurationHelper|null Instance of helper, or null if cannot be loaded |
231
|
|
|
*/ |
232
|
|
|
public static function getDatabaseConfigurationHelper($databaseClass) |
233
|
|
|
{ |
234
|
|
|
$adapters = static::get_adapters(); |
235
|
|
|
if (empty($adapters[$databaseClass]) || empty($adapters[$databaseClass]['helperClass'])) { |
236
|
|
|
return null; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
// Load if path given |
240
|
|
|
if (isset($adapters[$databaseClass]['helperPath'])) { |
241
|
|
|
include_once $adapters[$databaseClass]['helperPath']; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
// Construct |
245
|
|
|
$class = $adapters[$databaseClass]['helperClass']; |
246
|
|
|
return (class_exists($class)) ? new $class() : null; |
247
|
|
|
} |
248
|
|
|
} |
249
|
|
|
|
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.