PartsTableActionHandler::handleAction()   D
last analyzed

Complexity

Conditions 22
Paths 15

Size

Total Lines 87
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 22
eloc 63
nc 15
nop 4
dl 0
loc 87
rs 4.1666
c 1
b 0
f 0

How to fix   Long Method    Complexity   

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
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Affero General Public License as published
9
 * by the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License
18
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
namespace App\Services\Parts;
22
23
use App\Entity\Parts\Category;
24
use App\Entity\Parts\Footprint;
25
use App\Entity\Parts\Manufacturer;
26
use App\Entity\Parts\MeasurementUnit;
27
use App\Entity\Parts\Part;
28
use App\Entity\Parts\PartLot;
29
use App\Repository\DBElementRepository;
30
use App\Repository\PartRepository;
31
use Doctrine\ORM\EntityManagerInterface;
32
use InvalidArgumentException;
33
use Symfony\Component\HttpFoundation\RedirectResponse;
34
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
35
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
36
use Symfony\Component\Security\Core\Security;
37
38
final class PartsTableActionHandler
39
{
40
    private EntityManagerInterface $entityManager;
41
    private Security $security;
42
    private UrlGeneratorInterface $urlGenerator;
43
44
    public function __construct(EntityManagerInterface $entityManager, Security $security, UrlGeneratorInterface $urlGenerator)
45
    {
46
        $this->entityManager = $entityManager;
47
        $this->security = $security;
48
        $this->urlGenerator = $urlGenerator;
49
    }
50
51
    /**
52
     * Converts the given array to an array of Parts.
53
     *
54
     * @param string $ids a comma separated list of Part IDs
55
     *
56
     * @return Part[]
57
     */
58
    public function idStringToArray(string $ids): array
59
    {
60
        $id_array = explode(',', $ids);
61
62
        /** @var PartRepository $repo */
63
        $repo = $this->entityManager->getRepository(Part::class);
64
65
        return $repo->getElementsFromIDArray($id_array);
66
    }
67
68
    /**
69
     * @param Part[] $selected_parts
70
     * @return RedirectResponse|null Returns a redirect response if the user should be redirected to another page, otherwise null
71
     */
72
    public function handleAction(string $action, array $selected_parts, ?int $target_id, ?string $redirect_url = null): ?RedirectResponse
73
    {
74
        if ($action === 'add_to_project') {
75
            return new RedirectResponse(
76
                $this->urlGenerator->generate('project_add_parts', [
77
                    'id' => $target_id,
78
                    'parts' => implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts)),
79
                    '_redirect' => $redirect_url
80
                ])
81
            );
82
        }
83
84
        if ($action === 'generate_label' || $action === 'generate_label_lot') {
85
            //For parts we can just use the comma separated part IDs
86
            if ($action === 'generate_label') {
87
                $targets = implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts));
88
            } else { //For lots we have to extract the part lots
89
                $targets = implode(',', array_map(static function (Part $part) {
90
                    //We concat the lot IDs of every part with a comma (which are later concated with a comma too per part)
91
                    return implode(',', array_map(static fn (PartLot $lot) => $lot->getID(), $part->getPartLots()->toArray()));
92
                }, $selected_parts));
93
            }
94
95
            return new RedirectResponse(
96
                $this->urlGenerator->generate($target_id !== 0 && $target_id !== null ? 'label_dialog_profile' : 'label_dialog', [
97
                    'profile' => $target_id,
98
                    'target_id' => $targets,
99
                    'generate' => '1',
100
                    'target_type' => $action === 'generate_label_lot' ? 'part_lot' : 'part',
101
                ])
102
            );
103
        }
104
105
106
        //Iterate over the parts and apply the action to it:
107
        foreach ($selected_parts as $part) {
108
            if (!$part instanceof Part) {
109
                throw new InvalidArgumentException('$selected_parts must be an array of Part elements!');
110
            }
111
112
            //We modify parts, so you have to have the permission to modify it
113
            $this->denyAccessUnlessGranted('edit', $part);
114
115
            switch ($action) {
116
                case 'favorite':
117
                    $this->denyAccessUnlessGranted('change_favorite', $part);
118
                    $part->setFavorite(true);
119
                    break;
120
                case 'unfavorite':
121
                    $this->denyAccessUnlessGranted('change_favorite', $part);
122
                    $part->setFavorite(false);
123
                    break;
124
                case 'set_needs_review':
125
                    $this->denyAccessUnlessGranted('edit', $part);
126
                    $part->setNeedsReview(true);
127
                    break;
128
                case 'unset_needs_review':
129
                    $this->denyAccessUnlessGranted('edit', $part);
130
                    $part->setNeedsReview(false);
131
                    break;
132
                case 'delete':
133
                    $this->denyAccessUnlessGranted('delete', $part);
134
                    $this->entityManager->remove($part);
135
                    break;
136
                case 'change_category':
137
                    $this->denyAccessUnlessGranted('@categories.read');
138
                    $part->setCategory($this->entityManager->find(Category::class, $target_id));
139
                    break;
140
                case 'change_footprint':
141
                    $this->denyAccessUnlessGranted('@footprints.read');
142
                    $part->setFootprint(null === $target_id ? null : $this->entityManager->find(Footprint::class, $target_id));
143
                    break;
144
                case 'change_manufacturer':
145
                    $this->denyAccessUnlessGranted('@manufacturers.read');
146
                    $part->setManufacturer(null === $target_id ? null : $this->entityManager->find(Manufacturer::class, $target_id));
147
                    break;
148
                case 'change_unit':
149
                    $this->denyAccessUnlessGranted('@measurement_units.read');
150
                    $part->setPartUnit(null === $target_id ? null : $this->entityManager->find(MeasurementUnit::class, $target_id));
151
                    break;
152
153
                default:
154
                    throw new InvalidArgumentException('The given action is unknown! ('.$action.')');
155
            }
156
        }
157
158
        return null;
159
    }
160
161
    /**
162
     * Throws an exception unless the attributes are granted against the current authentication token and optionally
163
     * supplied subject.
164
     *
165
     * @throws AccessDeniedException
166
     */
167
    private function denyAccessUnlessGranted($attributes, $subject = null, string $message = 'Access Denied.'): void
168
    {
169
        if (!$this->security->isGranted($attributes, $subject)) {
170
            $exception = new AccessDeniedException($message);
171
            $exception->setAttributes($attributes);
172
            $exception->setSubject($subject);
173
174
            throw $exception;
175
        }
176
    }
177
}
178