Issues (126)

src/EdmUtil.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AlgoWeb\ODataMetadata;
6
7
use AlgoWeb\ODataMetadata\Csdl\Internal\Semantics\BadElements\UnresolvedFunction;
8
use AlgoWeb\ODataMetadata\Exception\ArgumentNullException;
9
use AlgoWeb\ODataMetadata\Interfaces\IEntityContainerElement;
10
use AlgoWeb\ODataMetadata\Interfaces\IFunction;
11
use AlgoWeb\ODataMetadata\Interfaces\IFunctionBase;
12
use AlgoWeb\ODataMetadata\Interfaces\IFunctionImport;
13
use AlgoWeb\ODataMetadata\Interfaces\IFunctionParameter;
14
use AlgoWeb\ODataMetadata\Interfaces\IProperty;
15
use AlgoWeb\ODataMetadata\Interfaces\ISchemaElement;
16
use AlgoWeb\ODataMetadata\Interfaces\ISchemaType;
17
use AlgoWeb\ODataMetadata\Interfaces\IVocabularyAnnotatable;
18
19
/**
20
 * Class EdmUtil.
21
 * @package AlgoWeb\ODataMetadata
22
 * @internal
23
 */
24
class EdmUtil
25
{
26
    // this is what we should be doing for CDM schemas
27
    // the RegEx for valid identifiers are taken from the C# Language Specification (2.4.2 Identifiers)
28
    // (except that we exclude _ as a valid starting character).
29
    // This results in a somewhat smaller set of identifier from what System.CodeDom.Compiler.CodeGenerator.IsValidLanguageIndependentIdentifier
30
    // allows. Not all identifiers allowed by IsValidLanguageIndependentIdentifier are valid in C#.IsValidLanguageIndependentIdentifier allows:
31
    //    Mn, Mc, and Pc as a leading character (which the spec and C# (at least for some Mn and Mc characters) do not allow)
32
    //    characters that Char.GetUnicodeCategory says are in Nl and Cf but which the RegEx does not accept (and which C# does allow).
33
    //
34
    // we could create the StartCharacterExp and OtherCharacterExp dynamically to force inclusion of the missing Nl and Cf characters...
35
    // initial character must be a unicode char - lowercase, uppercase, titlecase, other, modifier, or letter number
36
    private const StartCharacterExp = '[\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nl}]';
37
    private const OtherCharacterExp = '[\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{Cf}]';
38
39
    //private const NameExp = self::StartCharacterExp . self::OtherCharacterExp . "{0,}";
40
41
    public static function isNullOrWhiteSpaceInternal(?string $value): bool
42
    {
43
        return null === $value || '' === trim($value);
44
    }
45
46
    public static function checkArgumentNull($value, string $parameterName)
47
    {
48
        if (null === $value) {
49
            throw new ArgumentNullException($parameterName);
50
        }
51
52
        return $value;
53
    }
54
55
    // This is testing if the name can be parsed and serialized, not if it is valid.
56
    public static function isQualifiedName(string $name): bool
57
    {
58
        $nameTokens = explode('.', $name);
59
        if (count($nameTokens) < 2) {
60
            return false;
61
        }
62
63
        foreach ($nameTokens as $token) {
64
            if (empty($token)) {
65
                return false;
66
            }
67
        }
68
69
        return true;
70
    }
71
72
    public static function tryGetNamespaceNameFromQualifiedName(
73
        string $qualifiedName,
74
        ?string &$namespaceName,
75
        ?string &$name
76
    ): bool {
77
        // Qualified name can be a function import name which is separated by '/'
78
        $lastSlash = strrpos($qualifiedName, '/');
79
        if (false === $lastSlash) {
80
            // Not a FunctionImport
81
            $lastDot = strrpos($qualifiedName, '.');
82
            if (false === $lastDot) {
83
                $namespaceName = '';
84
                $name          = $qualifiedName;
85
                return false;
86
            }
87
88
            $namespaceName = substr($qualifiedName, 0, $lastDot);
89
            $name          = substr($qualifiedName, $lastDot + 1);
90
            return true;
91
        }
92
93
        $namespaceName = substr($qualifiedName, 0, $lastSlash);
94
        $name          = substr($qualifiedName, $lastSlash + 1);
95
        return true;
96
    }
97
98
    public static function fullyQualifiedName(IVocabularyAnnotatable $element): ?string
99
    {
100
        if ($element instanceof ISchemaElement) {
101
            if ($element instanceof IFunction) {
102
                return self::parameterizedName($element);
103
            } else {
104
                return $element->fullName();
105
            }
106
        } else {
107
            if ($element instanceof IEntityContainerElement) {
108
                $container = $element->getContainer();
109
                $fullName  = (null !== $container) ? $container->fullName() : '';
110
                if ($element instanceof IFunctionImport) {
111
                    return $fullName . '/' . self::parameterizedName($element);
112
                } else {
113
                    return $fullName . '/' . $element->getName();
114
                }
115
            } else {
116
                if ($element instanceof IProperty) {
117
                    $declaringSchemaType = $element->getDeclaringType();
118
                    if ($declaringSchemaType instanceof ISchemaType) {
119
                        $propertyOwnerName = self::fullyQualifiedName($declaringSchemaType);
120
                        if (null !== $propertyOwnerName) {
121
                            return $propertyOwnerName . '/' . $element->getName();
122
                        }
123
                    }
124
                } else {
125
                    if ($element instanceof IFunctionParameter) {
126
                        $parameterOwnerName = self::fullyQualifiedName($element->getDeclaringFunction());
127
                        if (null !== $parameterOwnerName) {
128
                            return $parameterOwnerName . '/' . $element->getName();
129
                        }
130
                    }
131
                }
132
            }
133
        }
134
135
        return null;
136
    }
137
138
139
    public static function parameterizedName(IFunctionBase $function): string
140
    {
141
        $index          = 0;
142
        $parameterCount = count($function->getParameters());
0 ignored issues
show
It seems like $function->getParameters() can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

142
        $parameterCount = count(/** @scrutinizer ignore-type */ $function->getParameters());
Loading history...
143
        $s              = '';
144
        if ($function instanceof UnresolvedFunction) {
145
            EdmUtil::checkArgumentNull($function->getNamespace(), 'function->getNamespace');
146
            $s .= $function->getNamespace();
147
            $s .= '/';
148
            $s .= $function->getName();
149
150
            return $s;
151
        }
152
153
        // If we have a function (rather than a function import), we want the parameterized name to include the namespace
154
        if ($function instanceof ISchemaElement) {
155
            EdmUtil::checkArgumentNull($function->getNamespace(), 'function->getNamespace');
156
            $s .= $function->getNamespace();
157
            $s .= '.';
158
        }
159
160
        $s .= $function->getName();
161
        $s .= '(';
162
        foreach ($function->getParameters() as $parameter) {
163
            if ($parameter->getType()->isCollection()) {
164
                $typeName = CsdlConstants::Value_Collection . '(' . $parameter->getType()->asCollection()->elementType()->fullName() . ')';
165
            } elseif ($parameter->getType()->isEntityReference()) {
166
                $typeName = CsdlConstants::Value_Ref . '(' . $parameter->getType()->asEntityReference()->entityType()->fullName() . ')';
167
            } else {
168
                $typeName = $parameter->getType()->fullName();
169
            }
170
171
            $s .= $typeName;
172
            ++$index;
173
            if ($index < $parameterCount) {
174
                $s .= ', ';
175
            }
176
        }
177
178
        $s .= ')';
179
        return $s;
180
    }
181
182
    public static function isValidDottedName(string $name): bool
183
    {
184
        // Each part of the dotted name needs to be a valid name.
185
        return array_reduce(explode('.', $name), function (bool $carry, string $part): bool {
186
            $carry &= self::isValidUndottedName($part);
187
            return boolval($carry);
188
        }, true);
189
    }
190
191
    public static function isValidUndottedName(string $name): bool
192
    {
193
        $nameExp = '/^' . self::StartCharacterExp . self::OtherCharacterExp . '{0,}/';
194
        return !empty($name) && preg_match($nameExp, $name);
195
    }
196
}
197