Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
65.33% |
49 / 75 |
|
14.29% |
1 / 7 |
CRAP | |
0.00% |
0 / 1 |
AdapterFactory | |
65.33% |
49 / 75 |
|
14.29% |
1 / 7 |
48.00 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
__invoke | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getAdapter | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
2.01 | |||
getDriverName | |
37.50% |
3 / 8 |
|
0.00% |
0 / 1 |
7.91 | |||
getDriverOptions | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
getAdapterFromOptions | |
44.44% |
8 / 18 |
|
0.00% |
0 / 1 |
9.29 | |||
getAdapterFromConnectionString | |
87.50% |
28 / 32 |
|
0.00% |
0 / 1 |
6.07 |
1 | <?php |
2 | |
3 | /** |
4 | * Database utility class. May be used as a service or as a standard |
5 | * Laminas factory. |
6 | * |
7 | * PHP version 8 |
8 | * |
9 | * Copyright (C) Villanova University 2010. |
10 | * |
11 | * This program is free software; you can redistribute it and/or modify |
12 | * it under the terms of the GNU General Public License version 2, |
13 | * as published by the Free Software Foundation. |
14 | * |
15 | * This program is distributed in the hope that it will be useful, |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | * GNU General Public License for more details. |
19 | * |
20 | * You should have received a copy of the GNU General Public License |
21 | * along with this program; if not, write to the Free Software |
22 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 | * |
24 | * @category VuFind |
25 | * @package Db |
26 | * @author Demian Katz <demian.katz@villanova.edu> |
27 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
28 | * @link https://vufind.org Main Site |
29 | */ |
30 | |
31 | namespace VuFind\Db; |
32 | |
33 | use Laminas\Config\Config; |
34 | use Laminas\Db\Adapter\Adapter; |
35 | use Laminas\ServiceManager\Exception\ServiceNotCreatedException; |
36 | use Laminas\ServiceManager\Exception\ServiceNotFoundException; |
37 | use Psr\Container\ContainerExceptionInterface as ContainerException; |
38 | use Psr\Container\ContainerInterface; |
39 | |
40 | /** |
41 | * Database utility class. May be used as a service or as a standard |
42 | * Laminas factory. |
43 | * |
44 | * @category VuFind |
45 | * @package Db |
46 | * @author Demian Katz <demian.katz@villanova.edu> |
47 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
48 | * @link https://vufind.org Main Site |
49 | */ |
50 | class AdapterFactory implements \Laminas\ServiceManager\Factory\FactoryInterface |
51 | { |
52 | /** |
53 | * VuFind configuration |
54 | * |
55 | * @var Config |
56 | */ |
57 | protected $config; |
58 | |
59 | /** |
60 | * Constructor |
61 | * |
62 | * @param Config $config VuFind configuration (provided when used as service; |
63 | * omitted when used as factory) |
64 | */ |
65 | public function __construct(Config $config = null) |
66 | { |
67 | $this->config = $config ?: new Config([]); |
68 | } |
69 | |
70 | /** |
71 | * Create an object (glue code for FactoryInterface compliance) |
72 | * |
73 | * @param ContainerInterface $container Service manager |
74 | * @param string $requestedName Service being created |
75 | * @param null|array $options Extra options (optional) |
76 | * |
77 | * @return object |
78 | * |
79 | * @throws ServiceNotFoundException if unable to resolve the service. |
80 | * @throws ServiceNotCreatedException if an exception is raised when |
81 | * creating a service. |
82 | * @throws ContainerException&\Throwable if any other error occurs |
83 | */ |
84 | public function __invoke( |
85 | ContainerInterface $container, |
86 | $requestedName, |
87 | array $options = null |
88 | ) { |
89 | if (!empty($options)) { |
90 | throw new \Exception('Unexpected options sent to factory!'); |
91 | } |
92 | $this->config = $container->get(\VuFind\Config\PluginManager::class) |
93 | ->get('config'); |
94 | return $this->getAdapter(); |
95 | } |
96 | |
97 | /** |
98 | * Obtain a Laminas\DB connection using standard VuFind configuration. |
99 | * |
100 | * @param string $overrideUser Username override (leave null to use username |
101 | * from config.ini) |
102 | * @param string $overridePass Password override (leave null to use password |
103 | * from config.ini) |
104 | * |
105 | * @return Adapter |
106 | */ |
107 | public function getAdapter($overrideUser = null, $overridePass = null) |
108 | { |
109 | // Parse details from connection string: |
110 | if (!isset($this->config->Database->database)) { |
111 | throw new \Exception('"database" setting missing'); |
112 | } |
113 | return $this->getAdapterFromConnectionString( |
114 | $this->config->Database->database, |
115 | $overrideUser, |
116 | $overridePass |
117 | ); |
118 | } |
119 | |
120 | /** |
121 | * Translate the connection string protocol into a driver name. |
122 | * |
123 | * @param string $type Database type from connection string |
124 | * |
125 | * @return string |
126 | */ |
127 | public function getDriverName($type) |
128 | { |
129 | switch (strtolower($type)) { |
130 | case 'mysql': |
131 | return 'mysqli'; |
132 | case 'oci8': |
133 | return 'Oracle'; |
134 | case 'pgsql': |
135 | return 'Pdo_Pgsql'; |
136 | } |
137 | return $type; |
138 | } |
139 | |
140 | /** |
141 | * Get options for the selected driver. |
142 | * |
143 | * @param string $driver Driver name |
144 | * |
145 | * @return array |
146 | */ |
147 | protected function getDriverOptions($driver) |
148 | { |
149 | switch ($driver) { |
150 | case 'mysqli': |
151 | return ($this->config->Database->verify_server_certificate ?? false) |
152 | ? [] : [MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT]; |
153 | } |
154 | return []; |
155 | } |
156 | |
157 | /** |
158 | * Obtain a Laminas\DB connection using an option array. |
159 | * |
160 | * @param array $options Options for building adapter |
161 | * |
162 | * @return Adapter |
163 | */ |
164 | public function getAdapterFromOptions($options) |
165 | { |
166 | // Set up custom options by database type: |
167 | $driver = strtolower($options['driver']); |
168 | switch ($driver) { |
169 | case 'mysqli': |
170 | $options['charset'] = $this->config->Database->charset ?? 'utf8mb4'; |
171 | if (strtolower($options['charset']) === 'latin1') { |
172 | throw new \Exception( |
173 | 'The latin1 encoding is no longer supported for MySQL' |
174 | . ' databases in VuFind. Please convert your database' |
175 | . ' to utf8 using VuFind 7.x or earlier BEFORE' |
176 | . ' upgrading to this version.' |
177 | ); |
178 | } |
179 | $options['options'] = ['buffer_results' => true]; |
180 | break; |
181 | } |
182 | |
183 | // Set up database connection: |
184 | $adapter = new Adapter($options); |
185 | |
186 | // Special-case setup: |
187 | if ($driver == 'pdo_pgsql' && isset($this->config->Database->schema)) { |
188 | // Set schema |
189 | $statement = $adapter->createStatement( |
190 | 'SET search_path TO ' . $this->config->Database->schema |
191 | ); |
192 | $statement->execute(); |
193 | } |
194 | |
195 | return $adapter; |
196 | } |
197 | |
198 | /** |
199 | * Obtain a Laminas\DB connection using a connection string. |
200 | * |
201 | * @param string $connectionString Connection string of the form |
202 | * [db_type]://[username]:[password]@[host]/[db_name] |
203 | * @param string $overrideUser Username override (leave null to use username |
204 | * from connection string) |
205 | * @param string $overridePass Password override (leave null to use password |
206 | * from connection string) |
207 | * |
208 | * @return Adapter |
209 | */ |
210 | public function getAdapterFromConnectionString( |
211 | $connectionString, |
212 | $overrideUser = null, |
213 | $overridePass = null |
214 | ) { |
215 | [$type, $details] = explode('://', $connectionString); |
216 | preg_match('/(.+)@([^@]+)\/(.+)/', $details, $matches); |
217 | $credentials = $matches[1] ?? null; |
218 | $host = $port = null; |
219 | if (isset($matches[2])) { |
220 | if (str_contains($matches[2], ':')) { |
221 | [$host, $port] = explode(':', $matches[2]); |
222 | } else { |
223 | $host = $matches[2]; |
224 | } |
225 | } |
226 | $dbName = $matches[3] ?? null; |
227 | if (strstr($credentials, ':')) { |
228 | [$username, $password] = explode(':', $credentials, 2); |
229 | } else { |
230 | $username = $credentials; |
231 | $password = null; |
232 | } |
233 | $username = $overrideUser ?? $username; |
234 | $password = $overridePass ?? $password; |
235 | |
236 | $driverName = $this->getDriverName($type); |
237 | $driverOptions = $this->getDriverOptions($driverName); |
238 | |
239 | // Set up default options: |
240 | $options = [ |
241 | 'driver' => $driverName, |
242 | 'hostname' => $host, |
243 | 'username' => $username, |
244 | 'password' => $password, |
245 | 'database' => $dbName, |
246 | 'use_ssl' => $this->config->Database->use_ssl ?? false, |
247 | 'driver_options' => $driverOptions, |
248 | ]; |
249 | if (!empty($port)) { |
250 | $options['port'] = $port; |
251 | } |
252 | // Get extra custom options from config: |
253 | $extraOptions = isset($this->config->Database->extra_options) |
254 | ? $this->config->Database->extra_options->toArray() |
255 | : []; |
256 | // Note: $options takes precedence over $extraOptions -- we don't want users |
257 | // using extended settings to override values from core settings. |
258 | return $this->getAdapterFromOptions($options + $extraOptions); |
259 | } |
260 | } |