Completed
Branch master (c3c77b)
by Aimeos
03:13
created

Standard::run()   B

Complexity

Conditions 9
Paths 25

Size

Total Lines 120
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 42
nc 25
nop 0
dl 0
loc 120
rs 7.6924
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), 2017-2018
6
 * @package Controller
7
 * @subpackage Jobs
8
 */
9
10
11
namespace Aimeos\Controller\Jobs\Coupon\Import\Csv\Code;
12
13
14
/**
15
 * Job controller for CSV coupon imports.
16
 *
17
 * @package Controller
18
 * @subpackage Jobs
19
 */
20
class Standard
21
	extends \Aimeos\Controller\Common\Coupon\Import\Csv\Base
22
	implements \Aimeos\Controller\Jobs\Iface
23
{
24
	/**
25
	 * Returns the localized name of the job.
26
	 *
27
	 * @return string Name of the job
28
	 */
29
	public function getName()
30
	{
31
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Coupon code import CSV' );
32
	}
33
34
35
	/**
36
	 * Returns the localized description of the job.
37
	 *
38
	 * @return string Description of the job
39
	 */
40
	public function getDescription()
41
	{
42
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Imports new and updates existing coupon code from CSV files' );
43
	}
44
45
46
	/**
47
	 * Executes the job.
48
	 *
49
	 * @throws \Aimeos\Controller\Jobs\Exception If an error occurs
50
	 */
51
	public function run()
52
	{
53
		$total = $errors = 0;
54
		$context = $this->getContext();
55
		$config = $context->getConfig();
56
		$logger = $context->getLogger();
57
		$mappings = $this->getDefaultMapping();
58
59
60
		/** controller/jobs/coupon/import/csv/code/mapping
61
		 * List of mappings between the position in the CSV file and item keys
62
		 *
63
		 * This configuration setting overwrites the shared option
64
		 * "controller/common/coupon/import/csv/mapping" if you need a
65
		 * specific setting for the job controller. Otherwise, you should
66
		 * use the shared option for consistency.
67
		 *
68
		 * @param array Associative list of processor names and lists of key/position pairs
69
		 * @since 2017.10
70
		 * @category Developer
71
		 * @see controller/jobs/coupon/import/csv/code/skip-lines
72
		 * @see controller/jobs/coupon/import/csv/code/max-size
73
		 */
74
		$mappings = $config->get( 'controller/jobs/coupon/import/csv/code/mapping', $mappings );
75
76
77
		/** controller/jobs/coupon/import/csv/code/max-size
78
		 * Maximum number of CSV rows to import at once
79
		 *
80
		 * It's more efficient to read and import more than one row at a time
81
		 * to speed up the import. Usually, the bigger the chunk that is imported
82
		 * at once, the less time the importer will need. The downside is that
83
		 * the amount of memory required by the import process will increase as
84
		 * well. Therefore, it's a trade-off between memory consumption and
85
		 * import speed.
86
		 *
87
		 * @param integer Number of rows
88
		 * @since 2017.10
89
		 * @category Developer
90
		 * @see controller/jobs/coupon/import/csv/code/skip-lines
91
		 * @see controller/jobs/coupon/import/csv/code/mapping
92
		 */
93
		$maxcnt = (int) $config->get( 'controller/jobs/coupon/import/csv/code/max-size', 1000 );
94
95
96
		/** controller/jobs/coupon/import/csv/code/skip-lines
97
		 * Number of rows skipped in front of each CSV files
98
		 *
99
		 * Some CSV files contain header information describing the content of
100
		 * the column values. These data is for informational purpose only and
101
		 * can't be imported into the database. Using this option, you can
102
		 * define the number of lines that should be left out before the import
103
		 * begins.
104
		 *
105
		 * @param integer Number of rows
106
		 * @since 2015.08
107
		 * @category Developer
108
		 * @see controller/jobs/coupon/import/csv/code/mapping
109
		 * @see controller/jobs/coupon/import/csv/code/max-size
110
		 */
111
		$skiplines = (int) $config->get( 'controller/jobs/coupon/import/csv/code/skip-lines', 0 );
112
113
114
		try
115
		{
116
			$processor = $this->getProcessors( $mappings );
117
			$codePos = $this->getCodePosition( $mappings['code'] );
118
			$fs = $context->getFileSystemManager()->get( 'fs-import' );
119
			$dir = 'couponcode/' . $context->getLocale()->getSite()->getCode();
120
121
			if( $fs->isDir( $dir ) === false ) {
122
				return;
123
			}
124
125
			foreach( $fs->scan( $dir ) as $filename )
126
			{
127
				if( $filename == '.' || $filename == '..' ) {
128
					continue;
129
				}
130
131
				list( $couponId, ) = explode( '.', $filename );
132
				$container = $this->getContainer( $fs->readf( $dir . '/' . $filename ) );
133
134
				$msg = sprintf( 'Started coupon import from "%1$s" (%2$s)', $filename, __CLASS__ );
135
				$logger->log( $msg, \Aimeos\MW\Logger\Base::NOTICE );
136
137
				foreach( $container as $content )
138
				{
139
					for( $i = 0; $i < $skiplines; $i++ ) {
140
						$content->next();
141
					}
142
143
					while( ( $data = $this->getData( $content, $maxcnt, $codePos ) ) !== [] )
144
					{
145
						$items = $this->getCouponCodeItems( array_keys( $data ) );
146
						$errcnt = $this->import( $items, $data, $couponId, $processor );
147
						$chunkcnt = count( $data );
148
149
						$msg = 'Imported coupon lines from "%1$s": %2$d/%3$d (%4$s)';
150
						$logger->log( sprintf( $msg, $filename, $chunkcnt - $errcnt, $chunkcnt, __CLASS__ ), \Aimeos\MW\Logger\Base::NOTICE );
151
152
						$errors += $errcnt;
153
						$total += $chunkcnt;
154
						unset( $items, $data );
155
					}
156
				}
157
158
				$container->close();
159
				$fs->rm( $dir . '/' . $filename );
160
161
				$msg = 'Finished coupon import: %1$d successful, %2$s errors, %3$s total (%4$s)';
162
				$logger->log( sprintf( $msg, $total - $errors, $errors, $total, __CLASS__ ), \Aimeos\MW\Logger\Base::NOTICE );
163
			}
164
		}
165
		catch( \Exception $e )
166
		{
167
			$logger->log( 'Coupon import error: ' . $e->getMessage() );
168
			$logger->log( $e->getTraceAsString() );
169
170
			throw $e;
171
		}
172
	}
173
174
175
	/**
176
	 * Returns the position of the "coupon.code" column from the coupon item mapping
177
	 *
178
	 * @param array $mapping Mapping of the "item" columns with position as key and code as value
179
	 * @return integer Position of the "coupon.code" column
180
	 * @throws \Aimeos\Controller\Jobs\Exception If no mapping for "coupon.code.code" is found
181
	 */
182
	protected function getCodePosition( array $mapping )
183
	{
184
		foreach( $mapping as $pos => $key )
185
		{
186
			if( $key === 'coupon.code.code' ) {
187
				return $pos;
188
			}
189
		}
190
191
		throw new \Aimeos\Controller\Jobs\Exception( sprintf( 'No "coupon.code.code" column in CSV mapping found' ) );
192
	}
193
194
195
	/**
196
	 * Opens and returns the container which includes the coupon data
197
	 *
198
	 * @param string $filepath Path to the container file
199
	 * @return \Aimeos\MW\Container\Iface Container object
200
	 */
201
	protected function getContainer( $filepath )
202
	{
203
		$config = $this->getContext()->getConfig();
204
205
		/** controller/jobs/coupon/import/csv/code/container/type
206
		 * Name of the container type to read the data from
207
		 *
208
		 * The container type tells the importer how it should retrieve the data.
209
		 * There are currently three container types that support the necessary
210
		 * CSV content:
211
		 * * File (plain)
212
		 * * Zip
213
		 * * PHPExcel
214
		 *
215
		 * '''Note:''' For the PHPExcel container, you need to install the
216
		 * "ai-container" extension.
217
		 *
218
		 * @param string Container type name
219
		 * @since 2017.10
220
		 * @category Developer
221
		 * @category User
222
		 * @see controller/jobs/coupon/import/csv/code/container/content
223
		 * @see controller/jobs/coupon/import/csv/code/container/options
224
		 */
225
		$container = $config->get( 'controller/jobs/coupon/import/csv/code/container/type', 'File' );
226
227
		/** controller/jobs/coupon/import/csv/code/container/content
228
		 * Name of the content type inside the container to read the data from
229
		 *
230
		 * The content type must always be a CSV-like format and there are
231
		 * currently two format types that are supported:
232
		 * * CSV
233
		 * * PHPExcel
234
		 *
235
		 * '''Note:''' for the PHPExcel content type, you need to install the
236
		 * "ai-container" extension.
237
		 *
238
		 * @param array Content type name
239
		 * @since 2017.10
240
		 * @category Developer
241
		 * @category User
242
		 * @see controller/jobs/coupon/import/csv/code/container/type
243
		 * @see controller/jobs/coupon/import/csv/code/container/options
244
		 */
245
		$content = $config->get( 'controller/jobs/coupon/import/csv/code/container/content', 'CSV' );
246
247
		/** controller/jobs/coupon/import/csv/code/container/options
248
		 * List of file container options for the coupon import files
249
		 *
250
		 * Some container/content type allow you to hand over additional settings
251
		 * for configuration. Please have a look at the article about
252
		 * {@link http://aimeos.org/docs/Developers/Utility/Create_and_read_files container/content files}
253
		 * for more information.
254
		 *
255
		 * @param array Associative list of option name/value pairs
256
		 * @since 2017.10
257
		 * @category Developer
258
		 * @category User
259
		 * @see controller/jobs/coupon/import/csv/code/container/content
260
		 * @see controller/jobs/coupon/import/csv/code/container/type
261
		 */
262
		$options = $config->get( 'controller/jobs/coupon/import/csv/code/container/options', [] );
263
264
		return \Aimeos\MW\Container\Factory::getContainer( $filepath, $container, $content, $options );
265
	}
266
267
268
	/**
269
	 * Imports the CSV data and creates new coupons or updates existing ones
270
	 *
271
	 * @param \Aimeos\MShop\Coupon\Item\Code\Iface[] $items List of coupons code items
272
	 * @param array $data Associative list of import data as index/value pairs
273
	 * @param string $couponId ID of the coupon item the coupon code should be added to
274
	 * @param \Aimeos\Controller\Common\Coupon\Import\Csv\Processor\Iface $processor Processor object
275
	 * @return integer Number of coupons that couldn't be imported
276
	 * @throws \Aimeos\Controller\Jobs\Exception
277
	 */
278
	protected function import( array $items, array $data, $couponId,
279
		\Aimeos\Controller\Common\Coupon\Import\Csv\Processor\Iface $processor )
280
	{
281
		$errors = 0;
282
		$context = $this->getContext();
283
		$manager = \Aimeos\MShop::create( $context, 'coupon/code' );
284
285
		foreach( $data as $code => $list )
286
		{
287
			$manager->begin();
288
289
			try
290
			{
291
				if( isset( $items[$code] ) ) {
292
					$item = $items[$code];
293
				} else {
294
					$item = $manager->createItem();
295
				}
296
297
				$item->setParentId( $couponId );
0 ignored issues
show
Bug introduced by
The method setParentId() does not exist on Aimeos\MShop\Attribute\Item\Iface. ( Ignorable by Annotation )

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

297
				$item->/** @scrutinizer ignore-call */ 
298
           setParentId( $couponId );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
298
				$list = $processor->process( $item, $list );
0 ignored issues
show
Unused Code introduced by
The assignment to $list is dead and can be removed.
Loading history...
Bug introduced by
It seems like $item can also be of type Aimeos\MShop\Attribute\Item\Iface; however, parameter $item of Aimeos\Controller\Common...cessor\Iface::process() does only seem to accept Aimeos\MShop\Coupon\Item\Code\Iface, maybe add an additional type check? ( Ignorable by Annotation )

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

298
				$list = $processor->process( /** @scrutinizer ignore-type */ $item, $list );
Loading history...
299
300
				$manager->commit();
301
			}
302
			catch( \Exception $e )
303
			{
304
				$manager->rollback();
305
306
				$msg = sprintf( 'Unable to import coupon with code "%1$s": %2$s', $code, $e->getMessage() );
307
				$context->getLogger()->log( $msg );
308
309
				$errors++;
310
			}
311
		}
312
313
		return $errors;
314
	}
315
}
316