diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 46c756bf7..2903e5c95 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -82,6 +82,8 @@ jobs: run: composer update -o - name: Run Analyse run: composer analyse src + - name: Run Type Tests + run: composer type-testing - name: Setup Services run: ./.travis/setup.services.sh - name: Run Test Cases diff --git a/composer.json b/composer.json index a5405fb24..277801581 100644 --- a/composer.json +++ b/composer.json @@ -334,6 +334,7 @@ ], "test:lint": "@php vendor/bin/php-cs-fixer fix --dry-run --diff --ansi", "test:types": "@php vendor/bin/pest --type-coverage", - "test:unit": "@php vendor/bin/pest" + "test:unit": "@php vendor/bin/pest", + "type-testing": "phpstan analyze --configuration=\"phpstan.types.neon.dist\" --memory-limit=-1" } } diff --git a/phpstan.types.neon.dist b/phpstan.types.neon.dist new file mode 100644 index 000000000..9a0996e02 --- /dev/null +++ b/phpstan.types.neon.dist @@ -0,0 +1,12 @@ +parameters: + level: max + paths: + - types + scanFiles: + - src/macros/output/Hyperf/Collection/Arr.php + - src/macros/output/Hyperf/Collection/Collection.php + - src/macros/output/Hyperf/Collection/LazyCollection.php + - src/macros/output/Hyperf/HttpServer/Contract/RequestInterface.php + - src/macros/output/Hyperf/HttpServer/Request.php + - src/macros/output/Hyperf/Stringable/Str.php + - src/macros/output/Hyperf/Stringable/Stringable.php diff --git a/src/macros/output/Hyperf/Collection/Collection.php b/src/macros/output/Hyperf/Collection/Collection.php index 76e159a35..3d3fe598f 100644 --- a/src/macros/output/Hyperf/Collection/Collection.php +++ b/src/macros/output/Hyperf/Collection/Collection.php @@ -13,6 +13,15 @@ class Collection { + /** + * Create a new collection. + * + * @param mixed $items + */ + public function __construct($items = []) + { + } + /** * Determine if the collection contains a single element. * @deprecated since v3.1, use `containsOneItem` instead, will be removed in v3.2. diff --git a/src/macros/output/Hyperf/Collection/LazyCollection.php b/src/macros/output/Hyperf/Collection/LazyCollection.php index e5486bcbc..0fd18ad33 100644 --- a/src/macros/output/Hyperf/Collection/LazyCollection.php +++ b/src/macros/output/Hyperf/Collection/LazyCollection.php @@ -13,6 +13,24 @@ class LazyCollection { + /** + * Create a new lazy collection instance. + * + * @param mixed $items + * @return static + */ + public static function make($items = []) + { + } + + /** + * Determine if the collection contains a single element. + * @return bool + */ + public function isSingle() + { + } + /** * Collapse the collection of items into a single array while preserving its keys. * diff --git a/src/macros/output/Hyperf/HttpServer/Request.php b/src/macros/output/Hyperf/HttpServer/Request.php new file mode 100644 index 000000000..82cb5ecb9 --- /dev/null +++ b/src/macros/output/Hyperf/HttpServer/Request.php @@ -0,0 +1,302 @@ + $enumClass + * @return null|TEnum + */ + public function enum($key, $enumClass) + { + } + + /** + * Determine if the request contains a given input item key. + * + * @param array|string $key + */ + public function exists($key): bool + { + } + + /** + * Retrieve input from the request as a Stringable instance. + * + * @param string $key + * @param mixed $default + * @return \Hyperf\Stringable\Stringable + */ + public function str($key, $default = null) + { + } + + /** + * Retrieve input from the request as a Stringable instance. + * + * @param string $key + * @param mixed $default + * @return \Hyperf\Stringable\Stringable + */ + public function string($key, $default = null) + { + } + + /** + * Retrieve input as an integer value. + * + * @param string $key + * @param int $default + */ + public function integer($key, $default = 0): int + { + } + + public function validate(array $rules, array $messages = [], array $customAttributes = []): array + { + } + + public function validateWithBag(string $errorBag, array $rules, array $messages = [], array $customAttributes = []): array + { + } +} diff --git a/src/macros/output/Hyperf/Stringable/Str.php b/src/macros/output/Hyperf/Stringable/Str.php index dbcc9946d..e1d8b6d30 100644 --- a/src/macros/output/Hyperf/Stringable/Str.php +++ b/src/macros/output/Hyperf/Stringable/Str.php @@ -13,17 +13,27 @@ class Str { + /** + * Return the remainder of a string after the first occurrence of a given value. + * + * @param mixed $string + * @return Stringable + */ + public static function of($string) + { + } + /** * Set the callable that will be used to generate UUIDs. */ - public static function createUuidsUsing(?callable $factory = null) + public static function createUuidsUsing(?callable $factory = null): void { } /** * Indicate that UUIDs should be created normally and not using a custom factory. */ - public static function createUuidsNormally() + public static function createUuidsNormally(): void { } @@ -67,4 +77,26 @@ public static function inlineMarkdown($string, array $options = []) public static function transliterate($string, $unknown = '?', $strict = false) { } + + /** + * Determine if a given string doesn't end with a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function doesntEndWith($haystack, $needles) + { + } + + /** + * Determine if a given string doesn't start with a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function doesntStartWith($haystack, $needles) + { + } } diff --git a/src/macros/output/Hyperf/Stringable/Stringable.php b/src/macros/output/Hyperf/Stringable/Stringable.php index d95631f80..3f2c4fe99 100644 --- a/src/macros/output/Hyperf/Stringable/Stringable.php +++ b/src/macros/output/Hyperf/Stringable/Stringable.php @@ -86,4 +86,24 @@ public function toHtmlString() public function whenIsAscii($callback, $default = null) { } + + /** + * Determine if a given string doesn't end with a given substring. + * + * @param string|array $needles + * @return bool + */ + public function doesntEndWith($needles) + { + } + + /** + * Determine if a given string doesn't start with a given substring. + * + * @param string|array $needles + * @return bool + */ + public function doesntStartWith($needles) + { + } } diff --git a/src/sentry/src/Factory/ClientBuilderFactory.php b/src/sentry/src/Factory/ClientBuilderFactory.php index 71cd4ac05..8a6f5839d 100644 --- a/src/sentry/src/Factory/ClientBuilderFactory.php +++ b/src/sentry/src/Factory/ClientBuilderFactory.php @@ -36,7 +36,7 @@ class ClientBuilderFactory 'tracing', ]; - public function __invoke(ContainerInterface $container) + public function __invoke(ContainerInterface $container): ClientBuilder { $userConfig = $container->get(ConfigInterface::class)->get('sentry', []); $userConfig['enable_tracing'] ??= true; diff --git a/src/sentry/src/Factory/HubFactory.php b/src/sentry/src/Factory/HubFactory.php index 480229104..11dd34454 100644 --- a/src/sentry/src/Factory/HubFactory.php +++ b/src/sentry/src/Factory/HubFactory.php @@ -19,6 +19,7 @@ use Sentry\ClientBuilder; use Sentry\Integration as SdkIntegration; use Sentry\State\Hub; +use Sentry\State\HubInterface; use function Hyperf\Support\make; @@ -28,7 +29,7 @@ */ class HubFactory { - public function __invoke(ContainerInterface $container) + public function __invoke(ContainerInterface $container): HubInterface { $clientBuilder = $container->get(ClientBuilder::class); $options = $clientBuilder->getOptions(); diff --git a/types/Cache/Facade.php b/types/Cache/Facade.php new file mode 100644 index 000000000..7638c24f6 --- /dev/null +++ b/types/Cache/Facade.php @@ -0,0 +1,92 @@ + 'value1', 'key2' => 'value2'])); +assertType('bool', Cache::putMany(['key1' => 'value1'], 60)); + +// Cache::add() tests +assertType('bool', Cache::add('key', 'value')); +assertType('bool', Cache::add('key', 'value', 60)); + +// Cache::forever() tests +assertType('bool', Cache::forever('key', 'value')); + +// Cache::forget() tests +assertType('bool', Cache::forget('key')); + +// Cache::flush() tests +assertType('bool', Cache::flush()); + +// Cache::increment() tests +assertType('bool|int', Cache::increment('key')); +assertType('bool|int', Cache::increment('key', 5)); + +// Cache::decrement() tests +assertType('bool|int', Cache::decrement('key')); +assertType('bool|int', Cache::decrement('key', 5)); + +// Cache::remember() tests +assertType('string', Cache::remember('key', 60, fn () => 'value')); +assertType('int', Cache::remember('key', 60, fn () => 123)); +assertType('array{}', Cache::remember('key', 60, fn () => [])); + +// Cache::rememberForever() tests +assertType('string', Cache::rememberForever('key', fn () => 'value')); +assertType('int', Cache::rememberForever('key', fn () => 123)); + +// Cache::sear() tests +assertType('string', Cache::sear('key', fn () => 'value')); +assertType('int', Cache::sear('key', fn () => 123)); + +// Cache::flexible() tests +assertType('string', Cache::flexible('key', [60, 120], fn () => 'value')); +assertType('int', Cache::flexible('key', [60, 120], fn () => 123)); +assertType('array{}', Cache::flexible('key', [60, 120], fn () => [])); diff --git a/types/Cache/Manager.php b/types/Cache/Manager.php new file mode 100644 index 000000000..706f20e15 --- /dev/null +++ b/types/Cache/Manager.php @@ -0,0 +1,36 @@ +store()); +assertType('FriendsOfHyperf\Cache\Contract\Repository', $manager->store('default')); +assertType('FriendsOfHyperf\Cache\Contract\Repository', $manager->store('redis')); + +// CacheManager::driver() tests +assertType('FriendsOfHyperf\Cache\Contract\Repository', $manager->driver()); +assertType('FriendsOfHyperf\Cache\Contract\Repository', $manager->driver('default')); + +// CacheManager::resolve() tests +assertType('FriendsOfHyperf\Cache\Contract\Repository', $manager->resolve('default')); + +// Factory::store() tests +assertType('FriendsOfHyperf\Cache\Contract\Repository', $factory->store()); +assertType('FriendsOfHyperf\Cache\Contract\Repository', $factory->store('default')); diff --git a/types/Cache/Repository.php b/types/Cache/Repository.php new file mode 100644 index 000000000..0ac8ee201 --- /dev/null +++ b/types/Cache/Repository.php @@ -0,0 +1,116 @@ +has('key')); + +// Repository::missing() tests +assertType('bool', $repository->missing('key')); + +// Repository::get() tests +assertType('mixed', $repository->get('key')); +assertType('mixed', $repository->get('key', 'default')); + +// Repository::set() tests +assertType('bool', $repository->set('key', 'value')); +assertType('bool', $repository->set('key', 'value', 60)); + +// Repository::delete() tests +assertType('bool', $repository->delete('key')); + +// Repository::clear() tests +assertType('bool', $repository->clear()); + +// Repository::many() tests +assertType('iterable', $repository->many(['key1', 'key2'])); + +// Repository::getMultiple() tests +// assertType('iterable', $repository->getMultiple(['key1', 'key2'])); +// assertType('iterable', $repository->getMultiple(['key1', 'key2'], 'default')); + +// Repository::setMultiple() tests +// assertType('bool', $repository->setMultiple(['key1' => 'value1', 'key2' => 'value2'])); +// assertType('bool', $repository->setMultiple(['key1' => 'value1'], 60)); + +// Repository::deleteMultiple() tests +assertType('bool', $repository->deleteMultiple(['key1', 'key2'])); + +// Repository::pull() tests +assertType('mixed', $repository->pull('key')); +assertType('string', $repository->pull('key', 'default')); + +// Repository::put() tests +assertType('bool', $repository->put('key', 'value')); +assertType('bool', $repository->put('key', 'value', 60)); +assertType('bool', $repository->put(['key1' => 'value1', 'key2' => 'value2'], 60)); + +// Repository::putMany() tests +assertType('bool', $repository->putMany(['key1' => 'value1', 'key2' => 'value2'])); +assertType('bool', $repository->putMany(['key1' => 'value1'], 60)); + +// Repository::add() tests +assertType('bool', $repository->add('key', 'value')); +assertType('bool', $repository->add('key', 'value', 60)); + +// Repository::forever() tests +assertType('bool', $repository->forever('key', 'value')); + +// Repository::forget() tests +assertType('bool', $repository->forget('key')); + +// Repository::flush() tests +assertType('bool', $repository->flush()); + +// Repository::increment() tests +assertType('bool|int', $repository->increment('key')); +assertType('bool|int', $repository->increment('key', 5)); + +// Repository::decrement() tests +assertType('bool|int', $repository->decrement('key')); +assertType('bool|int', $repository->decrement('key', 5)); + +// Repository::remember() tests +assertType('string', $repository->remember('key', 60, fn () => 'value')); +assertType('int', $repository->remember('key', 60, fn () => 123)); +assertType('array{}', $repository->remember('key', 60, fn () => [])); + +// Repository::rememberForever() tests +assertType('string', $repository->rememberForever('key', fn () => 'value')); +assertType('int', $repository->rememberForever('key', fn () => 123)); + +// Repository::sear() tests +assertType('string', $repository->sear('key', fn () => 'value')); +assertType('int', $repository->sear('key', fn () => 123)); + +// Repository::flexible() tests +assertType('string', $repository->flexible('key', [60, 120], fn () => 'value')); +assertType('int', $repository->flexible('key', [60, 120], fn () => 123)); +assertType('array{}', $repository->flexible('key', [60, 120], fn () => [])); + +// Repository::getDriver() tests +assertType('Hyperf\Cache\Driver\DriverInterface', $repository->getDriver()); + +// Repository::getStore() tests +assertType('Hyperf\Cache\Driver\DriverInterface', $repository->getStore()); + +// CacheRepository specific tests +assertType('Hyperf\Cache\Driver\DriverInterface', $cacheRepository->getDriver()); +assertType('Hyperf\Cache\Driver\DriverInterface', $cacheRepository->getStore()); diff --git a/types/Helpers/Functions.php b/types/Helpers/Functions.php new file mode 100644 index 000000000..61de7da8e --- /dev/null +++ b/types/Helpers/Functions.php @@ -0,0 +1,198 @@ + 'test')); + +// base_path() tests +assertType('string', base_path()); +assertType('string', base_path('foo/bar')); + +// blank() tests +assertType('bool', blank(null)); +assertType('bool', blank('')); +assertType('bool', blank('test')); +assertType('bool', blank([])); + +// cache() tests +assertType('mixed', cache()); +assertType('mixed', cache('key')); + +// class_namespace() tests +assertType('string', class_namespace(Fluent::class)); +assertType('string', class_namespace(new Fluent())); + +// di() tests +assertType('Psr\Container\ContainerInterface', di()); +assertType('mixed', di(Fluent::class)); + +// enum_value() tests +assertType('mixed', enum_value('test')); +assertType('mixed', enum_value('test', 'default')); + +// event() tests +$testEvent = new class {}; +assertType('AnonymousClass7af6ae4c28737d2b2877adcdeb4da107', event($testEvent)); + +// filled() tests +assertType('bool', filled(null)); +assertType('bool', filled('test')); + +// fluent() tests +assertType('Hyperf\Support\Fluent', fluent([])); +assertType('Hyperf\Support\Fluent', fluent(['key' => 'value'])); + +// get_client_ip() tests +assertType('string', get_client_ip()); + +// literal() tests +assertType('stdClass', literal()); +assertType('stdClass', literal(key: 'value')); + +// logger() tests +assertType('Psr\Log\LoggerInterface', logger()); +assertType('mixed', logger('test message')); + +// logs() tests +assertType('Psr\Log\LoggerInterface', logs()); +assertType('Psr\Log\LoggerInterface', logs('custom')); + +// object_get() tests +$obj = (object) ['key' => 'value']; +assertType('object{key: string}&stdClass', object_get($obj)); +assertType('mixed', object_get($obj, 'key')); + +// preg_replace_array() tests +assertType('string', preg_replace_array('/test/', ['replacement'], 'test string')); + +// request() tests +assertType('Hyperf\HttpServer\Contract\RequestInterface', request()); +assertType('mixed', request('key')); +assertType('array', request(['key1', 'key2'])); + +// rescue() tests +assertType('string|null', rescue(fn () => 'result')); +assertType('string', rescue(fn () => throw new Exception(), 'fallback')); + +// resolve() tests +assertType('mixed', resolve(Fluent::class)); +assertType('Closure', resolve(fn () => 'test')); + +// response() tests +assertType('Hyperf\HttpServer\Contract\ResponseInterface|Psr\Http\Message\ResponseInterface', response()); +assertType('Hyperf\HttpServer\Contract\ResponseInterface|Psr\Http\Message\ResponseInterface', response('content')); + +// throw_if() tests +assertType('bool', throw_if(false, 'Exception')); + +// throw_unless() tests +assertType('bool', throw_unless(true, 'Exception')); + +// transform() tests +assertType('string', transform('value', fn ($v) => $v)); +assertType('null', transform(null, fn ($v) => $v)); + +// validator() tests +assertType('Hyperf\Contract\ValidatorInterface|Hyperf\Validation\Contract\ValidatorFactoryInterface', validator()); +assertType('Hyperf\Contract\ValidatorInterface|Hyperf\Validation\Contract\ValidatorFactoryInterface', validator([], [])); + +// when() tests +assertType('mixed', when(true, 'value')); +assertType('mixed', when(false, 'value', 'default')); + +// cookie() tests +assertType('Hyperf\HttpMessage\Cookie\CookieJarInterface', cookie()); +assertType('Hyperf\HttpMessage\Cookie\Cookie', cookie('name', 'value')); + +// dispatch() tests - returns bool +// Note: dispatch() has complex return types based on job type, testing the common case +assertType('bool', dispatch(new class implements Hyperf\AsyncQueue\JobInterface { + public function handle(): void + { + } + + public function fail(Throwable $e): void + { + } + + public function getMaxAttempts(): int + { + return 0; + } + + public function setMaxAttempts(int $maxAttempts): static + { + return $this; + } +})); + +// environment() tests +assertType('bool|FriendsOfHyperf\Support\Environment', environment()); +assertType('bool|FriendsOfHyperf\Support\Environment', environment('production')); + +// info() tests +assertType('mixed', info('message')); + +// now() tests +assertType('Carbon\Carbon', now()); +assertType('Carbon\Carbon', now('UTC')); + +// session() tests +assertType('Hyperf\Contract\SessionInterface', session()); +assertType('mixed', session('key')); + +// today() tests +assertType('Carbon\Carbon', today()); +assertType('Carbon\Carbon', today('UTC')); + +// call() tests +assertType('int', call('command:name')); +assertType('int', call('command:name', ['arg' => 'value'])); diff --git a/types/Macros/Arr.php b/types/Macros/Arr.php new file mode 100644 index 000000000..6f33f2416 --- /dev/null +++ b/types/Macros/Arr.php @@ -0,0 +1,58 @@ + 'value'])); +assertType('bool', Arr::arrayable('string')); + +// Arr::array() tests +assertType('array', Arr::array(['nested' => ['key' => 'value']], 'nested')); +assertType('array', Arr::array(['data' => [1, 2, 3]], 'data', [])); + +// Arr::boolean() tests +assertType('bool', Arr::boolean(['is_active' => true], 'is_active')); +assertType('bool', Arr::boolean(['flag' => false], 'flag', false)); + +// Arr::every() tests +assertType('bool', Arr::every([1, 2, 3], fn ($value) => $value > 0)); +assertType('bool', Arr::every(['a', 'b', 'c'], fn ($value) => is_string($value))); + +// Arr::float() tests +assertType('float', Arr::float(['price' => 10.5], 'price')); +assertType('float', Arr::float(['amount' => 99.99], 'amount', 0.0)); + +// Arr::from() tests +assertType('array{}', Arr::from([])); +assertType('array', Arr::from(['key' => 'value'])); + +// Arr::hasAll() tests +assertType('bool', Arr::hasAll(['a' => 1, 'b' => 2], ['a', 'b'])); +assertType('bool', Arr::hasAll(['x' => 1], 'x')); + +// Arr::integer() tests +assertType('int', Arr::integer(['count' => 10], 'count')); +assertType('int', Arr::integer(['total' => 100], 'total', 0)); + +// Arr::string() tests +assertType('string', Arr::string(['name' => 'test'], 'name')); +assertType('string', Arr::string(['title' => 'value'], 'title', 'default')); + +// Arr::some() tests +assertType('bool', Arr::some([1, 2, 3], fn ($value) => $value > 2)); +assertType('bool', Arr::some(['a', 'b', 'c'], fn ($value) => $value === 'b')); + +// Arr::sortByMany() tests +assertType('array', Arr::sortByMany([['name' => 'John', 'age' => 30]], [['name', true]])); +assertType('array', Arr::sortByMany([['x' => 1], ['x' => 2]], [])); diff --git a/types/Macros/Collection.php b/types/Macros/Collection.php new file mode 100644 index 000000000..0b21474b5 --- /dev/null +++ b/types/Macros/Collection.php @@ -0,0 +1,22 @@ +isSingle()); +assertType('bool', (new Collection([1, 2]))->isSingle()); +assertType('bool', (new Collection([]))->isSingle()); + +// Collection::collapseWithKeys() tests +assertType('Hyperf\Collection\Collection', (new Collection([['a' => 1], ['b' => 2]]))->collapseWithKeys()); +assertType('Hyperf\Collection\Collection', (new Collection([]))->collapseWithKeys()); diff --git a/types/Macros/LazyCollection.php b/types/Macros/LazyCollection.php new file mode 100644 index 000000000..f96fd30b4 --- /dev/null +++ b/types/Macros/LazyCollection.php @@ -0,0 +1,22 @@ +isSingle()); +assertType('bool', LazyCollection::make([1, 2])->isSingle()); +assertType('bool', LazyCollection::make([])->isSingle()); + +// LazyCollection::collapseWithKeys() tests +assertType('Hyperf\Collection\LazyCollection', LazyCollection::make([['a' => 1], ['b' => 2]])->collapseWithKeys()); +assertType('Hyperf\Collection\LazyCollection', LazyCollection::make([])->collapseWithKeys()); diff --git a/types/Macros/Request.php b/types/Macros/Request.php new file mode 100644 index 000000000..3b3b98bbb --- /dev/null +++ b/types/Macros/Request.php @@ -0,0 +1,109 @@ +boolean('is_active')); +assertType('bool', $request->boolean('flag', false)); + +// Request::collect() tests +assertType('Hyperf\Collection\Collection', $request->collect()); +assertType('Hyperf\Collection\Collection', $request->collect('key')); +assertType('Hyperf\Collection\Collection', $request->collect(['key1', 'key2'])); + +// Request::date() tests +assertType('Carbon\Carbon|null', $request->date('created_at')); +assertType('Carbon\Carbon|null', $request->date('updated_at', 'Y-m-d')); +assertType('Carbon\Carbon|null', $request->date('deleted_at', 'Y-m-d H:i:s', 'UTC')); + +// Request::enum() tests +assertType('BackedEnum|null', $request->enum('status', BackedEnum::class)); + +// Request::exists() tests +assertType('bool', $request->exists('key')); + +// Request::filled() tests +assertType('bool', $request->filled('name')); +assertType('bool', $request->filled(['name', 'email'])); + +// Request::float() tests +assertType('float', $request->float('price')); +assertType('float', $request->float('amount', 0.0)); + +// Request::fluent() tests +assertType('Hyperf\Support\Fluent', $request->fluent()); +assertType('Hyperf\Support\Fluent', $request->fluent('key')); +assertType('Hyperf\Support\Fluent', $request->fluent(['key1', 'key2'])); + +// Request::hasAny() tests +assertType('bool', $request->hasAny(['key1', 'key2'])); +assertType('bool', $request->hasAny('key')); + +// Request::host() tests +assertType('string', $request->host()); + +// Request::httpHost() tests +assertType('string', $request->httpHost()); + +// Request::integer() tests +assertType('int', $request->integer('count')); +assertType('int', $request->integer('total', 0)); + +// Request::isEmptyString() tests +assertType('bool', $request->isEmptyString('field')); + +// Request::isJson() tests +assertType('bool', $request->isJson()); + +// Request::isNotFilled() tests +assertType('bool', $request->isNotFilled('field')); +assertType('bool', $request->isNotFilled(['field1', 'field2'])); + +// Request::keys() tests +assertType('array', $request->keys()); + +// Request::missing() tests +assertType('bool', $request->missing('key')); +assertType('bool', $request->missing(['key1', 'key2'])); + +// Request::str() tests +assertType('Hyperf\Stringable\Stringable', $request->str('name')); +assertType('Hyperf\Stringable\Stringable', $request->str('title', 'default')); + +// Request::string() tests +assertType('Hyperf\Stringable\Stringable', $request->string('name')); +assertType('Hyperf\Stringable\Stringable', $request->string('title', 'default')); + +// Request::wantsJson() tests +assertType('bool', $request->wantsJson()); + +// Request::isSecure() tests +assertType('bool', $request->isSecure()); + +// Request::getScheme() tests +assertType('string', $request->getScheme()); + +// Request::getPort() tests +assertType('int', $request->getPort()); + +// Request::schemeAndHttpHost() tests +assertType('string', $request->schemeAndHttpHost()); + +// Request::anyFilled() tests +assertType('bool', $request->anyFilled(['key1', 'key2'])); +assertType('bool', $request->anyFilled('key')); diff --git a/types/Macros/Str.php b/types/Macros/Str.php new file mode 100644 index 000000000..530d538ec --- /dev/null +++ b/types/Macros/Str.php @@ -0,0 +1,44 @@ + 'custom-uuid'); + +// Str::deduplicate() tests +assertType('string', Str::deduplicate('hello world')); +assertType('string', Str::deduplicate('a--b--c', '-')); + +// Str::inlineMarkdown() tests +assertType('string', Str::inlineMarkdown('**bold**')); +assertType('string', Str::inlineMarkdown('*italic*', [])); + +// Str::markdown() tests +assertType('string', Str::markdown('# Heading')); +assertType('string', Str::markdown('## Title', [], [])); + +// Str::transliterate() tests +assertType('string', Str::transliterate('こんにちは')); +assertType('string', Str::transliterate('café', '?', false)); + +// Str::doesntEndWith() tests +assertType('bool', Str::doesntEndWith('hello world', 'world')); +assertType('bool', Str::doesntEndWith('test', ['ing', 'ed'])); + +// Str::doesntStartWith() tests +assertType('bool', Str::doesntStartWith('hello world', 'hello')); +assertType('bool', Str::doesntStartWith('test', ['pre', 'post'])); diff --git a/types/Macros/Stringable.php b/types/Macros/Stringable.php new file mode 100644 index 000000000..aed8528bd --- /dev/null +++ b/types/Macros/Stringable.php @@ -0,0 +1,44 @@ +deduplicate()); +assertType('Hyperf\Stringable\Stringable', Str::of('a--b')->deduplicate('-')); + +// Stringable::hash() tests +assertType('Hyperf\Stringable\Stringable', Str::of('password')->hash('sha256')); +assertType('Hyperf\Stringable\Stringable', Str::of('text')->hash('md5')); + +// Stringable::inlineMarkdown() tests +assertType('Hyperf\Stringable\Stringable', Str::of('**bold**')->inlineMarkdown()); +assertType('Hyperf\Stringable\Stringable', Str::of('*italic*')->inlineMarkdown([])); + +// Stringable::markdown() tests +assertType('Hyperf\Stringable\Stringable', Str::of('# Heading')->markdown()); +assertType('Hyperf\Stringable\Stringable', Str::of('## Title')->markdown([], [])); + +// Stringable::toHtmlString() tests +assertType('FriendsOfHyperf\Support\HtmlString', Str::of('

test

')->toHtmlString()); + +// Stringable::whenIsAscii() tests +assertType('Hyperf\Stringable\Stringable', Str::of('hello')->whenIsAscii(fn ($s) => $s->upper())); +assertType('Hyperf\Stringable\Stringable', Str::of('test')->whenIsAscii(fn ($s) => $s->upper(), fn ($s) => $s->lower())); + +// Stringable::doesntEndWith() tests +assertType('bool', Str::of('hello world')->doesntEndWith('world')); +assertType('bool', Str::of('test')->doesntEndWith(['ing', 'ed'])); + +// Stringable::doesntStartWith() tests +assertType('bool', Str::of('hello world')->doesntStartWith('hello')); +assertType('bool', Str::of('test')->doesntStartWith(['pre', 'post'])); diff --git a/types/Sentry/Sentry.php b/types/Sentry/Sentry.php new file mode 100644 index 000000000..e0e157f30 --- /dev/null +++ b/types/Sentry/Sentry.php @@ -0,0 +1,189 @@ + + */ + private array $items; + + /** + * @param array $items + */ + public function __construct(array $items = []) + { + $this->items = $items; + } + + public function get(string $key, mixed $default = null): mixed + { + return $this->items[$key] ?? $default; + } + + public function has(string $keys): bool + { + return array_key_exists($keys, $this->items); + } + + public function set(string $key, mixed $value): void + { + $this->items[$key] = $value; + } +} + +final class DummyContainer implements ContainerInterface +{ + /** + * @param array $entries + */ + public function __construct(private array $entries) + { + } + + public function get(string $id): mixed + { + if (! $this->has($id)) { + throw new RuntimeException(sprintf('Entry "%s" not found.', $id)); + } + + $entry = $this->entries[$id]; + + if ($entry instanceof Closure) { + $entry = $entry($this); + } + + return $entry; + } + + public function has(string $id): bool + { + return array_key_exists($id, $this->entries); + } +} + +final class DummyTransport implements TransportInterface +{ + public function send(Event $event): Result + { + return new Result(ResultStatus::success(), $event); + } + + public function close(?int $timeout = null): Result + { + return new Result(ResultStatus::success()); + } +} + +assertType(Feature::class, feature()); + +$transaction = startTransaction(new TransactionContext('demo')); +assertType(Transaction::class, $transaction); + +assertType('bool', trace(static fn (Scope $scope): bool => true, new SpanContext())); + +$config = new ArrayConfig([ + 'sentry.enable.foo' => true, + 'sentry.breadcrumbs.foo' => false, + 'sentry.enable_tracing' => true, + 'sentry.tracing.enable.foo' => true, + 'sentry.tracing.spans.foo' => true, + 'sentry.tracing.extra_tags' => ['foo' => true], + 'sentry.crons.enable' => true, + 'sentry.ignore_exceptions' => [RuntimeException::class], +]); + +$feature = new Feature($config); +assertType('bool', $feature->isEnabled('foo')); +assertType('bool', $feature->isBreadcrumbEnabled('foo')); +assertType('bool', $feature->isTracingEnabled('foo')); +assertType('bool', $feature->isTracingSpanEnabled('foo')); +assertType('bool', $feature->isTracingExtraTagEnabled('foo')); +assertType('bool', $feature->isCronsEnabled()); + +Feature::disableCoroutineTracing(); +assertType('bool', Feature::isDisableCoroutineTracing()); + +$switcher = new Switcher($config); +assertType('bool', $switcher->isEnable('foo')); +assertType('bool', $switcher->isExceptionIgnored(RuntimeException::class)); + +assertType('string', Integration::sentryMeta()); +assertType('string', Integration::sentryTracingMeta()); +assertType('string', Integration::sentryBaggageMeta()); +assertType(Span::class . '|null', Integration::currentTracingSpan()); + +assertType('string', Version::getSdkIdentifier()); +assertType('string', Version::getSdkVersion()); + +$factoryConfig = new ArrayConfig([ + 'sentry' => [ + 'integrations' => [], + 'transport_channel_size' => 1, + ], +]); + +$clientBuilderFactoryContainer = new DummyContainer([ + ConfigInterface::class => $factoryConfig, + TransportInterface::class => new DummyTransport(), +]); + +$clientBuilderFactory = new ClientBuilderFactory(); +$clientBuilder = $clientBuilderFactory($clientBuilderFactoryContainer); +assertType(ClientBuilder::class, $clientBuilder); + +$hubContainer = new DummyContainer([ + ConfigInterface::class => $factoryConfig, + ClientBuilder::class => $clientBuilder, + RequestFetcher::class => new RequestFetcher(), +]); + +$hubFactory = new HubFactory(); +$hub = $hubFactory($hubContainer); +assertType(HubInterface::class, $hub); + +$requestFetcher = new RequestFetcher(); +assertType(ServerRequestInterface::class . '|null', $requestFetcher->fetchRequest()); + +$tracer = new Tracer(); +assertType(Transaction::class, $tracer->startTransaction(new TransactionContext('demo'))); +assertType('string', $tracer->trace(static fn (Scope $scope): string => 'done', new SpanContext())); diff --git a/types/Support/Support.php b/types/Support/Support.php new file mode 100644 index 000000000..021536ce6 --- /dev/null +++ b/types/Support/Support.php @@ -0,0 +1,63 @@ + 'value')); + +assertType(Onceable::class . '|null', Onceable::tryFromTrace(debug_backtrace(), static fn (): int => 1)); + +$timebox = new Timebox(); + +assertType('int', $timebox->call(static fn (Timebox $box): int => 42, 500)); + +assertType(Timebox::class, $timebox->returnEarly()); +assertType(Timebox::class, $timebox->dontReturnEarly()); + +assertType(Sleep::class, Sleep::for(1)); +assertType(Sleep::class, Sleep::sleep(1)); +assertType(Sleep::class, Sleep::usleep(1)); +assertType(Sleep::class, Sleep::until(1)); + +$sleep = Sleep::for(1)->and(1)->seconds(); +assertType(Sleep::class, $sleep); + +assertType('int', Number::parseInt('100')); +assertType('float', Number::parseFloat('100.5')); +assertType('float|int', Number::parse('100')); +assertType('string|false', Number::format(1000)); +assertType('float|int', Number::clamp(5, 1, 10)); + +$html = new HtmlString('

Hello

'); +assertType('string', $html->toHtml()); +assertType('bool', $html->isEmpty()); +assertType('bool', $html->isNotEmpty()); + +$environment = new Environment('production'); +assertType('string|null', $environment->get()); +assertType('bool', $environment->is('production')); + +assertType('Dotenv\Repository\RepositoryInterface', Env::getRepository()); + +$command = new RedisCommand('SET', ['key', 'value']); +assertType('string', (string) $command);