Skip to content

Commit e480c66

Browse files
committed
Add registry support
1 parent c5bf40c commit e480c66

File tree

6 files changed

+211
-3
lines changed

6 files changed

+211
-3
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
],
2020
"require": {
2121
"php": ">=7.4",
22+
"symfony/polyfill-php80": "^1.23",
2223
"ext-ffi": "*"
2324
},
2425
"autoload": {

resources/.phpstorm.meta.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace PHPSTORM_META {
4+
5+
override(\FFI\Proxy\Registry::get(), type(0));
6+
7+
registerArgumentsSet('ProxyCType',
8+
'void *',
9+
10+
'bool',
11+
12+
'float',
13+
'double',
14+
'long double',
15+
16+
'char',
17+
'signed char',
18+
'unsigned char',
19+
'int',
20+
'signed int',
21+
'unsigned int',
22+
'long',
23+
'signed long',
24+
'unsigned long',
25+
'long long',
26+
'signed long long',
27+
'unsigned long long',
28+
29+
'intptr_t',
30+
'uintptr_t',
31+
'size_t',
32+
'ssize_t',
33+
'ptrdiff_t',
34+
'off_t',
35+
'va_list',
36+
'__builtin_va_list',
37+
'__gnuc_va_list',
38+
39+
// stdint.h
40+
'int8_t',
41+
'uint8_t',
42+
'int16_t',
43+
'uint16_t',
44+
'int32_t',
45+
'uint32_t',
46+
'int64_t',
47+
'uint64_t',
48+
);
49+
50+
expectedArguments(\FFI\Proxy\ApiInterface::new(), 0, argumentsSet('ProxyCType'));
51+
expectedArguments(\FFI\Proxy\ApiInterface::cast(), 0, argumentsSet('ProxyCType'));
52+
expectedArguments(\FFI\Proxy\ApiInterface::type(), 0, argumentsSet('ProxyCType'));
53+
54+
expectedArguments(\FFI\Proxy\ApiAwareTrait::new(), 0, argumentsSet('ProxyCType'));
55+
expectedArguments(\FFI\Proxy\ApiAwareTrait::cast(), 0, argumentsSet('ProxyCType'));
56+
expectedArguments(\FFI\Proxy\ApiAwareTrait::type(), 0, argumentsSet('ProxyCType'));
57+
58+
expectedArguments(\FFI\Proxy\Proxy::new(), 0, argumentsSet('ProxyCType'));
59+
expectedArguments(\FFI\Proxy\Proxy::cast(), 0, argumentsSet('ProxyCType'));
60+
expectedArguments(\FFI\Proxy\Proxy::type(), 0, argumentsSet('ProxyCType'));
61+
62+
}

src/ApiAwareTrait.php

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,40 +22,65 @@
2222
*/
2323
trait ApiAwareTrait
2424
{
25+
/**
26+
* @param non-empty-string|CType $type
27+
* @return bool
28+
*/
29+
private function assertTypeArgument($type): bool
30+
{
31+
if (\is_string($type)) {
32+
return $type !== '';
33+
}
34+
35+
if (!$type instanceof CType) {
36+
throw new \TypeError(
37+
\sprintf('Type MUST be string|\FFI\CType, but %s passed', \get_debug_type($type))
38+
);
39+
}
40+
41+
return true;
42+
}
43+
2544
/**
2645
* @see ApiInterface::new()
2746
* @throws ParserException
2847
*
29-
* @psalm-param string|CType $type
48+
* @psalm-param CType|non-empty-string $type
3049
* @psalm-param bool $owned
3150
* @psalm-param bool $persistent
3251
* @psalm-return CData|null
3352
*/
3453
public function new($type, bool $owned = true, bool $persistent = false): ?CData
3554
{
55+
assert($this->assertTypeArgument($type));
56+
3657
return $this->ffi->new($type, $owned, $persistent);
3758
}
3859

3960
/**
4061
* @see ApiInterface::cast()
4162
*
42-
* @psalm-param CType|string $type
63+
* @psalm-param CType|non-empty-string $type
4364
* @psalm-param CData|int|float|bool|null $ptr
4465
* @psalm-return CData|null
4566
*/
4667
public function cast($type, $ptr): ?CData
4768
{
69+
assert($this->assertTypeArgument($type));
70+
4871
return $this->ffi->cast($type, $ptr);
4972
}
5073

5174
/**
5275
* @see ApiInterface::type()
5376
*
54-
* @psalm-param string $type
77+
* @psalm-param non-empty-string $type
5578
* @psalm-return CType|null
5679
*/
5780
public function type(string $type): ?CType
5881
{
82+
assert($type !== '', 'Type name MUST be not empty');
83+
5984
return $this->ffi->type($type);
6085
}
6186
}

src/Proxy.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ public function __construct(\FFI $ffi = null)
3333
if ($ffi !== null) {
3434
$this->ffi = $ffi;
3535
}
36+
37+
if (! Registry::has(static::class)) {
38+
Registry::register($this);
39+
}
3640
}
3741

3842
/**

src/ProxyAwareTrait.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ trait ProxyAwareTrait
2323
*/
2424
public function __call(string $method, array $args)
2525
{
26+
assert($method !== '', 'Method name MUST not be empty');
27+
2628
return $this->ffi->$method(...$args);
2729
}
2830

@@ -32,6 +34,8 @@ public function __call(string $method, array $args)
3234
*/
3335
public function __get(string $name)
3436
{
37+
assert($name !== '', 'Property name MUST not be empty');
38+
3539
return $this->ffi->$name;
3640
}
3741

@@ -42,6 +46,8 @@ public function __get(string $name)
4246
*/
4347
public function __set(string $name, $value): void
4448
{
49+
assert($name !== '', 'Property name MUST not be empty');
50+
4551
$this->ffi->$name = $value;
4652
}
4753

@@ -51,6 +57,8 @@ public function __set(string $name, $value): void
5157
*/
5258
public function __isset(string $name): bool
5359
{
60+
assert($name !== '', 'Property name MUST not be empty');
61+
5462
return isset($this->ffi->$name);
5563
}
5664

@@ -60,6 +68,8 @@ public function __isset(string $name): bool
6068
*/
6169
public function __unset(string $name): void
6270
{
71+
assert($name !== '', 'Property name MUST not be empty');
72+
6373
unset($this->ffi->$name);
6474
}
6575
}

src/Registry.php

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
/**
4+
* This file is part of FFI package.
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace FFI\Proxy;
13+
14+
/**
15+
* @psalm-type ApiName = class-string<ApiInterface>|non-empty-string
16+
* @psalm-type ApiImplementation = ApiInterface|\FFI
17+
*/
18+
final class Registry
19+
{
20+
/**
21+
* @var string
22+
*/
23+
private const ERROR_NOT_FOUND = 'The FFI API implementation named "%s" was not registered';
24+
25+
/**
26+
* @var string
27+
*/
28+
private const ERROR_EMPTY_NAME = 'The FFI API implementation name MUST not be empty';
29+
30+
/**
31+
* List of FFI API clients
32+
*
33+
* @var array<ApiName, ApiImplementation>
34+
*/
35+
private static array $implementations = [];
36+
37+
/**
38+
* Returns FFI API instance by its name or creates a new FFI API instance.
39+
*
40+
* @param ApiName $name
41+
* @return ApiImplementation
42+
* @throws \OutOfRangeException
43+
*/
44+
public static function get(string $name): object
45+
{
46+
assert($name !== '', self::ERROR_EMPTY_NAME);
47+
48+
if (isset(self::$implementations[$name])) {
49+
return self::$implementations[$name];
50+
}
51+
52+
if (\is_subclass_of($name, ApiInterface::class, true)) {
53+
return self::$implementations[$name] = new $name();
54+
}
55+
56+
throw new \OutOfRangeException(\sprintf(self::ERROR_NOT_FOUND, $name));
57+
}
58+
59+
/**
60+
* Returns {@see true} if FFI API instance is defined or {@see false} otherwise.
61+
*
62+
* @param ApiName $name
63+
* @return bool
64+
*/
65+
public static function has(string $name): bool
66+
{
67+
assert($name !== '', self::ERROR_EMPTY_NAME);
68+
69+
return isset(self::$implementations[$name]);
70+
}
71+
72+
/**
73+
* Clean registry storage.
74+
*
75+
* @return void
76+
*/
77+
public static function dispose(): void
78+
{
79+
self::$implementations = [];
80+
}
81+
82+
/**
83+
* Register proxy implementation by its name.
84+
*
85+
* @param ApiInterface $api
86+
* @return void
87+
*/
88+
public static function register(ApiInterface $api): void
89+
{
90+
self::$implementations[\get_class($api)] = $api;
91+
}
92+
93+
/**
94+
* Register FFI API implementation bu custom name.
95+
*
96+
* @param non-empty-string $name
97+
* @param ApiImplementation $api
98+
* @return void
99+
*/
100+
public static function registerAs(string $name, $api): void
101+
{
102+
assert($name !== '', self::ERROR_EMPTY_NAME);
103+
104+
self::$implementations[$name] = $api;
105+
}
106+
}

0 commit comments

Comments
 (0)