• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wol-soft / php-json-schema-model-generator / 24574026820

17 Apr 2026 03:50PM UTC coverage: 98.551% (-0.1%) from 98.654%
24574026820

push

github

web-flow
Merge pull request #125 from wol-soft/attributes

attributes

1815 of 1837 new or added lines in 80 files covered. (98.8%)

1 existing line in 1 file now uncovered.

4557 of 4624 relevant lines covered (98.55%)

610.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.81
/src/Utils/FilterReflection.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace PHPModelGenerator\Utils;
6

7
use PHPModelGenerator\Exception\InvalidFilterException;
8
use PHPModelGenerator\Filter\FilterInterface;
9
use PHPModelGenerator\Filter\TransformingFilterInterface;
10
use PHPModelGenerator\Model\Property\PropertyInterface;
11
use ReflectionException;
12
use ReflectionMethod;
13
use ReflectionNamedType;
14
use ReflectionUnionType;
15

16
/**
17
 * Reflection utilities for filter callables.
18
 *
19
 * Derives accepted types from the callable's first parameter and return type
20
 * information from the callable's return type hint.
21
 */
22
class FilterReflection
23
{
24
    /**
25
     * Derive accepted PHP type names from the first parameter of the filter callable.
26
     *
27
     * Returns an empty array for 'mixed' (accepts all types — no runtime type guard is generated).
28
     * Throws when the parameter has no type hint; all filter callables must declare one.
29
     *
30
     * @return string[]
31
     *
32
     * @throws InvalidFilterException when the first parameter has no type hint
33
     * @throws ReflectionException
34
     */
35
    public static function getAcceptedTypes(FilterInterface $filter, PropertyInterface $property): array
150✔
36
    {
37
        $params = (new ReflectionMethod($filter->getFilter()[0], $filter->getFilter()[1]))->getParameters();
150✔
38

39
        if (empty($params) || $params[0]->getType() === null) {
150✔
40
            throw new InvalidFilterException(
1✔
41
                sprintf(
1✔
42
                    'Filter %s must declare a type hint on its first parameter for property %s in file %s',
1✔
43
                    $filter->getToken(),
1✔
44
                    $property->getName(),
1✔
45
                    $property->getJsonSchema()->getFile(),
1✔
46
                ),
1✔
47
            );
1✔
48
        }
49

50
        $type = $params[0]->getType();
149✔
51

52
        if ($type instanceof ReflectionNamedType) {
149✔
53
            if ($type->getName() === 'mixed') {
125✔
54
                return [];
26✔
55
            }
56

57
            $types = [$type->getName()];
100✔
58

59
            if ($type->allowsNull() && $type->getName() !== 'null') {
100✔
60
                $types[] = 'null';
79✔
61
            }
62

63
            return $types;
100✔
64
        }
65

66
        if ($type instanceof ReflectionUnionType) {
38✔
67
            return array_map(
38✔
68
                static fn(ReflectionNamedType $namedType): string => $namedType->getName(),
38✔
69
                $type->getTypes(),
38✔
70
            );
38✔
71
        }
72

NEW
73
        return [];
×
74
    }
75

76
    /**
77
     * Extract non-null return type names from the transforming filter's callable.
78
     *
79
     * @return string[]
80
     *
81
     * @throws InvalidFilterException when return type is missing or void
82
     * @throws ReflectionException
83
     */
84
    public static function getReturnTypeNames(
75✔
85
        TransformingFilterInterface $filter,
86
        PropertyInterface $property,
87
    ): array {
88
        $returnType = self::reflectReturnType($filter);
75✔
89

90
        if ($returnType === null) {
75✔
91
            throw new InvalidFilterException(
1✔
92
                sprintf(
1✔
93
                    'Transforming filter %s must declare a return type for property %s in file %s',
1✔
94
                    $filter->getToken(),
1✔
95
                    $property->getName(),
1✔
96
                    $property->getJsonSchema()->getFile(),
1✔
97
                ),
1✔
98
            );
1✔
99
        }
100

101
        if ($returnType instanceof ReflectionNamedType) {
74✔
102
            $name = $returnType->getName();
73✔
103

104
            if ($name === 'void' || $name === 'never') {
73✔
105
                throw new InvalidFilterException(
2✔
106
                    sprintf(
2✔
107
                        'Transforming filter %s must not declare a %s return type'
2✔
108
                            . ' for property %s in file %s',
2✔
109
                        $filter->getToken(),
2✔
110
                        $name,
2✔
111
                        $property->getName(),
2✔
112
                        $property->getJsonSchema()->getFile(),
2✔
113
                    ),
2✔
114
                );
2✔
115
            }
116

117
            if ($name === 'null' || $name === 'mixed') {
71✔
118
                return [];
2✔
119
            }
120

121
            return [$name];
70✔
122
        }
123

124
        if ($returnType instanceof ReflectionUnionType) {
1✔
125
            return array_values(array_filter(
1✔
126
                array_map(
1✔
127
                    static fn(ReflectionNamedType $namedType): string => $namedType->getName(),
1✔
128
                    $returnType->getTypes(),
1✔
129
                ),
1✔
130
                static fn(string $name): bool => $name !== 'null',
1✔
131
            ));
1✔
132
        }
133

NEW
134
        return [];
×
135
    }
136

137
    /**
138
     * Whether the transforming filter's return type is nullable.
139
     *
140
     * @throws ReflectionException
141
     */
142
    public static function isReturnNullable(TransformingFilterInterface $filter): bool
72✔
143
    {
144
        $returnType = self::reflectReturnType($filter);
72✔
145

146
        if ($returnType === null) {
72✔
NEW
147
            return false;
×
148
        }
149

150
        if ($returnType instanceof ReflectionNamedType) {
72✔
151
            $name = $returnType->getName();
71✔
152
            // 'mixed' covers all types including null, but is treated as unconstrained
153
            // (not as a nullable specific type), so we report it as non-nullable here.
154
            if ($name === 'null' || $name === 'mixed') {
71✔
155
                return false;
2✔
156
            }
157

158
            return $returnType->allowsNull();
70✔
159
        }
160

161
        if ($returnType instanceof ReflectionUnionType) {
1✔
162
            foreach ($returnType->getTypes() as $namedType) {
1✔
163
                if ($namedType->getName() === 'null') {
1✔
NEW
164
                    return true;
×
165
                }
166
            }
167
        }
168

169
        return false;
1✔
170
    }
171

172
    /**
173
     * @throws ReflectionException
174
     */
175
    private static function reflectReturnType(TransformingFilterInterface $filter): ?\ReflectionType
75✔
176
    {
177
        return (new ReflectionMethod($filter->getFilter()[0], $filter->getFilter()[1]))->getReturnType();
75✔
178
    }
179
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc