Completed
Push — master ( b4b898...f81b53 )
by Woody
11s
created

src/Identifier.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
declare(strict_types=1);
3
4
namespace Latitude\QueryBuilder;
5
6
class Identifier
7
{
8
    const IDENTIFIER_REGEX = '/^[a-zA-Z](?:[a-zA-Z0-9_]+)?$/';
9
    const IDENTIFIER_CAPTURE_REGEX = '/([a-zA-Z](?:[a-zA-Z0-9_]+)?\.[a-zA-Z](?:[a-zA-Z0-9_]+)?)/';
10
11
    /**
12
     * @var Identifier
13
     */
14
    protected static $default;
15
16
    /**
17
     * Set the default identifier instance.
18
     */
19 6
    public static function setDefault(Identifier $default)
20
    {
21 6
        self::$default = $default;
22 6
    }
23
24
    /**
25
     * Get the default identifier instance.
26
     */
27 65
    public static function getDefault(): Identifier
28
    {
29 65
        return self::$default ?: static::make();
30
    }
31
32
    /**
33
     * Create a new identifier instance.
34
     */
35 82
    public static function make(): Identifier
36
    {
37 82
        return new static();
38
    }
39
40
    /**
41
     * Escape an unqualified identifier.
42
     */
43 69
    public function escape(string $identifier): string
44
    {
45 69
        if ($identifier === '*') {
46 1
            return $identifier;
47
        }
48
49 68
        $this->guardIdentifier($identifier);
50 66
        return $this->surround($identifier);
51
    }
52
53
    /**
54
     * Escape a (possibly) qualified identifier.
55
     */
56 61
    public function escapeQualified($identifier): string
57
    {
58 61
        if ($this->isExpression($identifier)) {
59 4
            return $identifier->sql($this);
60
        }
61
62 61
        if (\strpos($identifier, '.') === false) {
63 57
            return $this->escape($identifier);
64
        }
65
66 19
        $parts = \explode('.', $identifier);
67 19
        return \implode('.', \array_map([$this, 'escape'], $parts));
68
    }
69
70
    /**
71
     * Escape a identifier alias.
72
     */
73 30
    public function escapeAlias($alias): string
74
    {
75 30
        if ($this->isExpression($alias)) {
76 3
            return $alias->sql($this);
77
        }
78
79 30
        $parts = \preg_split('/ (?:AS )?/i', $alias);
80 30
        return \implode(' AS ', \array_map([$this, 'escapeQualified'], $parts));
81
    }
82
83
    /**
84
     * Escape a list of identifiers.
85
     */
86 11
    public function all(array $identifiers): array
87
    {
88 11
        return \array_map([$this, 'escape'], $identifiers);
89
    }
90
91
    /**
92
     * Escape a list of (possibly) qualified identifiers.
93
     */
94 13
    public function allQualified(array $identifiers): array
95
    {
96 13
        return \array_map([$this, 'escapeQualified'], $identifiers);
97
    }
98
99
    /**
100
     * Escape a list of identifier aliases.
101
     */
102 28
    public function allAliases(array $aliases): array
103
    {
104 28
        return \array_map([$this, 'escapeAlias'], $aliases);
105
    }
106
107
    /**
108
     * Escape all qualified identifiers in an expression.
109
     */
110 36
    public function escapeExpression(string $expression): string
111
    {
112 36
        if (\strpos($expression, '.') === false) {
113 26
            return $expression;
114
        }
115
116
        // table.col = other.col -> [table.col, other.col]
117 12
        \preg_match_all(self::IDENTIFIER_CAPTURE_REGEX, $expression, $matches);
118
        // [table.col, ...] -> ["table"."col", ...]
0 ignored issues
show
Unused Code Comprehensibility introduced by Woody Gilk
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
119 12
        $matches[1] = \array_map([$this, 'escapeQualified'], $matches[1]);
120
        // table.col = other.col -> "table"."col" = "other"."col"
121 12
        return \str_replace($matches[0], $matches[1], $expression);
122
    }
123
124
    /**
125
     * Surround the identifier with escape characters.
126
     */
127 57
    protected function surround(string $identifier): string
128
    {
129 57
        return $identifier;
130
    }
131
132
    /**
133
     * Check if the identifier is an identifier expression.
134
     */
135 61
    final protected function isExpression($identifier): bool
136
    {
137 61
        return \is_object($identifier) && $identifier instanceof Expression;
138
    }
139
140
    /**
141
     * Ensure that identifiers match SQL standard.
142
     *
143
     * @throws IdentifierException
144
     *  If the identifier is not valid.
145
     */
146 68
    final protected function guardIdentifier(string $identifier)
147
    {
148 68
        if (\preg_match('/^[a-zA-Z](?:[a-zA-Z0-9_]+)?$/', $identifier) == false) {
0 ignored issues
show
Bug Best Practice introduced by Woody Gilk
It seems like you are loosely comparing a-zA-Z?$/', $identifier)">\preg_match('/^...9_]+">a-zA-Z?$/', $identifier) of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
149 2
            throw IdentifierException::invalidIdentifier($identifier);
150
        }
151 66
    }
152
}
153