Completed
Pull Request — master (#38)
by
unknown
10:34 queued 07:56
created

YmlCatalog::gc()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
ccs 0
cts 0
cp 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 6
1
<?php
2
namespace pastuhov\ymlcatalog;
3
4
use pastuhov\ymlcatalog\models\BaseModel;
5
use pastuhov\ymlcatalog\models\Category;
6
use pastuhov\ymlcatalog\models\Currency;
7
use pastuhov\ymlcatalog\models\LocalDeliveryCost;
8
use pastuhov\ymlcatalog\models\Shop;
9
use pastuhov\ymlcatalog\models\SimpleOffer;
10
use pastuhov\ymlcatalog\models\DeliveryOption;
11
use Yii;
12
use pastuhov\FileStream\BaseFileStream;
13
use yii\base\Exception;
14
use yii\base\Model;
15
use yii\data\ActiveDataProvider;
16
use yii\data\Pagination;
17
use yii\db\ActiveQuery;
18
use yii\db\BatchQueryResult;
19
20
/**
21
 * Yml генератор каталога.
22
 *
23
 * @package pastuhov\ymlcatalog
24
 */
25
class YmlCatalog
26
{
27
    /**
28
     * @var BaseFileStream
29
     */
30
    protected $handle;
31
    /**
32
     * @var string
33
     */
34
    protected $shopClass;
35
    /**
36
     * @var string
37
     */
38
    protected $currencyClass;
39
    /**
40
     * @var string
41
     */
42
    protected $categoryClass;
43
    /**
44
     * @var null|string
45
     */
46
    protected $localDeliveryCostClass;
47
    /**
48
     * @var string
49
     */
50
    protected $offerClass;
51
    /**
52
     * @var null|string
53
     */
54
    protected $date;
55
56
    /**
57
     * @var null|callable
58
     */
59
    protected $onValidationError;
60
61
    /**
62
     * @var null|string
63
     */
64
    protected $customOfferClass;
65
66
    /**
67
     * @var null|string
68
     */
69
    protected $deliveryOptionClass;
70
71 9
    /**
72
     * @var ActiveQuery
73
     */
74 2
    private $query = null;
75
76
    /**
77
     * @var BatchQueryResult
78
     */
79
    private $queryIterator = null;
80
81
    /**
82 9
     * @var ActiveDataProvider
83 9
     */
84 9
    private $dataProvider = null;
85 9
86 9
    /**
87 9
     * @var Pagination
88 9
     */
89 9
    private $pagination = null;
90 9
91 9
    /**
92
     * @var int
93
     */
94
    private $paginationPage = 0;
95
96 9
    /**
97
     * @param BaseFileStream $handle
98 9
     * @param string $shopClass class name
99
     * @param string $currencyClass class name
100 9
     * @param string $categoryClass class name
101 9
     * @param string $localDeliveryCostClass class name
102 9
     * @param array $offerClasses
103 9
     * @param null|string $date
104 6
     * @param null|callable $onValidationError
105
     * @param null|string $customOfferClass
106 9
     */
107 9
    public function __construct(
108 9
        BaseFileStream $handle,
109 9
        $shopClass,
110 9
        $currencyClass,
111 9
        $categoryClass,
112 9
        $localDeliveryCostClass = null,
113 9
        Array $offerClasses,
114 9
        $date = null,
115 9
        $onValidationError = null,
116 9
        $customOfferClass = null,
117 9
        $deliveryOptionClass = null
118 4
    ) {
119 6
        $this->handle = $handle;
120 8
        $this->shopClass = $shopClass;
121
        $this->currencyClass = $currencyClass;
122 6
        $this->categoryClass = $categoryClass;
123 6
        $this->localDeliveryCostClass = $localDeliveryCostClass;
124
        $this->offerClasses = $offerClasses;
0 ignored issues
show
Bug introduced by
The property offerClasses does not seem to exist. Did you mean offerClass?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
125
        $this->date = $date;
126
        $this->onValidationError = $onValidationError;
127
        $this->customOfferClass = $customOfferClass;
128 9
        $this->deliveryOptionClass = $deliveryOptionClass;
129
    }
130 9
131
    /**
132 9
     * @throws Exception
133 3
     */
134 2
    public function generate()
135
    {
136 9
        $date = $this->getDate();
137
138
        $this->write(
139
            '<?xml version="1.0" encoding="utf-8"?>' . PHP_EOL .
140
            '<!DOCTYPE yml_catalog SYSTEM "shops.dtd">' . PHP_EOL .
141
            '<yml_catalog date="' . $date . '">' . PHP_EOL
142
        );
143 9
144 6
        $this->writeTag('shop');
145 9
        $this->writeModel(new Shop(), new $this->shopClass());
146 9
        $this->writeTag('currencies');
147
        $this->writeEachModel($this->currencyClass);
148
        $this->writeTag('/currencies');
149
        $this->writeTag('categories');
150
        $this->writeEachModel($this->categoryClass);
151 9
        $this->writeTag('/categories');
152 6
        if($this->deliveryOptionClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deliveryOptionClass of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
153 9
            $this->writeTag('delivery-options');
154 9
            $this->writeModel(new DeliveryOption(), \Yii::createObject($this->deliveryOptionClass));
155
            $this->writeTag('/delivery-options');
156
        }
157
        if($this->localDeliveryCostClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->localDeliveryCostClass of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
158
            $this->writeModel(new LocalDeliveryCost(), \Yii::createObject($this->localDeliveryCostClass));
0 ignored issues
show
Deprecated Code introduced by
The class pastuhov\ymlcatalog\models\LocalDeliveryCost has been deprecated.

This class, trait or interface has been deprecated.

Loading history...
159
        }
160
        $this->writeTag('offers');
161 9
        foreach ($this->offerClasses as $offerClass) {
0 ignored issues
show
Bug introduced by
The property offerClasses does not seem to exist. Did you mean offerClass?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
162
            $this->writeEachModel($offerClass);
163 9
        }
164 9
        $this->writeTag('/offers');
165 9
        $this->writeTag('/shop');
166 9
167
        $this->write('</yml_catalog>');
168 9
    }
169 6
170
    /**
171 9
     * @return null|string
172 9
     */
173 6
    protected function getDate()
174 4
    {
175 9
        $date = $this->date;
176 6
177 4
        if ($date === null) {
178
            $date = Yii::$app->formatter->asDatetime(new \DateTime(), 'php:Y-m-d H:i');
179 9
        }
180 9
181 9
        return $date;
182 6
    }
183 3
184
    /**
185 6
     * @param string $string
186 9
     * @throws \Exception
187
     */
188 9
    protected function write($string)
189
    {
190 9
        $this->handle->write($string);
191
    }
192
193
    /**
194
     * @param string $string tag name
195 9
     */
196
    protected function writeTag($string)
197 9
    {
198 9
        $this->write('<' . $string . '>' . PHP_EOL);
199
    }
200 9
201
    /**
202
     * @param BaseModel $model
203 9
     * @param $valuesModel
204
     * @throws Exception
205 9
     */
206 9
    protected function writeModel(BaseModel $model, $valuesModel)
207 9
    {
208 6
        if (method_exists($valuesModel, 'getParams')) {
209 6
            $model->setParams($valuesModel->getParams());
210 9
        }
211
        if (method_exists($valuesModel, 'getPictures')) {
212
            $model->setPictures($valuesModel->getPictures());
213
        }
214
        if(method_exists($valuesModel, 'getDeliveryOptions')) {
215
            $model->setDeliveryOptions($valuesModel->getDeliveryOptions());
216
        }
217 9
218
        if($model->loadModel($valuesModel, $this->onValidationError)) {
0 ignored issues
show
Bug introduced by
It seems like $this->onValidationError can also be of type callable; however, pastuhov\ymlcatalog\models\BaseModel::loadModel() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
219 9
            $string = $model->getYml();
220
            $this->write($string);
221 9
        }
222 9
    }
223 6
224 9
    /**
225 9
     * @param string|array $modelClass class name or yii configuration array. You can also set params:
226 3
     *      `findParams`:   array of additional find params;
227 3
     *      `query`:        ActiveQuery object to generate yml use already created object;
228 6
     *      `dataProvider`: ActiveDataProvider or true to generate yml with pagination;
229 6
     */
230 4
    protected function writeEachModel($modelClass)
231
    {
232
        /**
233
         * @var mixed
234 9
         */
235
        $findParams = [];
236
237
        /**
238
         * @var ActiveQuery
239
         */
240
        $query = null;
241
242
        /**
243
         * @var ActiveDataProvider
244
         */
245
        $dataProvider = null;
246
247
        $this->query = null;
248
        $this->dataProvider = null;
249
        $this->pagination = null;
250
        $this->paginationPage = 0;
251
        $this->queryIterator = null;
252
253
        if (is_array($modelClass)) {
254
            foreach (['findParams', 'query', 'dataProvider'] as $name) {
255
                if (array_key_exists($name, $modelClass)) {
256
                    $$name = $modelClass[$name];
257
                    unset($modelClass[$name]);
258
                }
259
            }
260
        }
261
262
        /**
263
         * @var BaseFindYmlInterface $class
264
         */
265
        $class = \Yii::createObject($modelClass);
266
267
        if (!empty($dataProvider)) {
268
            if ($dataProvider instanceof ActiveDataProvider) {
269
                $query = $dataProvider->query;
270
            } elseif ($dataProvider === true) {
271
                if (!$query) {
272
                    $query = $class::findYml($findParams);
273
                }
274
                $dataProvider = new ActiveDataProvider([
275
                    'query' => $query,
276
                    'pagination' => [
277
                        'pageSize' => 100
278
                    ]
279
                ]);
280
            } else {
281
                $dataProvider = null;
282
            }
283
            $this->dataProvider = $dataProvider;
284
        }
285
286
        $this->query = ($query ? : $class::findYml($findParams));
287
288
        $newModel = $this->getNewModel($class);
289
290
        if ($this->dataProvider) {
291
            $this->pagination = $this->dataProvider->getPagination();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->dataProvider->getPagination() can also be of type boolean. However, the property $pagination is declared as type object<yii\data\Pagination>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
292
        } else {
293
            $this->queryIterator = $this->query->batch();
294
        }
295
296
        while (($models = $this->getModels()) !== false) {
297
            foreach ($models as $model) {
298
                $this->writeModel($newModel, $model);
299
            }
300
            $this->gc();
301
        }
302
    }
303
304
    /**
305
     * @return Model[]|false
306
     */
307
    protected function getModels()
308
    {
309
        $result = false;
310
311
        if ($this->dataProvider) {
312
            if ($this->paginationPage === 0 || $this->paginationPage < $this->pagination->pageCount) {
313
                if ($this->paginationPage > 0) {
314
                    $this->pagination->setPage($this->paginationPage);
315
                    $this->dataProvider->prepare(true);
316
                }
317
                ++$this->paginationPage;
318
                $result = $this->dataProvider->getModels();
319
            }
320
        } else {
321
            $this->queryIterator->next();
322
            if ($this->queryIterator->valid()) {
323
                $result = $this->queryIterator->current();
324
            }
325
        }
326
327
        return $result;
328
    }
329
330
    /**
331
     * @param $modelClass
332
     * @return Category|Currency|SimpleOffer
333
     * @throws Exception
334
     */
335
    protected function getNewModel($modelClass)
336
    {
337
        $obj = is_object($modelClass) ? $modelClass : \Yii::createObject($modelClass);
338
339
        if ($obj instanceof CurrencyInterface) {
340
            $model = new Currency();
341
        } elseif ($obj instanceof CategoryInterface) {
342
            $model = new Category();
343
        } elseif ($obj instanceof CustomOfferInterface && $this->customOfferClass !== null && class_exists($this->customOfferClass)) {
344
            $model = \Yii::createObject($this->customOfferClass);
345
        } elseif ($obj instanceof SimpleOfferInterface) {
346
            $model = new SimpleOffer();
347
        } else {
348
            throw new Exception('Model ' . get_class($obj) . ' has unknown interface');
349
        }
350
351
        return $model;
352
    }
353
    
354
    /**
355
     * Performs PHP memory garbage collection.
356
     */
357
    protected function gc()
358
    {
359
        if (!gc_enabled()) {
360
            gc_enable();
361
        }
362
        gc_collect_cycles();
363
    }
364
}
365