_getPortableTableColumnDefinition()   F
last analyzed

Complexity

Conditions 32
Paths 13312

Size

Total Lines 106

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 32
nc 13312
nop 1
dl 0
loc 106
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Viktar Dubiniuk <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2018, ownCloud GmbH
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
22
namespace OC\DB;
23
24
use Doctrine\DBAL\DBALException;
25
use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
26
use Doctrine\DBAL\Platforms\MySqlPlatform;
27
use Doctrine\DBAL\Schema\Column;
28
use Doctrine\DBAL\Types\Type;
29
30
/**
31
 * Class MySqlSchemaColumnDefinitionListener
32
 *
33
 * @package OC\DB
34
 *
35
 * This class contains the smallest portion of native Doctrine code taken from
36
 * Doctrine\DBAL\Platforms\MySqlSchemaManager that allows to bypass the call to
37
 * native _getPortableTableColumnDefinition method
38
 *
39
 * TODO: remove once https://github.com/owncloud/core/issues/28695 is fixed and Doctrine upgraded
40
 */
41
class MySqlSchemaColumnDefinitionListener {
42
	/**
43
	 * @var \Doctrine\DBAL\Platforms\AbstractPlatform
44
	 */
45
	protected $_platform;
46
47
	public function onSchemaColumnDefinition(SchemaColumnDefinitionEventArgs $eventArgs) {
48
		// We need an instance of platform with ownCloud-specific mappings
49
		//  this part  can't be moved to constructor - it leads to an infinite recursion
50
		if ($this->_platform === null) {
51
			$this->_platform = \OC::$server->getDatabaseConnection()->getDatabasePlatform();
52
		}
53
54
		$version = \OC::$server->getDatabaseConnection()->getDatabaseVersionString();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OCP\IDBConnection as the method getDatabaseVersionString() does only exist in the following implementations of said interface: OC\DB\Connection, OC\DB\OracleConnection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
55
		$mariadb = \stripos($version, 'mariadb') !== false;
56
		if ($mariadb && \version_compare($this->getMariaDbMysqlVersionNumber($version), '10.2.7', '>=')) {
57
			$tableColumn = $eventArgs->getTableColumn();
58
			try {
59
				$column = $this->_getPortableTableColumnDefinition($tableColumn);
60
				$eventArgs->preventDefault();
61
				$eventArgs->setColumn($column);
62
			} catch (DBALException $e) {
63
				// Pass
64
			}
65
		}
66
	}
67
68
	/**
69
	 * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns
70
	 * the type given as default.
71
	 *
72
	 * @param string $comment
73
	 * @param string $currentType
74
	 *
75
	 * @return string
76
	 */
77
	public function extractDoctrineTypeFromComment($comment, $currentType) {
78
		if (\preg_match("(\(DC2Type:([a-zA-Z0-9_]+)\))", $comment, $match)) {
79
			$currentType = $match[1];
80
		}
81
82
		return $currentType;
83
	}
84
	
85
	/**
86
	 * @param string $comment
87
	 * @param string $type
88
	 *
89
	 * @return string
90
	 */
91
	public function removeDoctrineTypeFromComment($comment, $type) {
92
		return \str_replace('(DC2Type:'.$type.')', '', $comment);
93
	}
94
	
95
	protected function _getPortableTableColumnDefinition($tableColumn) {
96
		$tableColumn = \array_change_key_case($tableColumn, CASE_LOWER);
97
98
		$dbType = \strtolower($tableColumn['type']);
99
		$dbType = \strtok($dbType, '(), ');
100
		if (isset($tableColumn['length'])) {
101
			$length = $tableColumn['length'];
102
		} else {
103
			$length = \strtok('(), ');
104
		}
105
106
		$fixed = null;
107
108
		if (! isset($tableColumn['name'])) {
109
			$tableColumn['name'] = '';
110
		}
111
112
		$scale = null;
113
		$precision = null;
114
115
		$type = $this->_platform->getDoctrineTypeMapping($dbType);
116
117
		// In cases where not connected to a database DESCRIBE $table does not return 'Comment'
118
		if (isset($tableColumn['comment'])) {
119
			$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
120
			$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
121
		}
122
123
		switch ($dbType) {
124
			case 'char':
125
			case 'binary':
126
				$fixed = true;
127
				break;
128
			case 'float':
129
			case 'double':
130
			case 'real':
131
			case 'numeric':
132
			case 'decimal':
133
				if (\preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['type'], $match)) {
134
					$precision = $match[1];
135
					$scale = $match[2];
136
					$length = null;
137
				}
138
				break;
139
			case 'tinytext':
140
				$length = MySqlPlatform::LENGTH_LIMIT_TINYTEXT;
141
				break;
142
			case 'text':
143
				$length = MySqlPlatform::LENGTH_LIMIT_TEXT;
144
				break;
145
			case 'mediumtext':
146
				$length = MySqlPlatform::LENGTH_LIMIT_MEDIUMTEXT;
147
				break;
148
			case 'tinyblob':
149
				$length = MySqlPlatform::LENGTH_LIMIT_TINYBLOB;
150
				break;
151
			case 'blob':
152
				$length = MySqlPlatform::LENGTH_LIMIT_BLOB;
153
				break;
154
			case 'mediumblob':
155
				$length = MySqlPlatform::LENGTH_LIMIT_MEDIUMBLOB;
156
				break;
157
			case 'tinyint':
158
			case 'smallint':
159
			case 'mediumint':
160
			case 'int':
161
			case 'integer':
162
			case 'bigint':
163
			case 'year':
164
				$length = null;
165
				break;
166
		}
167
168
		$length = ((int) $length == 0) ? null : (int) $length;
169
170
		$default = isset($tableColumn['default']) ? $tableColumn['default'] : null;
171
		$columnDefault = $this->getMariaDb1027ColumnDefault($default);
172
173
		$options = [
174
			'length'		=> $length,
175
			'unsigned'	  => (bool) (\strpos($tableColumn['type'], 'unsigned') !== false),
176
			'fixed'		 => (bool) $fixed,
177
			// This line was changed to fix breaking change introduced in MariaDB 10.2.6
178
			'default'	   => $columnDefault,
179
			'notnull'	   => (bool) ($tableColumn['null'] != 'YES'),
180
			'scale'		 => null,
181
			'precision'	 => null,
182
			'autoincrement' => (bool) (\strpos($tableColumn['extra'], 'auto_increment') !== false),
183
			'comment'	   => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
184
				? $tableColumn['comment']
185
				: null,
186
		];
187
188
		if ($scale !== null && $precision !== null) {
189
			$options['scale'] = $scale;
190
			$options['precision'] = $precision;
191
		}
192
193
		$column = new Column($tableColumn['field'], Type::getType($type), $options);
194
195
		if (isset($tableColumn['collation'])) {
196
			$column->setPlatformOption('collation', $tableColumn['collation']);
197
		}
198
199
		return $column;
200
	}
201
202
	/**
203
	 * Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers.
204
	 *
205
	 * - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted
206
	 *   to distinguish them from expressions (see MDEV-10134).
207
	 * - Note: Quoted 'NULL' is not enforced by Maria, it is technically possible to have
208
	 *   null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053)
209
	 *
210
	 * @link https://mariadb.com/kb/en/library/information-schema-columns-table/
211
	 * @link https://jira.mariadb.org/browse/MDEV-13132
212
	 *
213
	 * @param null|string $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7
214
	 * @return string
215
	 */
216
	private function getMariaDb1027ColumnDefault($columnDefault) {
217
		if ($columnDefault === 'NULL' || $columnDefault === null) {
218
			return null;
219
		}
220
		if ($columnDefault[0] === "'") {
221
			return \preg_replace('/^\'(.*)\'$/', '$1', $columnDefault);
222
		}
223
224
		return $columnDefault;
225
	}
226
227
	/**
228
	 * Detect MariaDB server version, including hack for some mariadb distributions
229
	 * that starts with the prefix '5.5.5-'
230
	 *
231
	 * @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial'
232
	 * @return string
233
	 * @throws DBALException
234
	 */
235
	private function getMariaDbMysqlVersionNumber($versionString) {
236
		if (!\preg_match('/^(?:5\.5\.5-)?(mariadb-)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i', $versionString, $versionParts)) {
237
			throw DBALException::invalidPlatformVersionSpecified(
238
				$versionString,
239
				'^(?:5\.5\.5-)?(mariadb-)?<major_version>.<minor_version>.<patch_version>'
240
			);
241
		}
242
		return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch'];
243
	}
244
}
245