SiteImporter   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 234
Duplicated Lines 7.69 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 18
loc 234
rs 10
c 0
b 0
f 0
wmc 28
lcom 1
cbo 4

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A getExceptionCallback() 0 3 1
A setExceptionCallback() 0 3 1
A importFromFile() 0 9 2
A importFromXML() 0 23 3
A importFromDOM() 0 4 1
B makeSiteList() 0 30 6
B makeSite() 0 36 4
A getAttributeValue() 9 15 3
A getChildText() 9 16 3
A hasChild() 0 3 1
A handleException() 0 7 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
/**
4
 * Utility for importing site entries from XML.
5
 * For the expected format of the input, see docs/sitelist.txt and docs/sitelist-1.0.xsd.
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
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 General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License along
18
 * with this program; if not, write to the Free Software Foundation, Inc.,
19
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
 * http://www.gnu.org/copyleft/gpl.html
21
 *
22
 * @since 1.25
23
 *
24
 * @file
25
 * @ingroup Site
26
 *
27
 * @license GNU GPL v2+
28
 * @author Daniel Kinzler
29
 */
30
class SiteImporter {
31
32
	/**
33
	 * @var SiteStore
34
	 */
35
	private $store;
36
37
	/**
38
	 * @var callable|null
39
	 */
40
	private $exceptionCallback;
41
42
	/**
43
	 * @param SiteStore $store
44
	 */
45
	public function __construct( SiteStore $store ) {
46
		$this->store = $store;
47
	}
48
49
	/**
50
	 * @return callable
51
	 */
52
	public function getExceptionCallback() {
53
		return $this->exceptionCallback;
54
	}
55
56
	/**
57
	 * @param callable $exceptionCallback
58
	 */
59
	public function setExceptionCallback( $exceptionCallback ) {
60
		$this->exceptionCallback = $exceptionCallback;
61
	}
62
63
	/**
64
	 * @param string $file
65
	 */
66
	public function importFromFile( $file ) {
67
		$xml = file_get_contents( $file );
68
69
		if ( $xml === false ) {
70
			throw new RuntimeException( 'Failed to read ' . $file . '!' );
71
		}
72
73
		$this->importFromXML( $xml );
74
	}
75
76
	/**
77
	 * @param string $xml
78
	 *
79
	 * @throws InvalidArgumentException
80
	 */
81
	public function importFromXML( $xml ) {
82
		$document = new DOMDocument();
83
84
		$oldLibXmlErrors = libxml_use_internal_errors( true );
85
		$ok = $document->loadXML( $xml, LIBXML_NONET );
86
87
		if ( !$ok ) {
88
			$errors = libxml_get_errors();
89
			libxml_use_internal_errors( $oldLibXmlErrors );
90
91
			foreach ( $errors as $error ) {
92
				/** @var LibXMLError $error */
93
				throw new InvalidArgumentException(
94
					'Malformed XML: ' . $error->message . ' in line ' . $error->line
95
				);
96
			}
97
98
			throw new InvalidArgumentException( 'Malformed XML!' );
99
		}
100
101
		libxml_use_internal_errors( $oldLibXmlErrors );
102
		$this->importFromDOM( $document->documentElement );
103
	}
104
105
	/**
106
	 * @param DOMElement $root
107
	 */
108
	private function importFromDOM( DOMElement $root ) {
109
		$sites = $this->makeSiteList( $root );
110
		$this->store->saveSites( $sites );
111
	}
112
113
	/**
114
	 * @param DOMElement $root
115
	 *
116
	 * @return Site[]
117
	 */
118
	private function makeSiteList( DOMElement $root ) {
119
		$sites = [];
120
121
		// Old sites, to get the row IDs that correspond to the global site IDs.
122
		// TODO: Get rid of internal row IDs, they just get in the way. Get rid of ORMRow, too.
123
		$oldSites = $this->store->getSites();
124
125
		$current = $root->firstChild;
126
		while ( $current ) {
127
			if ( $current instanceof DOMElement && $current->tagName === 'site' ) {
128
				try {
129
					$site = $this->makeSite( $current );
130
					$key = $site->getGlobalId();
131
132
					if ( $oldSites->hasSite( $key ) ) {
133
						$oldSite = $oldSites->getSite( $key );
134
						$site->setInternalId( $oldSite->getInternalId() );
135
					}
136
137
					$sites[$key] = $site;
138
				} catch ( Exception $ex ) {
139
					$this->handleException( $ex );
140
				}
141
			}
142
143
			$current = $current->nextSibling;
144
		}
145
146
		return $sites;
147
	}
148
149
	/**
150
	 * @param DOMElement $siteElement
151
	 *
152
	 * @return Site
153
	 * @throws InvalidArgumentException
154
	 */
155
	public function makeSite( DOMElement $siteElement ) {
156
		if ( $siteElement->tagName !== 'site' ) {
157
			throw new InvalidArgumentException( 'Expected <site> tag, found ' . $siteElement->tagName );
158
		}
159
160
		$type = $this->getAttributeValue( $siteElement, 'type', Site::TYPE_UNKNOWN );
161
		$site = Site::newForType( $type );
162
163
		$site->setForward( $this->hasChild( $siteElement, 'forward' ) );
164
		$site->setGlobalId( $this->getChildText( $siteElement, 'globalid' ) );
165
		$site->setGroup( $this->getChildText( $siteElement, 'group', Site::GROUP_NONE ) );
166
		$site->setSource( $this->getChildText( $siteElement, 'source', Site::SOURCE_LOCAL ) );
167
168
		$pathTags = $siteElement->getElementsByTagName( 'path' );
169
		for ( $i = 0; $i < $pathTags->length; $i++ ) {
170
			$pathElement = $pathTags->item( $i );
171
			$pathType = $this->getAttributeValue( $pathElement, 'type' );
0 ignored issues
show
Compatibility introduced by
$pathElement of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
172
			$path = $pathElement->textContent;
173
174
			$site->setPath( $pathType, $path );
175
		}
176
177
		$idTags = $siteElement->getElementsByTagName( 'localid' );
178
		for ( $i = 0; $i < $idTags->length; $i++ ) {
179
			$idElement = $idTags->item( $i );
180
			$idType = $this->getAttributeValue( $idElement, 'type' );
0 ignored issues
show
Compatibility introduced by
$idElement of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
181
			$id = $idElement->textContent;
182
183
			$site->addLocalId( $idType, $id );
184
		}
185
186
		// @todo: import <data>
187
		// @todo: import <config>
188
189
		return $site;
190
	}
191
192
	/**
193
	 * @param DOMElement $element
194
	 * @param $name
195
	 * @param string|null|bool $default
196
	 *
197
	 * @return null|string
198
	 * @throws MWException If the attribute is not found and no default is provided
199
	 */
200
	private function getAttributeValue( DOMElement $element, $name, $default = false ) {
201
		$node = $element->getAttributeNode( $name );
202
203 View Code Duplication
		if ( !$node ) {
204
			if ( $default !== false ) {
205
				return $default;
206
			} else {
207
				throw new MWException(
208
					'Required ' . $name . ' attribute not found in <' . $element->tagName . '> tag'
209
				);
210
			}
211
		}
212
213
		return $node->textContent;
214
	}
215
216
	/**
217
	 * @param DOMElement $element
218
	 * @param string $name
219
	 * @param string|null|bool $default
220
	 *
221
	 * @return null|string
222
	 * @throws MWException If the child element is not found and no default is provided
223
	 */
224
	private function getChildText( DOMElement $element, $name, $default = false ) {
225
		$elements = $element->getElementsByTagName( $name );
226
227 View Code Duplication
		if ( $elements->length < 1 ) {
228
			if ( $default !== false ) {
229
				return $default;
230
			} else {
231
				throw new MWException(
232
					'Required <' . $name . '> tag not found inside <' . $element->tagName . '> tag'
233
				);
234
			}
235
		}
236
237
		$node = $elements->item( 0 );
238
		return $node->textContent;
239
	}
240
241
	/**
242
	 * @param DOMElement $element
243
	 * @param string $name
244
	 *
245
	 * @return bool
246
	 * @throws MWException
247
	 */
248
	private function hasChild( DOMElement $element, $name ) {
249
		return $this->getChildText( $element, $name, null ) !== null;
250
	}
251
252
	/**
253
	 * @param Exception $ex
254
	 */
255
	private function handleException( Exception $ex ) {
256
		if ( $this->exceptionCallback ) {
257
			call_user_func( $this->exceptionCallback, $ex );
258
		} else {
259
			wfLogWarning( $ex->getMessage() );
260
		}
261
	}
262
263
}
264