SheetManager::throwIfNameIsInvalid()   B
last analyzed

Complexity

Conditions 8
Paths 21

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 37
ccs 22
cts 22
cp 1
rs 8.0835
c 0
b 0
f 0
cc 8
nc 21
nop 2
crap 8
1
<?php
2
3
namespace Box\Spout\Writer\Common\Manager;
4
5
use Box\Spout\Common\Helper\StringHelper;
6
use Box\Spout\Writer\Common\Entity\Sheet;
7
use Box\Spout\Writer\Exception\InvalidSheetNameException;
8
9
/**
10
 * Class SheetManager
11
 * Sheet manager
12
 */
13
class SheetManager
14
{
15
    /** Sheet name should not exceed 31 characters */
16
    const MAX_LENGTH_SHEET_NAME = 31;
17
18
    /** @var array Invalid characters that cannot be contained in the sheet name */
19
    private static $INVALID_CHARACTERS_IN_SHEET_NAME = ['\\', '/', '?', '*', ':', '[', ']'];
20
21
    /** @var array Associative array [WORKBOOK_ID] => [[SHEET_INDEX] => [SHEET_NAME]] keeping track of sheets' name to enforce uniqueness per workbook */
22
    private static $SHEETS_NAME_USED = [];
23
24
    /** @var StringHelper */
25
    private $stringHelper;
26
27
    /**
28
     * SheetManager constructor.
29
     *
30
     * @param StringHelper $stringHelper
31
     */
32 98
    public function __construct(StringHelper $stringHelper)
33
    {
34 98
        $this->stringHelper = $stringHelper;
35 98
    }
36
37
    /**
38
     * Throws an exception if the given sheet's name is not valid.
39
     * @see Sheet::setName for validity rules.
40
     *
41
     * @param string $name
42
     * @param Sheet $sheet The sheet whose future name is checked
43
     * @throws \Box\Spout\Writer\Exception\InvalidSheetNameException If the sheet's name is invalid.
44
     * @return void
45
     */
46 98
    public function throwIfNameIsInvalid($name, Sheet $sheet)
47
    {
48 98
        if (!\is_string($name)) {
49 2
            $actualType = \gettype($name);
50 2
            $errorMessage = "The sheet's name is invalid. It must be a string ($actualType given).";
51 2
            throw new InvalidSheetNameException($errorMessage);
52
        }
53
54 98
        $failedRequirements = [];
55 98
        $nameLength = $this->stringHelper->getStringLength($name);
56
57 98
        if (!$this->isNameUnique($name, $sheet)) {
58 3
            $failedRequirements[] = 'It should be unique';
59
        } else {
60 98
            if ($nameLength === 0) {
61 1
                $failedRequirements[] = 'It should not be blank';
62
            } else {
63 98
                if ($nameLength > self::MAX_LENGTH_SHEET_NAME) {
64 1
                    $failedRequirements[] = 'It should not exceed 31 characters';
65
                }
66
67 98
                if ($this->doesContainInvalidCharacters($name)) {
68 7
                    $failedRequirements[] = 'It should not contain these characters: \\ / ? * : [ or ]';
69
                }
70
71 98
                if ($this->doesStartOrEndWithSingleQuote($name)) {
72 2
                    $failedRequirements[] = 'It should not start or end with a single quote';
73
                }
74
            }
75
        }
76
77 98
        if (\count($failedRequirements) !== 0) {
78 14
            $errorMessage = "The sheet's name (\"$name\") is invalid. It did not respect these rules:\n - ";
79 14
            $errorMessage .= \implode("\n - ", $failedRequirements);
80 14
            throw new InvalidSheetNameException($errorMessage);
81
        }
82 98
    }
83
84
    /**
85
     * Returns whether the given name contains at least one invalid character.
86
     * @see Sheet::$INVALID_CHARACTERS_IN_SHEET_NAME for the full list.
87
     *
88
     * @param string $name
89
     * @return bool TRUE if the name contains invalid characters, FALSE otherwise.
90
     */
91 98
    private function doesContainInvalidCharacters($name)
92
    {
93 98
        return (\str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name);
94
    }
95
96
    /**
97
     * Returns whether the given name starts or ends with a single quote
98
     *
99
     * @param string $name
100
     * @return bool TRUE if the name starts or ends with a single quote, FALSE otherwise.
101
     */
102 98
    private function doesStartOrEndWithSingleQuote($name)
103
    {
104 98
        $startsWithSingleQuote = ($this->stringHelper->getCharFirstOccurrencePosition('\'', $name) === 0);
105 98
        $endsWithSingleQuote = ($this->stringHelper->getCharLastOccurrencePosition('\'', $name) === ($this->stringHelper->getStringLength($name) - 1));
106
107 98
        return ($startsWithSingleQuote || $endsWithSingleQuote);
108
    }
109
110
    /**
111
     * Returns whether the given name is unique.
112
     *
113
     * @param string $name
114
     * @param Sheet $sheet The sheet whose future name is checked
115
     * @return bool TRUE if the name is unique, FALSE otherwise.
116
     */
117 98
    private function isNameUnique($name, Sheet $sheet)
118
    {
119 98
        foreach (self::$SHEETS_NAME_USED[$sheet->getAssociatedWorkbookId()] as $sheetIndex => $sheetName) {
120 35
            if ($sheetIndex !== $sheet->getIndex() && $sheetName === $name) {
121 3
                return false;
122
            }
123
        }
124
125 98
        return true;
126
    }
127
128
    /**
129
     * @param int $workbookId Workbook ID associated to a Sheet
130
     * @return void
131
     */
132 98
    public function markWorkbookIdAsUsed($workbookId)
133
    {
134 98
        if (!isset(self::$SHEETS_NAME_USED[$workbookId])) {
135 83
            self::$SHEETS_NAME_USED[$workbookId] = [];
136
        }
137 98
    }
138
139
    /**
140
     * @param Sheet $sheet
141
     * @return void
142
     */
143 98
    public function markSheetNameAsUsed(Sheet $sheet)
144
    {
145 98
        self::$SHEETS_NAME_USED[$sheet->getAssociatedWorkbookId()][$sheet->getIndex()] = $sheet->getName();
146 98
    }
147
}
148