Completed
Push — master ( 4eff91...8ce34a )
by Marcus
01:39
created

Date::handleDelimiters()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 10
cts 10
cp 1
rs 9.584
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
namespace Mbright\Validation\Rule\Validate\MySql;
4
5
use Mbright\Validation\Rule\Validate\ValidateRuleInterface;
6
7
/**
8
 * Validates that data can be inserted into one of the following column types:
9
 * - Date
10
 */
11
class Date implements ValidateRuleInterface
12
{
13
    /**
14
     * Returns a bool indicating if the value can be safely stored in a MySql Date column.
15
     *
16
     * @param object $subject
17
     * @param string $field
18
     *
19
     * @return bool
20
     */
21 36
    public function __invoke($subject, string $field): bool
22
    {
23 36
        $value = (string) $subject->$field;
24 36
        $dateParts = $this->extractDateParts($value);
25
26 36
        return !is_null($dateParts) && checkdate($dateParts->month, $dateParts->day, $dateParts->year);
27
    }
28
29
    /**
30
     * Extract date parts from the given string.
31
     *
32
     * Will return a stdClass with 3 properties: year, month, day. Each property defaults to null. If the string cannot
33
     * be parsed, return null.
34
     *
35
     * @param string $dateString
36
     *
37
     * @return null|\stdClass
38
     */
39 36
    protected function extractDateParts(string $dateString): ?\stdClass
40
    {
41
        $extractedParts = (object) [
42 36
            'year' => null,
43
            'month' => null,
44
            'day' => null
45
        ];
46
47 36
        $sanitizedDateString = $this->sanitizeDateString($dateString);
48
49 36
        if (is_null($sanitizedDateString)) {
50 9
            return null;
51
        }
52
53
        // extract the individual date parts for checkdate
54 27
        $extractedParts->year = substr($sanitizedDateString, 0, 4);
55 27
        $extractedParts->month = substr($sanitizedDateString, 4, 2);
56 27
        $extractedParts->day = substr($sanitizedDateString, 6, 2);
57
58 27
        return $extractedParts;
59
    }
60
61
    /**
62
     * Sanitizes the given dateString to YYYYMMDD.
63
     *
64
     * Returns null if the string cannot be sanitized.
65
     *
66
     * @param string $dateString
67
     *
68
     * @return null|string
69
     */
70 36
    protected function sanitizeDateString(string $dateString): ?string
71
    {
72 36
        $sanitizedString = $this->handleDelimiters($dateString);
73 36
        if ($sanitizedString) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sanitizedString 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...
74 33
            $sanitizedString = $this->normalizeYear($sanitizedString);
75
            // More than 8 characters here means we don't have the format YYYYMMDD
76 33
            if (strlen($sanitizedString) !== 8) {
77 6
                return null;
78
            }
79
        }
80
81 30
        return $sanitizedString;
82
    }
83
84
    /**
85
     * Strips delimiters from the given date string and returns the sanitized string.
86
     *
87
     * The returned string will be either
88
     *
89
     * @param string $dateString
90
     *
91
     * @return null|string
92
     */
93 36
    protected function handleDelimiters(string $dateString): ?string
94
    {
95
        // Grab everything that isn't an int, these things must be delimiters
96 36
        $delimiters = [];
97 36
        preg_match_all('/\D/', $dateString, $delimiters);
98
99 36
        if (count($delimiters[0]) > 1) {
100
            // ensure that all delimiters are valid delimiters accepted by mysql
101 15
            $delimiterString = implode('', $delimiters[0]);
102 15
            if (preg_match('/[^[:punct:]]/', $delimiterString) > 0) {
103 3
                return null;
104
            }
105
106
            // sanitize valid punctuation characters that are being used as delimiters
107 12
            $delimiterString = preg_quote($delimiterString, '/');
108
            // Remove delimiters to make parsing date segments easier
109 12
            $dateString = preg_replace("/[$delimiterString]/", '', $dateString);
110
        }
111
112 33
        return $dateString;
113
    }
114
115
    /**
116
     * Converts YYMMDD to YYYYMMDD.
117
     *
118
     * @param string $dateString
119
     *
120
     * @return null|string
121
     */
122 33
    public function normalizeYear(string $dateString): ?string
123
    {
124
        // if we have a two digit year, convert it to a 4 digit year for the sake of comparison
125 33
        if (strlen($dateString) === 6) {
126 9
            $twoDigitYear = substr($dateString, 0, 2);
127 9
            $yearPrefix = 0 < $twoDigitYear && $twoDigitYear < 69 ? '20' : '19';
128 9
            $dateString = $yearPrefix . $dateString;
129
        }
130
131 33
        return $dateString;
132
    }
133
}
134