Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
6.33% covered (danger)
6.33%
5 / 79
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
FfiFaker
6.33% covered (danger)
6.33%
5 / 79
16.67% covered (danger)
16.67%
1 / 6
348.76
0.00% covered (danger)
0.00%
0 / 1
 getFfi
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 supports
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 createRandomIntArray
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 fillIntArray
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 fillCharArray
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 fakeFor
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2
3namespace Apie\Faker\Fakers;
4
5use Apie\Faker\Interfaces\ApieClassFaker;
6use Faker\Generator;
7use FFI;
8use FFI\CData;
9use FFI\CType;
10use ReflectionClass;
11
12/** @implements ApieClassFaker<CData|CType> */
13class FfiFaker implements ApieClassFaker
14{
15    private static FFI $instance;
16
17    private static function getFfi(): FFI
18    {
19        if (!isset(self::$instance)) {
20            self::$instance = FFI::cdef();
21        }
22
23        return self::$instance;
24    }
25
26    public function supports(ReflectionClass $class): bool
27    {
28        return in_array(
29            $class->name,
30            [CData::class, CType::class],
31            true
32        );
33    }
34
35    /**
36     * @return array<array-key, int>
37     */
38    private static function createRandomIntArray(Generator $faker): array
39    {
40        $numbers = [];
41
42        for ($i = 0; $i < 10; $i++) {
43            $numbers[] = $faker->numberBetween(1, 100);
44        }
45
46        return $numbers;
47    }
48
49    /**
50     * @param array<array-key, int> $values
51     */
52    private static function fillIntArray(
53        CData $data,
54        array $values
55    ): void {
56        foreach ($values as $index => $value) {
57            $data[$index] = $value;
58        }
59    }
60
61    private static function fillCharArray(
62        CData $data,
63        string $value
64    ): void {
65        $length = min(FFI::sizeof($data), strlen($value));
66
67        for ($i = 0; $i < $length; $i++) {
68            $data[$i] = $value[$i];
69        }
70    }
71
72    public function fakeFor(
73        Generator $generator,
74        ReflectionClass $class
75    ): object {
76        $typedef = $generator->randomElement([
77            'int',
78            'int[10]',
79            'char[42]',
80            'unsigned int',
81            'char',
82            'unsigned char',
83            'bool',
84            'float',
85            'double',
86        ]);
87
88        $ffi = self::getFfi();
89        $type = $ffi->type($typedef);
90
91        // CType is already the requested object.
92        if ($class->name === CType::class) {
93            return $type;
94        }
95
96        $res = $ffi->new($type);
97
98        switch ($typedef) {
99            case 'int':
100                // @phpstan-ignore-next-line property.notFound
101                $res->cdata = $generator->numberBetween(
102                    -2147483647,
103                    2147483647
104                );
105                break;
106
107            case 'int[10]':
108                self::fillIntArray(
109                    $res,
110                    self::createRandomIntArray($generator)
111                );
112                break;
113
114            case 'char[42]':
115                self::fillCharArray(
116                    $res,
117                    str_shuffle(
118                        'abcdefghijklmnopqrstuvwxyz'
119                        . 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
120                    )
121                );
122                break;
123
124            case 'unsigned int':
125                // @phpstan-ignore-next-line property.notFound
126                $res->cdata = $generator->numberBetween(
127                    0,
128                    2147483647
129                );
130                break;
131
132            case 'char':
133                // FFI expects a one-character string for `char`.
134                // @phpstan-ignore-next-line property.notFound
135                $res->cdata = $generator->randomElement(
136                    str_split(
137                        'abcdefghijklmnopqrstuvwxyz'
138                        . 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
139                    )
140                );
141                break;
142
143            case 'unsigned char':
144                // FFI expects an integer for `unsigned char`.
145                // @phpstan-ignore-next-line property.notFound
146                $res->cdata = $generator->numberBetween(0, 255);
147                break;
148
149            case 'bool':
150                // @phpstan-ignore-next-line property.notFound
151                $res->cdata = $generator->boolean();
152                break;
153
154            case 'float':
155            case 'double':
156                // @phpstan-ignore-next-line property.notFound
157                $res->cdata = $generator->randomFloat();
158                break;
159        }
160
161        return $res;
162    }
163}