TypedCDataWrapper   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 134
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 75
c 1
b 0
f 0
dl 0
loc 134
rs 10
wmc 19

2 Methods

Rating   Name   Duplication   Size   Complexity  
A getElementTypeForCDataArrayParameter() 0 20 3
C createWrapper() 0 109 16
1
<?php
2
3
/**
4
 * This file is part of the sj-i/typed-cdata package.
5
 *
6
 * (c) sji <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace TypedCData;
15
16
use Closure;
17
use FFI\CData;
18
use PhpDocTypeReader\Context\IdentifierContextFactory;
0 ignored issues
show
Bug introduced by
The type PhpDocTypeReader\Context\IdentifierContextFactory was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use PhpDocTypeReader\PhpDocTypeReader;
0 ignored issues
show
Bug introduced by
The type PhpDocTypeReader\PhpDocTypeReader was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
use PhpDocTypeReader\Type\GenericType;
0 ignored issues
show
Bug introduced by
The type PhpDocTypeReader\Type\GenericType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
21
use PhpDocTypeReader\Type\ObjectType;
0 ignored issues
show
Bug introduced by
The type PhpDocTypeReader\Type\ObjectType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
use ReflectionClass;
23
24
final class TypedCDataWrapper
25
{
26
    public function createWrapper(callable $callable): \Closure
27
    {
28
        if (!is_array($callable) or !is_object($callable[0])) {
29
            return Closure::fromCallable($callable);
30
        }
31
        $class = new ReflectionClass(get_class($callable[0]));
32
        $method = $class->getMethod($callable[1]);
33
34
        $input_converters = [];
35
        $outout_converters = [];
36
        foreach ($method->getParameters() as $parameter) {
37
            $class = $parameter->getClass();
38
            if (!is_null($class) and $class->isSubclassOf(TypedCDataInterface::class)) {
39
                $fromCData = $class->getMethod('fromCData');
40
                $input_converters[] = function (CData $cdata) use ($fromCData): TypedCDataInterface {
41
                    /** @var TypedCDataInterface */
42
                    return $fromCData->invoke(null, $cdata);
43
                };
44
                $outout_converters[] = function (TypedCDataInterface $typed_c_data, CData $toCData): CData {
45
                    return $typed_c_data->toCData($toCData);
46
                };
47
            } elseif (!is_null($class) and $class->getName() === TypedCDataArray::class) {
48
                $fromCData = $class->getMethod('fromCData');
49
                $elementType = $this->getElementTypeForCDataArrayParameter($parameter);
50
                $input_converters[] = function (CData $cdata) use ($fromCData, $elementType): TypedCDataArray {
51
                    /** @var TypedCDataArray */
52
                    return $fromCData->invoke(null, $cdata, $elementType);
53
                };
54
                $outout_converters[] = function (TypedCDataArray $typed_c_data_array, CData $toCData): CData {
55
                    return $typed_c_data_array->toCData($toCData);
56
                };
57
            } elseif ($parameter->isPassedByReference()) {
58
                $type = $parameter->getType();
59
                if ($type instanceof \ReflectionNamedType and $type->getName() === 'string') {
60
                    $input_converters[] = function (CData $cdata): string {
61
                        return \FFI::string($cdata);
62
                    };
63
                    $outout_converters[] =
64
                        function (string $value, CData $toCData): CData {
65
                            \FFI::memcpy($toCData, $value, strlen($value));
66
                            return $toCData;
67
                        };
68
                } else {
69
                    $input_converters[] = function (CData $cdata): CData {
70
                        /** @var \FFI\CDataArray $cdata */
71
                        return $cdata[0];
72
                    };
73
                    $outout_converters[] =
74
                        /**
75
                         * @param mixed $value
76
                         * @param \FFI\CDataArray $toCData
77
                         * @return mixed
78
                         */
79
                        function ($value, CData $toCData) {
80
                            /** @psalm-suppress MixedAssignment */
81
                            return $toCData[0] = $value;
82
                        };
83
                }
84
            } else {
85
                $input_converters[] =
86
                    /**
87
                     * @param mixed $data
88
                     * @return mixed
89
                     */
90
                    fn ($data) => $data;
91
                $outout_converters[] =
92
                    /**
93
                     * @param mixed $_1
94
                     * @param mixed $_2
95
                     * @return mixed
96
                     */
97
                    fn ($_1, $_2) => $_1;
0 ignored issues
show
Unused Code introduced by
The parameter $_2 is not used and could be removed. ( Ignorable by Annotation )

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

97
                    fn ($_1, /** @scrutinizer ignore-unused */ $_2) => $_1;

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
98
            }
99
        }
100
101
        return
102
            /** @param mixed $args */
103
            function (...$args) use ($input_converters, $outout_converters, $callable) {
104
                $new_args = [];
105
                foreach ($input_converters as $key => $converter) {
106
                    if (is_null($args[$key])) {
107
                        $new_args[$key] = $args[$key];
108
                        continue;
109
                    }
110
                    /**
111
                     * @psalm-suppress MixedArgument
112
                     * @psalm-suppress MixedAssignment
113
                     */
114
                    $new_args[$key] = $converter($args[$key]);
115
                }
116
                /** @psalm-suppress MixedAssignment */
117
                $result = $callable(...$new_args);
118
                foreach ($outout_converters as $key => $converter) {
119
                    if (is_null($new_args[$key])) {
120
                        $args[$key] = $new_args[$key];
121
                        continue;
122
                    }
123
                    assert(!is_null($args[$key]));
124
                    /**
125
                     * @psalm-suppress PossiblyInvalidArgument
126
                     * @psalm-suppress MixedArgument
127
                     * @psalm-suppress MixedAssignment
128
                     */
129
                    $args[$key] = $converter($new_args[$key], $args[$key]);
130
                }
131
                if ($result instanceof TypedCDataInterface) {
132
                    $result = $result->newCData();
133
                }
134
                return $result;
135
            };
136
    }
137
138
    public function getElementTypeForCDataArrayParameter(\ReflectionParameter $parameter): string
139
    {
140
        $parameter_name = $parameter->getName();
141
        $doc_comment = $parameter->getDeclaringFunction()->getDocComment();
142
        $declaring_file_name = $parameter->getDeclaringFunction()->getFileName();
143
        $identifier_context_factory = new IdentifierContextFactory();
144
        $php_doc_type_reader = new PhpDocTypeReader();
145
        $param_types = $php_doc_type_reader->getParamTypes(
146
            $doc_comment,
147
            $identifier_context_factory->createFromFile($declaring_file_name)
148
        );
149
        $parameter_type = $param_types[$parameter_name];
150
        if (!($parameter_type instanceof GenericType)) {
151
            throw new \LogicException('TypedCDataArray must have a generic type annotation');
152
        }
153
        $element_type = $parameter_type->parameter_types[0];
154
        if (!($element_type instanceof ObjectType)) {
155
            throw new \LogicException('TypedCDataArray must have a object parameter type');
156
        }
157
        return $element_type->class_name;
158
    }
159
}
160