Passed
Push — master ( 29f10b...e68c42 )
by Aimeos
05:07
created

Standard::run()   B

Complexity

Conditions 8
Paths 52

Size

Total Lines 67
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 27
nc 52
nop 0
dl 0
loc 67
rs 8.4444
c 0
b 0
f 0

How to fix   Long Method   

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
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2019
6
 * @package Controller
7
 * @subpackage Jobs
8
 */
9
10
11
namespace Aimeos\Controller\Jobs\Attribute\Import\Xml;
12
13
14
/**
15
 * Job controller for XML attribute imports
16
 *
17
 * @package Controller
18
 * @subpackage Jobs
19
 */
20
class Standard
21
	extends \Aimeos\Controller\Jobs\Base
22
	implements \Aimeos\Controller\Jobs\Iface
23
{
24
	use \Aimeos\Controller\Common\Common\Import\Xml\Traits;
25
26
27
	/**
28
	 * Returns the localized name of the job.
29
	 *
30
	 * @return string Name of the job
31
	 */
32
	public function getName()
33
	{
34
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Attribute import XML' );
35
	}
36
37
38
	/**
39
	 * Returns the localized description of the job.
40
	 *
41
	 * @return string Description of the job
42
	 */
43
	public function getDescription()
44
	{
45
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Imports new and updates existing attributes from XML files' );
46
	}
47
48
49
	/**
50
	 * Executes the job.
51
	 *
52
	 * @throws \Aimeos\Controller\Jobs\Exception If an error occurs
53
	 */
54
	public function run()
55
	{
56
		$context = $this->getContext();
57
		$config = $context->getConfig();
58
		$logger = $context->getLogger();
59
60
		/** controller/jobs/attribute/import/xml/location
61
		 * File or directory where the content is stored which should be imported
62
		 *
63
		 * You need to configure the XML file or directory with the XML files that
64
		 * should be imported. It should be an absolute path to be sure but can be
65
		 * relative path if you absolutely know from where the job will be executed
66
		 * from.
67
		 *
68
		 * @param string Absolute file or directory path
69
		 * @since 2019.04
70
		 * @category Developer
71
		 * @category User
72
		 * @see controller/jobs/attribute/import/xml/container/type
73
		 * @see controller/jobs/attribute/import/xml/container/content
74
		 * @see controller/jobs/attribute/import/xml/container/options
75
		 */
76
		$location = $config->get( 'controller/jobs/attribute/import/xml/location' );
77
78
		try
79
		{
80
			$msg = sprintf( 'Started attribute import from "%1$s" (%2$s)', $location, __CLASS__ );
81
			$logger->log( $msg, \Aimeos\MW\Logger\Base::INFO );
82
83
			if( !file_exists( $location ) )
84
			{
85
				$msg = sprintf( 'File or directory "%1$s" doesn\'t exist', $location );
86
				throw new \Aimeos\Controller\Jobs\Exception( $msg );
87
			}
88
89
			$files = [];
90
91
			if( is_dir( $location ) )
92
			{
93
				foreach( new \DirectoryIterator( $location ) as $entry )
94
				{
95
					if( strncmp( $entry->getFilename(), 'attribute', 8 ) === 0 && $entry->getExtension() === 'xml' ) {
96
						$files[] = $entry->getPathname();
97
					}
98
				}
99
			}
100
			else
101
			{
102
				$files[] = $location;
103
			}
104
105
			sort( $files );
106
			$total = 0;
107
108
			foreach( $files as $filepath ) {
109
				$total += $this->import( $filepath );
110
			}
111
112
			$msg = 'Finished attribute import from "%1$s": %2$s total (%3$s MB)';
113
			$mem = number_format( memory_get_peak_usage() / 1024 / 1024, 2 );
114
115
			$logger->log( sprintf( $msg, $location, $total, $mem ), \Aimeos\MW\Logger\Base::INFO );
116
		}
117
		catch( \Exception $e )
118
		{
119
			$logger->log( 'Attribute import error: ' . $e->getMessage() . "\n" . $e->getTraceAsString() );
120
			throw $e;
121
		}
122
	}
123
124
125
	/**
126
	 * Returns the attribute items for the given nodes
127
	 *
128
	 * @param \DomElement[] $nodes List of XML attribute item nodes
129
	 * @param string[] $ref Domain names of referenced items that should be fetched too
130
	 * @return \Aimeos\MShop\Attribute\Item\Iface[] Associative list of attribute items with IDs as keys
131
	 */
132
	protected function getItems( array $nodes, array $ref )
133
	{
134
		$domains = $types = $codes = [];
135
136
		foreach( $nodes as $node )
137
		{
138
			if( ( $attr = $node->attributes->getNamedItem( 'ref' ) ) !== null )
139
			{
140
				list( $domain, $type, $code ) = explode( '/', $attr->nodeValue );
141
				$domains[$domain] = $types[$type] = $codes[$code] = null;
142
			}
143
		}
144
145
		$manager = \Aimeos\MShop::create( $this->getContext(), 'attribute' );
146
		$search = $manager->createSearch()->setSlice( 0, 0x7fffffff );
147
		$search->setConditions( $search->combine( '&&', [
148
			$search->compare( '==', 'attribute.domain', array_keys( $domains ) ),
149
			$search->compare( '==', 'attribute.type', array_keys( $types ) ),
150
			$search->compare( '==', 'attribute.code', array_keys( $codes ) ),
151
		] ) );
152
153
		return $manager->searchItems( $search, $ref );
154
	}
155
156
157
	/**
158
	 * Imports the XML file given by its path
159
	 *
160
	 * @param $filename Absolute or relative path to the XML file
0 ignored issues
show
Bug introduced by
The type Aimeos\Controller\Jobs\A...ute\Import\Xml\Absolute was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
161
	 * @return integer Total number of imported attributes
162
	 */
163
	protected function import( $filename )
164
	{
165
		$context = $this->getContext();
166
		$config = $context->getConfig();
167
		$domains = ['attribute/property', 'media', 'price', 'text'];
168
169
		/** controller/jobs/attribute/import/xml/domains
170
		 * List of item domain names that should be retrieved along with the attribute items
171
		 *
172
		 * This configuration setting overwrites the shared option
173
		 * "controller/common/attribute/import/xml/domains" if you need a
174
		 * specific setting for the job controller. Otherwise, you should
175
		 * use the shared option for consistency.
176
		 *
177
		 * @param array Associative list of MShop item domain names
178
		 * @since 2019.04
179
		 * @category Developer
180
		 * @see controller/jobs/attribute/import/xml/backup
181
		 * @see controller/jobs/attribute/import/xml/max-query
182
		 */
183
		$domains = $config->get( 'controller/jobs/attribute/import/xml/domains', $domains );
184
185
		/** controller/jobs/attribute/import/xml/backup
186
		 * Name of the backup for sucessfully imported files
187
		 *
188
		 * After a XML file was imported successfully, you can move it to another
189
		 * location, so it won't be imported again and isn't overwritten by the
190
		 * next file that is stored at the same location in the file system.
191
		 *
192
		 * You should use an absolute path to be sure but can be relative path
193
		 * if you absolutely know from where the job will be executed from. The
194
		 * name of the new backup location can contain placeholders understood
195
		 * by the PHP strftime() function to create dynamic paths, e.g. "backup/%Y-%m-%d"
196
		 * which would create "backup/2000-01-01". For more information about the
197
		 * strftime() placeholders, please have a look into the PHP documentation of
198
		 * the {@link http://php.net/manual/en/function.strftime.php strftime() function}.
199
		 *
200
		 * '''Note:''' If no backup name is configured, the file or directory
201
		 * won't be moved away. Please make also sure that the parent directory
202
		 * and the new directory are writable so the file or directory could be
203
		 * moved.
204
		 *
205
		 * @param integer Name of the backup file, optionally with date/time placeholders
206
		 * @since 2019.04
207
		 * @category Developer
208
		 * @see controller/jobs/attribute/import/xml/domains
209
		 * @see controller/jobs/attribute/import/xml/max-query
210
		 */
211
		$backup = $config->get( 'controller/jobs/attribute/import/xml/backup' );
212
213
		/** controller/jobs/attribute/import/xml/max-query
214
		 * Maximum number of XML nodes processed at once
215
		 *
216
		 * Processing and fetching several attribute items at once speeds up importing
217
		 * the XML files. The more items can be processed at once, the faster the
218
		 * import. More items also increases the memory usage of the importer and
219
		 * thus, this parameter should be low enough to avoid reaching the memory
220
		 * limit of the PHP process.
221
		 *
222
		 * @param integer Number of XML nodes
223
		 * @since 2019.04
224
		 * @category Developer
225
		 * @category User
226
		 * @see controller/jobs/attribute/import/xml/domains
227
		 * @see controller/jobs/attribute/import/xml/backup
228
		 */
229
		$maxquery = $config->get( 'controller/jobs/attribute/import/xml/max-query', 1000 );
230
231
232
		$nodes = [];
233
		$total = $slice = 0;
234
		$xml = new \XMLReader();
235
236
		if( $xml->open( $filename, LIBXML_COMPACT | LIBXML_PARSEHUGE ) === false ) {
237
			throw new \Aimeos\Controller\Jobs\Exception( sprintf( 'No XML file "%1$s" found', $filename ) );
238
		}
239
240
		while( $xml->read() === true )
241
		{
242
			if( $xml->depth === 1 && $xml->nodeType === \XMLReader::ELEMENT && $xml->name === 'attributeitem' )
243
			{
244
				if( ( $dom = $xml->expand() ) === false )
245
				{
246
					$msg = sprintf( 'Expanding "%1$s" node failed', 'attributeitem' );
247
					throw new \Aimeos\Controller\Jobs\Exception( $msg );
248
				}
249
250
				$nodes[] = $dom;
251
252
				if( $slice++ >= $maxquery )
253
				{
254
					$this->importNodes( $nodes, $domains );
255
					unset( $nodes );
256
					$nodes = [];
257
					$slice = 0;
258
				}
259
260
				$total++;
261
			}
262
		}
263
264
		$this->importNodes( $nodes, $domains );
265
		unset( $nodes );
266
267
		if( !empty( $backup ) && @rename( $filename, strftime( $backup ) ) === false )
268
		{
269
			$msg = sprintf( 'Unable to move imported file "%1$s" to "%2$s"', $filename, $backup );
270
			throw new \Aimeos\Controller\Jobs\Exception( $msg );
271
		}
272
273
		return $total;
274
	}
275
276
277
	/**
278
	 * Imports the given DOM nodes
279
	 *
280
	 * @param \DomElement[] $nodes List of nodes to import
281
	 * @param string[] $ref List of domain names whose referenced items will be updated in the attribute items
282
	 */
283
	protected function importNodes( array $nodes, array $ref )
284
	{
285
		$map = [];
286
		$manager = \Aimeos\MShop::create( $this->getContext(), 'attribute' );
287
288
		foreach( $this->getItems( $nodes, $ref ) as $item ) {
289
			$map[$item->getDomain() . '/' . $item->getType() . '/' . $item->getCode()] = $item;
290
		}
291
292
		foreach( $nodes as $node )
293
		{
294
			if( ( $attr = $node->attributes->getNamedItem( 'ref' ) ) !== null && isset( $map[$attr->nodeValue] ) ) {
295
				$item = $this->process( $map[$attr->nodeValue], $node );
296
			} else {
297
				$item = $this->process( $manager->createItem(), $node );
298
			}
299
300
			$manager->saveItem( $item );
301
		}
302
	}
303
304
305
	/**
306
	 * Updates the attribute item and its referenced items using the given DOM node
307
	 *
308
	 * @param \Aimeos\MShop\Attribute\Item\Iface $item Attribute item object to update
309
	 * @param \DomElement $node DOM node used for updateding the attribute item
310
	 * @return \Aimeos\MShop\Attribute\Item\Iface $item Updated attribute item object
311
	 */
312
	protected function process( \Aimeos\MShop\Attribute\Item\Iface $item, \DomElement $node )
313
	{
314
		$list = [];
315
316
		foreach( $node->attributes as $attr ) {
317
			$list[$attr->nodeName] = $attr->nodeValue;
318
		}
319
320
		foreach( $node->childNodes as $tag )
321
		{
322
			if( in_array( $tag->nodeName, ['lists', 'property'] ) ) {
323
				$item = $this->getProcessor( $tag->nodeName )->process( $item, $tag );
324
			} else {
325
				$list[$tag->nodeName] = $tag->nodeValue;
326
			}
327
		}
328
329
		return $item->fromArray( $list, true );
330
	}
331
}
332