Skip to content

Commit bdcd51d

Browse files
committed
Refactored barcode scan functions
This is preparatory work for issue #373
1 parent 563edb1 commit bdcd51d

File tree

8 files changed

+322
-162
lines changed

8 files changed

+322
-162
lines changed

src/Controller/ScanController.php

+20-6
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@
4242
namespace App\Controller;
4343

4444
use App\Form\LabelSystem\ScanDialogType;
45-
use App\Services\LabelSystem\Barcodes\BarcodeNormalizer;
45+
use App\Services\LabelSystem\Barcodes\BarcodeScanHelper;
4646
use App\Services\LabelSystem\Barcodes\BarcodeRedirector;
47+
use App\Services\LabelSystem\Barcodes\BarcodeScanResult;
48+
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
4749
use Doctrine\ORM\EntityNotFoundException;
4850
use InvalidArgumentException;
4951
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -55,7 +57,7 @@
5557
#[Route(path: '/scan')]
5658
class ScanController extends AbstractController
5759
{
58-
public function __construct(protected BarcodeRedirector $barcodeParser, protected BarcodeNormalizer $barcodeNormalizer)
60+
public function __construct(protected BarcodeRedirector $barcodeParser, protected BarcodeScanHelper $barcodeNormalizer)
5961
{
6062
}
6163

@@ -73,10 +75,9 @@ public function dialog(Request $request, #[MapQueryParameter] ?string $input = n
7375

7476
if ($input !== null) {
7577
try {
76-
[$type, $id] = $this->barcodeNormalizer->normalizeBarcodeContent($input);
77-
78+
$scan_result = $this->barcodeNormalizer->scanBarcodeContent($input);
7879
try {
79-
return $this->redirect($this->barcodeParser->getRedirectURL($type, $id));
80+
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
8081
} catch (EntityNotFoundException) {
8182
$this->addFlash('success', 'scan.qr_not_found');
8283
}
@@ -95,10 +96,23 @@ public function dialog(Request $request, #[MapQueryParameter] ?string $input = n
9596
*/
9697
public function scanQRCode(string $type, int $id): Response
9798
{
99+
$type = strtolower($type);
100+
98101
try {
99102
$this->addFlash('success', 'scan.qr_success');
100103

101-
return $this->redirect($this->barcodeParser->getRedirectURL($type, $id));
104+
if (!isset(BarcodeScanHelper::QR_TYPE_MAP[$type])) {
105+
throw new InvalidArgumentException('Unknown type: '.$type);
106+
}
107+
//Construct the scan result manually, as we don't have a barcode here
108+
$scan_result = new BarcodeScanResult(
109+
target_type: BarcodeScanHelper::QR_TYPE_MAP[$type],
110+
target_id: $id,
111+
//The routes are only used on the internal generated QR codes
112+
source_type: BarcodeSourceType::INTERNAL
113+
);
114+
115+
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
102116
} catch (EntityNotFoundException) {
103117
$this->addFlash('success', 'scan.qr_not_found');
104118

src/Services/LabelSystem/Barcodes/BarcodeRedirector.php

+11-12
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
namespace App\Services\LabelSystem\Barcodes;
4343

44+
use App\Entity\LabelSystem\LabelSupportedElement;
4445
use App\Entity\Parts\PartLot;
4546
use Doctrine\ORM\EntityManagerInterface;
4647
use Doctrine\ORM\EntityNotFoundException;
@@ -59,32 +60,30 @@ public function __construct(private readonly UrlGeneratorInterface $urlGenerator
5960
/**
6061
* Determines the URL to which the user should be redirected, when scanning a QR code.
6162
*
62-
* @param string $type The type of the element that was scanned (e.g. 'part', 'lot', etc.)
63-
* @param int $id The ID of the element that was scanned
64-
*
63+
* @param BarcodeScanResult $barcodeScan The result of the barcode scan
6564
* @return string the URL to which should be redirected
6665
*
6766
* @throws EntityNotFoundException
6867
*/
69-
public function getRedirectURL(string $type, int $id): string
68+
public function getRedirectURL(BarcodeScanResult $barcodeScan): string
7069
{
71-
switch ($type) {
72-
case 'part':
73-
return $this->urlGenerator->generate('app_part_show', ['id' => $id]);
74-
case 'lot':
70+
switch ($barcodeScan->target_type) {
71+
case LabelSupportedElement::PART:
72+
return $this->urlGenerator->generate('app_part_show', ['id' => $barcodeScan->target_id]);
73+
case LabelSupportedElement::PART_LOT:
7574
//Try to determine the part to the given lot
76-
$lot = $this->em->find(PartLot::class, $id);
75+
$lot = $this->em->find(PartLot::class, $barcodeScan->target_id);
7776
if (!$lot instanceof PartLot) {
7877
throw new EntityNotFoundException();
7978
}
8079

8180
return $this->urlGenerator->generate('app_part_show', ['id' => $lot->getPart()->getID()]);
8281

83-
case 'location':
84-
return $this->urlGenerator->generate('part_list_store_location', ['id' => $id]);
82+
case LabelSupportedElement::STORELOCATION:
83+
return $this->urlGenerator->generate('part_list_store_location', ['id' => $barcodeScan->target_id]);
8584

8685
default:
87-
throw new InvalidArgumentException('Unknown $type: '.$type);
86+
throw new InvalidArgumentException('Unknown $type: '.$barcodeScan->target_type->name);
8887
}
8988
}
9089
}

src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php renamed to src/Services/LabelSystem/Barcodes/BarcodeScanHelper.php

+69-13
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,59 @@
4141

4242
namespace App\Services\LabelSystem\Barcodes;
4343

44+
use App\Entity\LabelSystem\LabelSupportedElement;
4445
use InvalidArgumentException;
4546

4647
/**
4748
* @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeNormalizerTest
4849
*/
49-
final class BarcodeNormalizer
50+
final class BarcodeScanHelper
5051
{
5152
private const PREFIX_TYPE_MAP = [
52-
'L' => 'lot',
53-
'P' => 'part',
54-
'S' => 'location',
53+
'L' => LabelSupportedElement::PART_LOT,
54+
'P' => LabelSupportedElement::PART,
55+
'S' => LabelSupportedElement::STORELOCATION,
5556
];
5657

58+
public const QR_TYPE_MAP = [
59+
'lot' => LabelSupportedElement::PART_LOT,
60+
'part' => LabelSupportedElement::PART,
61+
'location' => LabelSupportedElement::STORELOCATION,
62+
];
63+
64+
/**
65+
* Parse the given barcode content and return the target type and ID.
66+
* If the barcode could not be parsed, an exception is thrown.
67+
* Using the $type parameter, you can specify how the barcode should be parsed. If set to null, the function
68+
* will try to guess the type.
69+
* @param string $input
70+
* @param BarcodeSourceType|null $type
71+
* @return BarcodeScanResult
72+
*/
73+
public function scanBarcodeContent(string $input, ?BarcodeSourceType $type = null): BarcodeScanResult
74+
{
75+
//Do specific parsing
76+
if ($type === BarcodeSourceType::INTERNAL) {
77+
return $this->parseInternalBarcode($input) ?? throw new InvalidArgumentException('Could not parse barcode');
78+
}
79+
80+
//Null means auto and we try the different formats
81+
$result = $this->parseInternalBarcode($input);
82+
83+
if ($result !== null) {
84+
return $result;
85+
}
86+
throw new InvalidArgumentException('Unknown barcode format');
87+
}
88+
5789
/**
58-
* Parses barcode content and normalizes it.
59-
* Returns an array in the format ['part', 1]: First entry contains element type, second the ID of the element.
90+
* This function tries to interpret the given barcode content as an internal barcode.
91+
* If the barcode could not be parsed at all, null is returned. If the barcode is a valid format, but could
92+
* not be found in the database, an exception is thrown.
93+
* @param string $input
94+
* @return BarcodeScanResult|null
6095
*/
61-
public function normalizeBarcodeContent(string $input): array
96+
private function parseInternalBarcode(string $input): ?BarcodeScanResult
6297
{
6398
$input = trim($input);
6499
$matches = [];
@@ -68,7 +103,11 @@ public function normalizeBarcodeContent(string $input): array
68103

69104
//Extract parts from QR code's URL
70105
if (preg_match('#^https?://.*/scan/(\w+)/(\d+)/?$#', $input, $matches)) {
71-
return [$matches[1], (int) $matches[2]];
106+
return new BarcodeScanResult(
107+
target_type: self::QR_TYPE_MAP[strtolower($matches[1])],
108+
target_id: (int) $matches[2],
109+
source_type: BarcodeSourceType::INTERNAL
110+
);
72111
}
73112

74113
//New Code39 barcode use L0001 format
@@ -80,7 +119,11 @@ public function normalizeBarcodeContent(string $input): array
80119
throw new InvalidArgumentException('Unknown prefix '.$prefix);
81120
}
82121

83-
return [self::PREFIX_TYPE_MAP[$prefix], $id];
122+
return new BarcodeScanResult(
123+
target_type: self::PREFIX_TYPE_MAP[$prefix],
124+
target_id: $id,
125+
source_type: BarcodeSourceType::INTERNAL
126+
);
84127
}
85128

86129
//During development the L-000001 format was used
@@ -92,19 +135,32 @@ public function normalizeBarcodeContent(string $input): array
92135
throw new InvalidArgumentException('Unknown prefix '.$prefix);
93136
}
94137

95-
return [self::PREFIX_TYPE_MAP[$prefix], $id];
138+
return new BarcodeScanResult(
139+
target_type: self::PREFIX_TYPE_MAP[$prefix],
140+
target_id: $id,
141+
source_type: BarcodeSourceType::INTERNAL
142+
);
96143
}
97144

98145
//Legacy Part-DB location labels used $L00336 format
99146
if (preg_match('#^\$L(\d{5,})$#', $input, $matches)) {
100-
return ['location', (int) $matches[1]];
147+
return new BarcodeScanResult(
148+
target_type: LabelSupportedElement::STORELOCATION,
149+
target_id: (int) $matches[1],
150+
source_type: BarcodeSourceType::INTERNAL
151+
);
101152
}
102153

103154
//Legacy Part-DB used EAN8 barcodes for part labels. Format 0000001(2) (note the optional 8th digit => checksum)
104155
if (preg_match('#^(\d{7})\d?$#', $input, $matches)) {
105-
return ['part', (int) $matches[1]];
156+
return new BarcodeScanResult(
157+
target_type: LabelSupportedElement::PART,
158+
target_id: (int) $matches[1],
159+
source_type: BarcodeSourceType::INTERNAL
160+
);
106161
}
107162

108-
throw new InvalidArgumentException('Unknown barcode format!');
163+
//This function abstain from further parsing
164+
return null;
109165
}
110166
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
/*
3+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4+
*
5+
* Copyright (C) 2019 - 2023 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+
declare(strict_types=1);
22+
23+
24+
namespace App\Services\LabelSystem\Barcodes;
25+
26+
use App\Entity\LabelSystem\LabelSupportedElement;
27+
28+
/**
29+
* This class represents the result of a barcode scan, with the target type and the ID of the element
30+
*/
31+
class BarcodeScanResult
32+
{
33+
public function __construct(
34+
public readonly LabelSupportedElement $target_type,
35+
public readonly int $target_id,
36+
public readonly BarcodeSourceType $source_type,
37+
) {
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
/*
3+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4+
*
5+
* Copyright (C) 2019 - 2023 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+
declare(strict_types=1);
22+
23+
24+
namespace App\Services\LabelSystem\Barcodes;
25+
26+
/**
27+
* This enum represents the different types, where a barcode/QR-code can be generated from
28+
*/
29+
enum BarcodeSourceType
30+
{
31+
/** This Barcode was generated using Part-DB internal recommended barcode generator */
32+
case INTERNAL;
33+
}

0 commit comments

Comments
 (0)