setup_cmd_database   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 305
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 131
dl 0
loc 305
rs 9.0399
c 0
b 0
f 0
wmc 42

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 26 2
A connect() 0 22 5
A drop() 0 15 3
A _merge_defaults() 0 15 4
A defaults() 0 24 3
B exec() 0 39 11
C create() 0 70 14

How to fix   Complexity   

Complex Class

Complex classes like setup_cmd_database often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use setup_cmd_database, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * eGgroupWare setup - test or create the database
4
 *
5
 * @link http://www.egroupware.org
6
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7
 * @package setup
8
 * @copyright (c) 2007-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 * @version $Id$
11
 */
12
13
use EGroupware\Api;
14
15
/**
16
 * setup command: test or create the database
17
 */
18
class setup_cmd_database extends setup_cmd
19
{
20
	/**
21
	 * Allow to run this command via setup-cli
22
	 */
23
	const SETUP_CLI_CALLABLE = true;
24
25
	/**
26
	 * Maximum length of database name (at least for MySQL this is the limit)
27
	 *
28
	 * @var int
29
	 */
30
	const MAX_DB_NAME_LEN = 16;
31
32
	/**
33
	 * Instance of Api\Db to connect or create the db
34
	 *
35
	 * @var Api\Db
36
	 */
37
	private $test_db;
38
39
	/**
40
	 * Constructor
41
	 *
42
	 * @param string/array $domain domain-name to customize the defaults or array with all parameters
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/array at position 0 could not be parsed: Unknown type name 'string/array' at position 0 in string/array.
Loading history...
43
	 * @param string $db_type db-type (mysql, pgsql, ...)
44
	 * @param string $db_host =null
45
	 * @param string $db_port =null
46
	 * @param string $db_name =null
47
	 * @param string $db_user =null
48
	 * @param string $db_pass =null
49
	 * @param string $db_root =null
50
	 * @param string $db_root_pw =null
51
	 * @param string $sub_command ='create_db' 'create_db', 'test_db', 'test_db_root'
52
	 * @param string $db_grant_host ='localhost' host/ip of webserver for grant
53
	 * @param boolean $make_db_name_unique =false true: if create fails because db exists,
54
	 * 	try creating a unique name by shortening the name and adding a number to it
55
	 */
56
	function __construct($domain,$db_type=null,$db_host=null,$db_port=null,$db_name=null,$db_user=null,$db_pass=null,
57
		$db_root=null,$db_root_pw=null,$sub_command='create_db',$db_grant_host='localhost',$make_db_name_unique=false)
58
	{
59
		if (!is_array($domain))
60
		{
61
			$data = array(
62
				'domain'  => $domain,
63
				'db_type' => $db_type,
64
				'db_host' => $db_host,
65
				'db_port' => $db_port,
66
				'db_name' => $db_name,
67
				'db_user' => $db_user,
68
				'db_pass' => $db_pass,
69
				'db_root' => $db_root,
70
				'db_root_pw' => $db_root_pw,
71
				'sub_command' => $sub_command,
72
				'db_grant_host' => $db_grant_host,
73
				'make_db_name_unique' => $make_db_name_unique,
74
			);
75
		}
76
		else
77
		{
78
			$data = $domain;
79
		}
80
		// need to incorporate correct defaults for given database type
81
		admin_cmd::__construct(array_merge(self::defaults($data['db_type']), array_diff($data, array(null))));
82
	}
83
84
	/**
85
	 * run the command: test or create database
86
	 *
87
	 * @param boolean $check_only =false only run the checks (and throw the exceptions), but not the command itself
88
	 * @return string success message
89
	 * @throws Exception(lang('Wrong credentials to access the header.inc.php file!'),2);
90
	 * @throws Exception('header.inc.php not found!');
91
	 */
92
	protected function exec($check_only=false)
93
	{
94
		if (!empty($this->domain) && !preg_match('/^([a-z0-9_-]+\.)*[a-z0-9]+/i',$this->domain))
0 ignored issues
show
Bug Best Practice introduced by
The property domain does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
95
		{
96
			throw new Api\Exception\WrongUserinput(lang("'%1' is no valid domain name!",$this->domain));
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $this->domain. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

96
			throw new Api\Exception\WrongUserinput(/** @scrutinizer ignore-call */ lang("'%1' is no valid domain name!",$this->domain));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
97
		}
98
		if ($this->remote_id && $check_only) return true;	// further checks can only done locally
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->remote_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
99
100
		$this->_merge_defaults();
101
		//_debug_array($this->as_array());
102
103
		try {
104
			switch($this->sub_command)
0 ignored issues
show
Bug Best Practice introduced by
The property sub_command does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
105
			{
106
				case 'test_db_root':
107
					$msg = $this->connect($this->db_root,$this->db_root_pw,$this->db_meta);
0 ignored issues
show
Bug Best Practice introduced by
The property db_meta does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_root_pw does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_root does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
108
					break;
109
				case 'test_db':
110
					$msg = $this->connect();
111
					break;
112
				case 'drop':
113
					$msg = $this->drop();
114
					break;
115
				case 'create_db':
116
				default:
117
					$msg = $this->create();
118
					break;
119
			}
120
		}
121
		catch (Exception $e) {
122
			// we catch the exception to properly restore the db
123
		}
124
		$this->restore_db();
125
126
		if ($e)
127
		{
128
			throw $e;
129
		}
130
		return $msg;
131
	}
132
133
	/**
134
	 * Connect to database
135
	 *
136
	 * @param string $user =null default $this->db_user
137
	 * @param string $pass =null default $this->db_pass
138
	 * @param string $name =null default $this->db_name
139
	 * @throws Api\Exception\WrongUserinput Can not connect to database ...
140
	 */
141
	private function connect($user=null,$pass=null,$name=null)
142
	{
143
		// propagate all db_* vars
144
		$this->test_db = new Api\Db($this->data);
0 ignored issues
show
Bug Best Practice introduced by
The property $data is declared private in admin_cmd. Since you implement __get, consider adding a @property or @property-read.
Loading history...
145
146
		$error_rep = error_reporting();
147
		error_reporting($error_rep & ~E_WARNING);	// switch warnings off, in case they are on
148
		try {
149
			$this->test_db->connect($name, null, null, $user, $pass);
150
		}
151
		catch (Exception $e) {
152
			// just give a nicer error, after switching error_reporting on again
153
		}
154
		error_reporting($error_rep);
155
156
		if ($e)
157
		{
158
			throw new Api\Exception\WrongUserinput(lang('Can not connect to %1 database %2 on host %3 using user %4!',
159
				$this->db_type,$name,$this->db_host.($this->db_port?':'.$this->db_port:''),$user).' ('.$e->getMessage().')');
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $this->db_type. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

159
			throw new Api\Exception\WrongUserinput(/** @scrutinizer ignore-call */ lang('Can not connect to %1 database %2 on host %3 using user %4!',

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug Best Practice introduced by
The property db_port does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_type does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_host does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
160
		}
161
		return lang('Successful connected to %1 database %2 on %3 using user %4.',
162
				$this->db_type,$name,$this->db_host.($this->db_port?':'.$this->db_port:''),$user);
163
	}
164
165
	/**
166
	 * Check and if does not yet exist create the new database and user
167
	 *
168
	 * The check will fail if the database exists, but already contains tables
169
	 *
170
	 * if $this->make_db_name_unique is set, a decrementing nummeric prefix gets
171
	 * added to $this->db_name AND $this->db_user, if db already exists.
172
	 *
173
	 * @return string with success message
174
	 * @throws Api\Exception\WrongUserinput
175
	 */
176
	private function create()
177
	{
178
		static $try_make_unique = 0;	// to limit trials to create a unique name
179
180
		// shorten db-name/-user to self::MAX_DB_NAME_LEN chars
181
		if ($this->make_db_name_unique && strlen($this->db_name) > self::MAX_DB_NAME_LEN)
0 ignored issues
show
Bug Best Practice introduced by
The property make_db_name_unique does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
182
		{
183
			$this->set_defaults['db_name'] = $this->db_name =
0 ignored issues
show
Bug Best Practice introduced by
The property db_name does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
184
			$this->set_defaults['db_user'] = $this->db_user = // change user too (otherwise existing user/db could not connect any more!)
0 ignored issues
show
Bug Best Practice introduced by
The property db_user does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
185
				substr(str_replace(array('.', '-'), '_', $this->db_name),0,self::MAX_DB_NAME_LEN);
186
		}
187
		try {
188
			$msg = $this->connect();
189
		}
190
		catch (Api\Exception\WrongUserinput $e) {
191
			// db or user not working --> connect as root and create it
192
			try {
193
				$this->test_db->create_database($this->db_root,$this->db_root_pw,$this->db_charset,$this->db_grant_host);
0 ignored issues
show
Bug Best Practice introduced by
The property db_grant_host does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_root_pw does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_charset does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_root does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
194
				$this->connect();
195
			}
196
			catch(Api\Db\Exception $e) {	// catches failed to create database
197
				// try connect as root to check if wrong root/root_pw is the problem
198
				$this->connect($this->db_root,$this->db_root_pw,$this->db_meta);
0 ignored issues
show
Bug Best Practice introduced by
The property db_meta does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
199
200
				// if we should create a db with a unique name (try it only N times, not endless!)
201
				if ($this->make_db_name_unique && $try_make_unique++ < 20)
202
				{
203
					// check if we can connect as root to the db to create --> db exists already
204
					try {
205
						$this->connect($this->db_root,$this->db_root_pw);
206
						// create new db_name by incrementing an existing numeric postfix
207
						$matches = null;
208
						if (preg_match('/([0-9]+)$/',$this->db_name,$matches))
209
						{
210
							$num = (string)(++$matches[1]);
211
						}
212
						else	// or adding one starting with 2
213
						{
214
							$num = '2';
215
						}
216
						$this->set_defaults['db_name'] = $this->db_name =
217
						$this->set_defaults['db_user'] = $this->db_user = // change user too (otherwise existing user/db could not connect any more!)
218
							substr($this->db_name,0,self::MAX_DB_NAME_LEN-strlen($num)).$num;
219
220
						return $this->create();
221
					}
222
					catch (Api\Exception\WrongUserinput $e2)
223
					{
224
						// we can NOT connect to db as root --> ignore exception to give general error
225
					}
226
				}
227
				// if not give general error
228
				throw new Api\Exception\WrongUserinput(lang('Can not create %1 database %2 on %3 for user %4!',
229
					$this->db_type,$this->db_name,$this->db_host.($this->db_port?':'.$this->db_port:''),$this->db_user));
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $this->db_type. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

229
				throw new Api\Exception\WrongUserinput(/** @scrutinizer ignore-call */ lang('Can not create %1 database %2 on %3 for user %4!',

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug Best Practice introduced by
The property db_type does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_host does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_port does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
230
			}
231
			$msg = lang('Successful connected to %1 on %3 and created database %2 for user %4.',
232
					$this->db_type,$this->db_name,$this->db_host.($this->db_port?':'.$this->db_port:''),$this->db_user);
233
		}
234
		// check if it already contains tables
235
		if (($tables = $this->test_db->table_names()))
236
		{
237
			foreach($tables as &$table)
238
			{
239
				$table = $table['table_name'];
240
			}
241
			throw new Api\Exception\WrongUserinput(lang('%1 database %2 on %3 already contains the following tables:',
242
				$this->db_type,$this->db_name,$this->db_host.($this->db_port?':'.$this->db_port:'')).' '.
243
				implode(', ',$tables));
244
		}
245
		return $msg;
246
	}
247
248
	/**
249
	 * Drop database and user
250
	 *
251
	 * @return string with success message
252
	 * @throws Api\Exception\WrongUserinput
253
	 * @throws Api\Db\Exception if database not exist
254
	 */
255
	private function drop()
256
	{
257
		$this->connect($this->db_root,$this->db_root_pw,$this->db_meta);
0 ignored issues
show
Bug Best Practice introduced by
The property db_root does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_meta does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property db_root_pw does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
258
		$this->test_db->query('DROP DATABASE '.$this->test_db->name_quote($this->db_name),__LINE__,__FILE__);
259
		$msg = lang('Datebase %1 droped.',$this->db_name);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $this->db_name. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

259
		$msg = /** @scrutinizer ignore-call */ lang('Datebase %1 droped.',$this->db_name);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
260
		try {
261
			$this->test_db->query('DROP USER '.$this->test_db->quote($this->db_user).'@'.
262
				$this->test_db->quote($this->db_grant_host?$this->db_grant_host:'%'),__LINE__,__FILE__);
0 ignored issues
show
Bug Best Practice introduced by
The property db_grant_host does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
263
		}
264
		catch (Api\Db\Exception $e) {
265
			unset($e);
266
			// we make this no fatal error, as the granthost might be something else ...
267
			$msg .= ' '.lang('Error dropping User!');
268
		}
269
		return $msg;
270
	}
271
272
	/**
273
	 * Return default database settings for a given domain
274
	 *
275
	 * @param string $db_type ='mysqli'
276
	 * @return array
277
	 */
278
	static function defaults($db_type='mysqli')
279
	{
280
		switch($db_type)
281
		{
282
			case 'mysql':
283
			default:
284
				$db_type = $meta_db = 'mysql';
285
				break;
286
			case 'pgsql':
287
				$meta_db = 'template1';
288
				break;
289
		}
290
		return array(
291
			'db_type' => $db_type,
292
			'db_host' => 'localhost',
293
			'db_port' => 3306,
294
			'db_name' => 'egw_$domain',
295
			'db_user' => 'egw_$domain',
296
			'db_pass' => self::randomstring(),
297
			'db_root' => 'root',
298
			'db_root_pw' => '',	// not really a default
299
			'db_meta' => $meta_db,
300
			'db_charset' => 'utf-8',
301
			'db_grant_host' => 'localhost',
302
		);
303
	}
304
305
	/**
306
	 * Merges the default into the current properties, if they are empty or contain placeholders
307
	 */
308
	private function _merge_defaults()
309
	{
310
		foreach(self::defaults() as $name => $default)
311
		{
312
			if (!$this->$name)
313
			{
314
				//echo "<p>setting $name='{$this->$name}' to it's default='$default'</p>\n";
315
				$this->set_defaults[$name] = $this->$name = $default;
316
			}
317
			if (strpos($this->$name,'$domain') !== false)
318
			{
319
				// limit names to 16 chars (16 char is user-name limit in MySQL)
320
				$this->set_defaults[$name] = $this->$name =
321
					substr(str_replace(array('$domain','.','-'),array($this->domain,'_','_'),$this->$name),
0 ignored issues
show
Bug Best Practice introduced by
The property domain does not exist on setup_cmd_database. Since you implemented __get, consider adding a @property annotation.
Loading history...
322
					0,self::MAX_DB_NAME_LEN);
323
			}
324
		}
325
	}
326
}
327