Passed
Push — master ( 417598...532336 )
by Aimeos
03:26
created

Standard::getSuggestions()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 30
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 16
c 1
b 0
f 0
nc 3
nop 8
dl 0
loc 30
rs 9.7333

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2014
6
 * @copyright Aimeos (aimeos.org), 2015-2021
7
 * @package Controller
8
 * @subpackage Jobs
9
 */
10
11
12
namespace Aimeos\Controller\Jobs\Product\Bought;
13
14
15
/**
16
 * Job controller for bought together products.
17
 *
18
 * @package Controller
19
 * @subpackage Jobs
20
 */
21
class Standard
22
	extends \Aimeos\Controller\Jobs\Base
23
	implements \Aimeos\Controller\Jobs\Iface
24
{
25
	/**
26
	 * Returns the localized name of the job.
27
	 *
28
	 * @return string Name of the job
29
	 */
30
	public function getName() : string
31
	{
32
		return $this->context()->translate( 'controller/jobs', 'Products bought together' );
33
	}
34
35
36
	/**
37
	 * Returns the localized description of the job.
38
	 *
39
	 * @return string Description of the job
40
	 */
41
	public function getDescription() : string
42
	{
43
		return $this->context()->translate( 'controller/jobs', 'Creates bought together product suggestions' );
44
	}
45
46
47
	/**
48
	 * Executes the job.
49
	 *
50
	 * @throws \Aimeos\Controller\Jobs\Exception If an error occurs
51
	 */
52
	public function run()
53
	{
54
		$context = $this->context();
55
		$config = $context->config();
56
57
58
		/** controller/jobs/product/bought/max-items
59
		 * Maximum number of suggested items per product
60
		 *
61
		 * Each product can contain zero or more suggested products based on
62
		 * the used algorithm. The maximum number of items limits the quantity
63
		 * of products that are associated as suggestions to one product.
64
		 * Usually, you don't need more products than shown in the product
65
		 * detail view as suggested products.
66
		 *
67
		 * @param integer Number of suggested products
68
		 * @since 2014.09
69
		 * @category Developer
70
		 * @category User
71
		 * @see controller/jobs/product/bought/min-support
72
		 * @see controller/jobs/product/bought/min-confidence
73
		 * @see controller/jobs/product/bought/limit-days
74
		 */
75
		$maxItems = $config->get( 'controller/jobs/product/bought/max-items', 5 );
76
77
		/** controller/jobs/product/bought/min-support
78
		 * Minimum support value to sort out all irrelevant combinations
79
		 *
80
		 * A minimum support value of 0.02 requires the combination of two
81
		 * products to be in at least 2% of all orders to be considered relevant
82
		 * enough as product suggestion.
83
		 *
84
		 * You can tune this value for your needs, e.g. if you sell several
85
		 * thousands different products and you have only a few suggestions for
86
		 * all products, a lower value might work better for you. The other way
87
		 * round, if you sell less than thousand different products, you may
88
		 * have a lot of product suggestions of low quality. In this case it's
89
		 * better to increase this value, e.g. to 0.05 or higher.
90
		 *
91
		 * Caution: Decreasing the support to lower values than 0.01 exponentially
92
		 * increases the time for generating the suggestions. If your database
93
		 * contains a lot of orders, the time to complete the job may rise from
94
		 * hours to days!
95
		 *
96
		 * @param float Minimum support value from 0 to 1
97
		 * @since 2014.09
98
		 * @category Developer
99
		 * @category User
100
		 * @see controller/jobs/product/bought/max-items
101
		 * @see controller/jobs/product/bought/min-confidence
102
		 * @see controller/jobs/product/bought/limit-days
103
		 */
104
		$minSupport = $config->get( 'controller/jobs/product/bought/min-support', 0.02 );
105
106
		/** controller/jobs/product/bought/min-confidence
107
		 * Minimum confidence value for high quality suggestions
108
		 *
109
		 * The confidence value is used to remove low quality suggestions. Using
110
		 * a confidence value of 0.95 would only suggest product combinations
111
		 * that are almost always bought together. Contrary, a value of 0.1 would
112
		 * yield a lot of combinations that are bought together only in very rare
113
		 * cases.
114
		 *
115
		 * To get good product suggestions, the value should be at least above
116
		 * 0.5 and the higher the value, the better the suggestions. You can
117
		 * either increase the default value to get better suggestions or lower
118
		 * the value to get more suggestions per product if you have only a few
119
		 * ones in total.
120
		 *
121
		 * @param float Minimum confidence value from 0 to 1
122
		 * @since 2014.09
123
		 * @category Developer
124
		 * @category User
125
		 * @see controller/jobs/product/bought/max-items
126
		 * @see controller/jobs/product/bought/min-support
127
		 * @see controller/jobs/product/bought/limit-days
128
		 */
129
		$minConfidence = $config->get( 'controller/jobs/product/bought/min-confidence', 0.66 );
130
131
		/** controller/jobs/product/bought/limit-days
132
		 * Only use orders placed in the past within the configured number of days for calculating bought together products
133
		 *
134
		 * This option limits the orders that are evaluated for calculating the
135
		 * bought together products. Only ordered products that were bought by
136
		 * customers within the configured number of days are used.
137
		 *
138
		 * Limiting the orders taken into account to the last ones increases the
139
		 * quality of suggestions if customer interests shifts to new products.
140
		 * If you only have a few orders per month, you can also increase this
141
		 * value to several years to get enough suggestions. Please keep in mind
142
		 * that the more orders are evaluated, the longer the it takes to
143
		 * calculate the product combinations.
144
		 *
145
		 * @param integer Number of days
146
		 * @since 2014.09
147
		 * @category User
148
		 * @category Developer
149
		 * @see controller/jobs/product/bought/max-items
150
		 * @see controller/jobs/product/bought/min-support
151
		 * @see controller/jobs/product/bought/min-confidence
152
		 */
153
		$days = $config->get( 'controller/jobs/product/bought/limit-days', 180 );
154
		$date = date( 'Y-m-d H:i:s', time() - $days * 86400 );
155
156
		$domains = [
157
			'attribute', 'catalog', 'media', 'media/property', 'price',
158
			'product', 'product/property', 'supplier', 'text'
159
		];
160
161
162
		$manager = \Aimeos\MShop::create( $context, 'product' );
163
		$baseManager = \Aimeos\MShop::create( $context, 'order/base' );
164
		$baseProductManager = \Aimeos\MShop::create( $context, 'order/base/product' );
165
166
		$search = $baseProductManager->filter()->add( 'order.base.product.ctime', '>', $date );
167
		$filter = $baseManager->filter()->add( 'order.base.ctime', '>', $date )->slice( 0, 0 );
168
169
		$start = $total = 0;
170
		$baseManager->search( $filter, [], $total );
171
172
		do
173
		{
174
			$counts = $baseProductManager->aggregate( $search, 'order.base.product.productid' );
175
			$prodIds = $counts->keys()->all();
176
			$products = $manager->search( $manager->filter()->add( 'product.id', '==', $prodIds ), $domains );
177
178
			foreach( $counts as $id => $count )
179
			{
180
				if( ( $item = $products->get( $id ) ) === null ) {
181
					continue;
182
				}
183
184
				$listItems = $item->getListItems( 'product', 'bought-together' );
185
186
				if( $count / $total > $minSupport )
187
				{
188
					$productIds = $this->getSuggestions( $id, $prodIds, $count, $total, $maxItems,
189
						$minSupport, $minConfidence, $date );
190
191
					foreach( $productIds as $pid )
192
					{
193
						$litem = $item->getListItem( 'product', 'bought-together', $pid ) ?: $manager->createListItem();
0 ignored issues
show
Bug introduced by
The method createListItem() does not exist on Aimeos\MShop\Common\Manager\Iface. Did you maybe mean create()? ( Ignorable by Annotation )

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

193
						$litem = $item->getListItem( 'product', 'bought-together', $pid ) ?: $manager->/** @scrutinizer ignore-call */ createListItem();

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...
194
						$item->addListItem( 'product', $litem->setRefId( $pid ) );
195
						$listItems->remove( $litem->getId() );
196
					}
197
				}
198
199
				$item->deleteListItems( $listItems );
200
			}
201
202
			$manager->save( $products );
203
204
			$count = count( $counts );
205
			$start += $count;
206
			$search->slice( $start );
207
		}
208
		while( $count >= $search->getLimit() );
209
	}
210
211
212
	/**
213
	 * Returns the IDs of the suggested products.
214
	 *
215
	 * @param string $id Product ID to calculate the suggestions for
216
	 * @param string[] $prodIds List of product IDs to create suggestions for
217
	 * @param int $count Number of ordered products
218
	 * @param int $total Total number of orders
219
	 * @param int $maxItems Maximum number of suggestions
220
	 * @param float $minSupport Minium support value for calculating the suggested products
221
	 * @param float $minConfidence Minium confidence value for calculating the suggested products
222
	 * @param string $date Date in YYYY-MM-DD HH:mm:ss format after which orders should be used for calculations
223
	 * @return array List of suggested product IDs as key and their confidence as value
224
	 */
225
	protected function getSuggestions( string $id, array $prodIds, int $count, int $total, int $maxItems,
226
		float $minSupport, float $minConfidence, string $date ) : array
227
	{
228
		$baseProductManager = \Aimeos\MShop::create( $this->context(), 'order/base/product' );
229
230
		$search = $baseProductManager->filter();
231
		$search->add( $search->and( [
232
			$search->is( 'order.base.product.productid', '==', $prodIds ),
233
			$search->is( 'order.base.product.ctime', '>', $date ),
234
			$search->is( $search->make( 'order.base.product.count', [(string) $id] ), '==', 1 ),
235
		] ) );
236
		$relativeCounts = $baseProductManager->aggregate( $search, 'order.base.product.productid' );
237
238
239
		unset( $relativeCounts[$id] );
240
		$supportA = $count / $total;
241
		$products = [];
242
243
		foreach( $relativeCounts as $prodId => $relCnt )
244
		{
245
			$supportAB = $relCnt / $total;
246
247
			if( $supportAB > $minSupport && ( $conf = ( $supportAB / $supportA ) ) > $minConfidence ) {
248
				$products[$prodId] = $conf;
249
			}
250
		}
251
252
		arsort( $products );
253
254
		return array_keys( array_slice( $products, 0, $maxItems, true ) );
255
	}
256
}
257