Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.00% covered (success)
88.00%
22 / 25
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
TypeNormalizer
88.00% covered (success)
88.00%
22 / 25
66.67% covered (warning)
66.67%
2 / 3
16.44
0.00% covered (danger)
0.00%
0 / 1
 normalize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolveReflectionType
84.21% covered (success)
84.21%
16 / 19
0.00% covered (danger)
0.00%
0 / 1
12.57
 joinIntersectionType
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Code\Reflection\Support;
16
17/**
18 * Type normalizer class
19 *
20 * Maps gettype()'s legacy type names to the PHP type-hint keyword they should have been. gettype()
21 * returns 'integer'/'boolean'/'double', none of which are valid in a PHP type-hint position — using
22 * them there directly is what produced invalid generated code before this fix.
23 *
24 * @category   Pop
25 * @package    Pop\Code
26 * @author     Nick Sagona, III <dev@noladev.com>
27 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
28 * @license    https://www.popphp.org/license     New BSD License
29 * @version    6.0.0
30 */
31class TypeNormalizer
32{
33
34    /**
35     * Map of gettype() names to PHP type-hint keywords
36     * @var array
37     */
38    protected const MAP = [
39        'integer' => 'int',
40        'boolean' => 'bool',
41        'double'  => 'float',
42        'NULL'    => 'null',
43    ];
44
45    /**
46     * Normalize a gettype()-style name to a PHP type-hint keyword
47     *
48     * @param  string $gettypeName
49     * @return string
50     */
51    public static function normalize(string $gettypeName): string
52    {
53        return self::MAP[$gettypeName] ?? $gettypeName;
54    }
55
56    /**
57     * Resolve a native ReflectionType into a bare, pipe-joined type-hint string
58     *
59     * Handles named types, union types, and intersection types -- including a DNF union whose
60     * members are themselves intersection types (e.g. `(A&B)|C`), which PHP 8.2+ allows. This
61     * consolidates what used to be four separate, partially-duplicated, partially-incomplete
62     * copies of this logic (MethodReflection and FunctionReflection each had their own -- neither
63     * handled intersection types, and MethodReflection's parameter branch and FunctionReflection's
64     * parameter branch didn't handle unions at all, either crashing or silently dropping the type).
65     *
66     * @param  ?\ReflectionType $type
67     * @return string|null
68     */
69    public static function resolveReflectionType(?\ReflectionType $type): string|null
70    {
71        if ($type === null) {
72            return null;
73        }
74
75        if ($type instanceof \ReflectionIntersectionType) {
76            return self::joinIntersectionType($type);
77        }
78
79        if ($type instanceof \ReflectionUnionType) {
80            $names = [];
81            foreach ($type->getTypes() as $memberType) {
82                $names[] = ($memberType instanceof \ReflectionIntersectionType)
83                    ? self::joinIntersectionType($memberType)
84                    : $memberType->getName();
85            }
86            if ($type->allowsNull() && !in_array('null', $names, true)) {
87                $names[] = 'null';
88            }
89            return implode('|', $names);
90        }
91
92        if ($type instanceof \ReflectionNamedType) {
93            $name = $type->getName();
94            if (($name !== 'mixed') && ($name !== 'null') && $type->allowsNull()) {
95                $name .= '|null';
96            }
97            return $name;
98        }
99
100        return null;
101    }
102
103    /**
104     * Join an intersection type's member names with '&'
105     *
106     * @param  \ReflectionIntersectionType $type
107     * @return string
108     */
109    protected static function joinIntersectionType(\ReflectionIntersectionType $type): string
110    {
111        $names = [];
112        foreach ($type->getTypes() as $memberType) {
113            if ($memberType instanceof \ReflectionNamedType) {
114                $names[] = $memberType->getName();
115            }
116        }
117        return implode('&', $names);
118    }
119
120}