Completed
Push — master ( 02dfab...64bf64 )
by Jeff
11:02
created

ProductController::saveProductCombinations()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 45
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 27
dl 0
loc 45
c 0
b 0
f 0
rs 8.8657
cc 6
nc 9
nop 2
1
<?php
2
3
namespace App\Http\Controllers\Admin\Products;
4
5
use App\Shop\Attributes\Repositories\AttributeRepositoryInterface;
6
use App\Shop\AttributeValues\Repositories\AttributeValueRepositoryInterface;
7
use App\Shop\Brands\Repositories\BrandRepositoryInterface;
8
use App\Shop\Categories\Repositories\Interfaces\CategoryRepositoryInterface;
9
use App\Shop\ProductAttributes\ProductAttribute;
10
use App\Shop\Products\Exceptions\ProductInvalidArgumentException;
11
use App\Shop\Products\Exceptions\ProductNotFoundException;
12
use App\Shop\Products\Product;
13
use App\Shop\Products\Repositories\Interfaces\ProductRepositoryInterface;
14
use App\Shop\Products\Repositories\ProductRepository;
15
use App\Shop\Products\Requests\CreateProductRequest;
16
use App\Shop\Products\Requests\UpdateProductRequest;
17
use App\Http\Controllers\Controller;
18
use App\Shop\Products\Transformations\ProductTransformable;
19
use App\Shop\Tools\UploadableTrait;
20
use Illuminate\Http\Request;
21
use Illuminate\Http\UploadedFile;
22
use Illuminate\Support\Facades\DB;
23
use Illuminate\Support\Facades\Log;
24
use Illuminate\Support\Facades\Validator;
25
26
class ProductController extends Controller
27
{
28
    use ProductTransformable, UploadableTrait;
29
30
    /**
31
     * @var ProductRepositoryInterface
32
     */
33
    private $productRepo;
34
35
    /**
36
     * @var CategoryRepositoryInterface
37
     */
38
    private $categoryRepo;
39
40
    /**
41
     * @var AttributeRepositoryInterface
42
     */
43
    private $attributeRepo;
44
45
    /**
46
     * @var AttributeValueRepositoryInterface
47
     */
48
    private $attributeValueRepository;
49
50
    /**
51
     * @var ProductAttribute
52
     */
53
    private $productAttribute;
54
55
    /**
56
     * @var BrandRepositoryInterface
57
     */
58
    private $brandRepo;
59
60
    /**
61
     * ProductController constructor.
62
     *
63
     * @param ProductRepositoryInterface $productRepository
64
     * @param CategoryRepositoryInterface $categoryRepository
65
     * @param AttributeRepositoryInterface $attributeRepository
66
     * @param AttributeValueRepositoryInterface $attributeValueRepository
67
     * @param ProductAttribute $productAttribute
68
     * @param BrandRepositoryInterface $brandRepository
69
     */
70
    public function __construct(
71
        ProductRepositoryInterface $productRepository,
72
        CategoryRepositoryInterface $categoryRepository,
73
        AttributeRepositoryInterface $attributeRepository,
74
        AttributeValueRepositoryInterface $attributeValueRepository,
75
        ProductAttribute $productAttribute,
76
        BrandRepositoryInterface $brandRepository
77
    ) {
78
        $this->productRepo = $productRepository;
79
        $this->categoryRepo = $categoryRepository;
80
        $this->attributeRepo = $attributeRepository;
81
        $this->attributeValueRepository = $attributeValueRepository;
82
        $this->productAttribute = $productAttribute;
83
        $this->brandRepo = $brandRepository;
84
85
        $this->middleware(['permission:create-product, guard:employee'], ['only' => ['create', 'store']]);
86
        $this->middleware(['permission:update-product, guard:employee'], ['only' => ['edit', 'update']]);
87
        $this->middleware(['permission:delete-product, guard:employee'], ['only' => ['destroy']]);
88
        $this->middleware(['permission:view-product, guard:employee'], ['only' => ['index', 'show']]);
89
    }
90
91
    /**
92
     * Display a listing of the resource.
93
     *
94
     * @return \Illuminate\Http\Response
95
     */
96
    public function index()
97
    {
98
        $list = $this->productRepo->listProducts('id');
99
100
        if (request()->has('q') && request()->input('q') != '') {
101
            $list = $this->productRepo->searchProduct(request()->input('q'));
102
        }
103
104
        $products = $list->map(function (Product $item) {
105
            return $this->transformProduct($item);
106
        })->all();
107
108
        return view('admin.products.list', [
0 ignored issues
show
Bug Best Practice introduced by
The expression return view('admin.produ...esults($products, 25))) returns the type Illuminate\View\View which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
109
            'products' => $this->productRepo->paginateArrayResults($products, 25)
110
        ]);
111
    }
112
113
    /**
114
     * Show the form for creating a new resource.
115
     *
116
     * @return \Illuminate\Http\Response
117
     */
118
    public function create()
119
    {
120
        $categories = $this->categoryRepo->listCategories('name', 'asc')->where('parent_id', 1);
121
122
        return view('admin.products.create', [
0 ignored issues
show
Bug Best Practice introduced by
The expression return view('admin.produ...op\Products\Product())) returns the type Illuminate\View\View which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
123
            'categories' => $categories,
124
            'brands' => $this->brandRepo->listBrands(['*'], 'name', 'asc'),
125
            'default_weight' => env('SHOP_WEIGHT'),
126
            'weight_units' => Product::MASS_UNIT,
127
            'product' => new Product
128
        ]);
129
    }
130
131
    /**
132
     * Store a newly created resource in storage.
133
     *
134
     * @param  CreateProductRequest $request
135
     *
136
     * @return \Illuminate\Http\Response
137
     */
138
    public function store(CreateProductRequest $request)
139
    {
140
        $data = $request->except('_token', '_method');
141
        $data['slug'] = str_slug($request->input('name'));
142
143
        if ($request->hasFile('cover') && $request->file('cover') instanceof UploadedFile) {
144
            $data['cover'] = $this->productRepo->saveCoverImage($request->file('cover'));
145
        }
146
147
        $product = $this->productRepo->createProduct($data);
148
149
        $productRepo = new ProductRepository($product);
150
151
        if ($request->hasFile('image')) {
152
            $productRepo->saveProductImages(collect($request->file('image')));
153
        }
154
155
        $productRepo = new ProductRepository($product);
156
        if ($request->hasFile('image')) {
157
            $productRepo->saveProductImages(collect($request->file('image')));
158
        }
159
160
        if ($request->has('categories')) {
161
            $productRepo->syncCategories($request->input('categories'));
162
        } else {
163
            $productRepo->detachCategories();
164
        }
165
166
        return redirect()->route('admin.products.edit', $product->id)->with('message', 'Create successful');
0 ignored issues
show
Bug introduced by
$product->id of type integer is incompatible with the type array expected by parameter $parameters of Illuminate\Routing\Redirector::route(). ( Ignorable by Annotation )

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

166
        return redirect()->route('admin.products.edit', /** @scrutinizer ignore-type */ $product->id)->with('message', 'Create successful');
Loading history...
Bug Best Practice introduced by
The expression return redirect()->route...', 'Create successful') returns the type Illuminate\Http\RedirectResponse which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
167
    }
168
169
    /**
170
     * Display the specified resource.
171
     *
172
     * @param  int $id
173
     *
174
     * @return \Illuminate\Http\Response
175
     */
176
    public function show(int $id)
177
    {
178
        $product = $this->productRepo->findProductById($id);
179
        return view('admin.products.show', compact('product'));
0 ignored issues
show
Bug Best Practice introduced by
The expression return view('admin.produ...w', compact('product')) returns the type Illuminate\View\View which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
180
    }
181
182
    /**
183
     * Show the form for editing the specified resource.
184
     *
185
     * @param  int $id
186
     *
187
     * @return \Illuminate\Http\Response
188
     */
189
    public function edit(int $id)
190
    {
191
        $product = $this->productRepo->findProductById($id);
192
        $productAttributes = $product->attributes()->get();
193
194
        $qty = $productAttributes->map(function ($item) {
195
            return $item->quantity;
196
        })->sum();
197
198
        if (request()->has('delete') && request()->has('pa')) {
199
            $pa = $productAttributes->where('id', request()->input('pa'))->first();
200
            $pa->attributesValues()->detach();
201
            $pa->delete();
202
203
            request()->session()->flash('message', 'Delete successful');
204
            return redirect()->route('admin.products.edit', [$product->id, 'combination' => 1]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return redirect()->route...d, 'combination' => 1)) returns the type Illuminate\Http\RedirectResponse which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
205
        }
206
207
        $categories = $this->categoryRepo->listCategories('name', 'asc')
208
            ->where('parent_id', 1);
209
210
        return view('admin.products.edit', [
0 ignored issues
show
Bug Best Practice introduced by
The expression return view('admin.produ...ts\Product::MASS_UNIT)) returns the type Illuminate\View\View which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
211
            'product' => $product,
212
            'images' => $product->images()->get(['src']),
213
            'categories' => $categories,
214
            'selectedIds' => $product->categories()->pluck('category_id')->all(),
215
            'attributes' => $this->attributeRepo->listAttributes(),
216
            'productAttributes' => $productAttributes,
217
            'qty' => $qty,
218
            'brands' => $this->brandRepo->listBrands(['*'], 'name', 'asc'),
219
            'weight' => $product->weight,
220
            'default_weight' => $product->mass_unit,
221
            'weight_units' => Product::MASS_UNIT
222
        ]);
223
    }
224
225
    /**
226
     * Update the specified resource in storage.
227
     *
228
     * @param  UpdateProductRequest $request
229
     * @param  int $id
230
     *
231
     * @return \Illuminate\Http\Response
232
     * @throws \App\Shop\Products\Exceptions\ProductUpdateErrorException
233
     */
234
    public function update(UpdateProductRequest $request, int $id)
235
    {
236
        $product = $this->productRepo->findProductById($id);
237
        $productRepo = new ProductRepository($product);
238
239
        if ($request->has('attributeValue')) {
240
            $this->saveProductCombinations($request, $product);
241
            return redirect()->route('admin.products.edit', [$id, 'combination' => 1])
0 ignored issues
show
Bug Best Practice introduced by
The expression return redirect()->route...on created successful') returns the type Illuminate\Http\RedirectResponse which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
242
                ->with('message', 'Attribute combination created successful');
243
        }
244
245
        $data = $request->except(
246
            'categories',
247
            '_token',
248
            '_method',
249
            'default',
250
            'image',
251
            'productAttributeQuantity',
252
            'productAttributePrice',
253
            'attributeValue',
254
            'combination'
255
        );
256
257
        $data['slug'] = str_slug($request->input('name'));
258
259
        if ($request->hasFile('cover')) {
260
            $data['cover'] = $productRepo->saveCoverImage($request->file('cover'));
261
        }
262
263
        if ($request->hasFile('image')) {
264
            $productRepo->saveProductImages(collect($request->file('image')));
265
        }
266
267
        if ($request->has('categories')) {
268
            $productRepo->syncCategories($request->input('categories'));
269
        } else {
270
            $productRepo->detachCategories();
271
        }
272
273
        $productRepo->updateProduct($data);
274
275
        return redirect()->route('admin.products.edit', $id)
0 ignored issues
show
Bug introduced by
$id of type integer is incompatible with the type array expected by parameter $parameters of Illuminate\Routing\Redirector::route(). ( Ignorable by Annotation )

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

275
        return redirect()->route('admin.products.edit', /** @scrutinizer ignore-type */ $id)
Loading history...
Bug Best Practice introduced by
The expression return redirect()->route...', 'Update successful') returns the type Illuminate\Http\RedirectResponse which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
276
            ->with('message', 'Update successful');
277
    }
278
279
    /**
280
     * Remove the specified resource from storage.
281
     *
282
     * @param  int $id
283
     *
284
     * @return \Illuminate\Http\Response
285
     * @throws \Exception
286
     */
287
    public function destroy($id)
288
    {
289
        $product = $this->productRepo->findProductById($id);
290
        $product->categories()->sync([]);
291
        $productAttr = $product->attributes();
292
293
        $productAttr->each(function ($pa) {
294
            DB::table('attribute_value_product_attribute')->where('product_attribute_id', $pa->id)->delete();
295
        });
296
297
        $productAttr->where('product_id', $product->id)->delete();
298
299
        $productRepo = new ProductRepository($product);
300
        $productRepo->removeProduct();
301
302
        return redirect()->route('admin.products.index')->with('message', 'Delete successful');
0 ignored issues
show
Bug Best Practice introduced by
The expression return redirect()->route...', 'Delete successful') returns the type Illuminate\Http\RedirectResponse which is incompatible with the documented return type Illuminate\Http\Response.
Loading history...
303
    }
304
305
    /**
306
     * @param Request $request
307
     *
308
     * @return \Illuminate\Http\RedirectResponse
309
     */
310
    public function removeImage(Request $request)
311
    {
312
        $this->productRepo->deleteFile($request->only('product', 'image'), 'uploads');
313
        return redirect()->back()->with('message', 'Image delete successful');
314
    }
315
316
    /**
317
     * @param Request $request
318
     *
319
     * @return \Illuminate\Http\RedirectResponse
320
     */
321
    public function removeThumbnail(Request $request)
322
    {
323
        $this->productRepo->deleteThumb($request->input('src'));
324
        return redirect()->back()->with('message', 'Image delete successful');
325
    }
326
327
    /**
328
     * @param Request $request
329
     * @param Product $product
330
     * @return boolean
331
     */
332
    private function saveProductCombinations(Request $request, Product $product): bool
333
    {
334
        $fields = $request->only(
335
            'productAttributeQuantity',
336
            'productAttributePrice',
337
            'sale_price',
338
            'default'
339
        );
340
341
        if ($errors = $this->validateFields($fields)) {
342
            return redirect()->route('admin.products.edit', [$product->id, 'combination' => 1])
0 ignored issues
show
Bug Best Practice introduced by
The expression return redirect()->route...))->withErrors($errors) returns the type Illuminate\Http\RedirectResponse which is incompatible with the type-hinted return boolean.
Loading history...
343
                ->withErrors($errors);
344
        }
345
346
        $quantity = $fields['productAttributeQuantity'];
347
        $price = $fields['productAttributePrice'];
348
349
        $sale_price = null;
350
        if (isset($fields['sale_price'])) {
351
            $sale_price = $fields['sale_price'];
352
        }
353
354
        $attributeValues = $request->input('attributeValue');
355
        $productRepo = new ProductRepository($product);
356
357
        $hasDefault = $productRepo->listProductAttributes()->where('default', 1)->count();
358
359
        $default = 0;
360
        if ($request->has('default')) {
361
            $default = $fields['default'];
362
        }
363
364
        if ($default == 1 && $hasDefault > 0) {
365
            $default = 0;
366
        }
367
368
        $productAttribute = $productRepo->saveProductAttributes(
369
            new ProductAttribute(compact('quantity', 'price', 'sale_price', 'default'))
370
        );
371
372
        // save the combinations
373
        return collect($attributeValues)->each(function ($attributeValueId) use ($productRepo, $productAttribute) {
0 ignored issues
show
Bug Best Practice introduced by
The expression return collect($attribut...{ /* ... */ })->count() returns the type integer which is incompatible with the type-hinted return boolean.
Loading history...
374
            $attribute = $this->attributeValueRepository->find($attributeValueId);
375
            return $productRepo->saveCombination($productAttribute, $attribute);
376
        })->count();
377
    }
378
379
    /**
380
     * @param array $data
381
     *
382
     * @return
383
     */
384
    private function validateFields(array $data)
385
    {
386
        $validator = Validator::make($data, [
387
            'productAttributeQuantity' => 'required'
388
        ]);
389
390
        if ($validator->fails()) {
391
            return $validator;
392
        }
393
    }
394
}
395