SerializerPhp81Test.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. <?php
  2. use Tests\Fixtures\Model;
  3. use Tests\Fixtures\ModelAttribute;
  4. use Tests\Fixtures\RegularClass;
  5. test('enums', function () {
  6. $f = function (SerializerGlobalEnum $role) {
  7. return $role;
  8. };
  9. $f = s($f);
  10. expect($f(SerializerGlobalEnum::Guest))->toBe(
  11. SerializerGlobalEnum::Guest
  12. );
  13. $role = SerializerGlobalEnum::Admin;
  14. $f = function () use ($role) {
  15. return $role;
  16. };
  17. $f = s($f);
  18. expect($f())->toBe(SerializerGlobalEnum::Admin);
  19. if (! enum_exists(SerializerScopedEnum::class)) {
  20. enum SerializerScopedEnum {
  21. case Admin;
  22. case Guest;
  23. case Moderator;
  24. }
  25. }
  26. $f = function () {
  27. return SerializerScopedEnum::Admin;
  28. };
  29. $f = s($f);
  30. expect($f()->name)->toBe('Admin');
  31. $role = SerializerScopedEnum::Admin;
  32. $f = function () use ($role) {
  33. return $role;
  34. };
  35. $f = s($f);
  36. expect($f())->toBe(SerializerScopedEnum::Admin);
  37. })->with('serializers');
  38. test('enums properties', function () {
  39. $object = new ClassWithEnumProperty();
  40. $f = $object->getClosure();
  41. $f = s($f);
  42. expect($f())
  43. ->name->toBe('Admin')
  44. ->value->toBeNull();
  45. })->with('serializers');
  46. test('backed enums', function () {
  47. $f = function (SerializerGlobalBackedEnum $role) {
  48. return $role;
  49. };
  50. $f = s($f);
  51. expect($f(SerializerGlobalBackedEnum::Guest))->toBe(
  52. SerializerGlobalBackedEnum::Guest
  53. );
  54. $role = SerializerGlobalBackedEnum::Admin;
  55. $f = function () use ($role) {
  56. return $role;
  57. };
  58. $f = s($f);
  59. expect($f())->toBe(SerializerGlobalBackedEnum::Admin);
  60. if (! enum_exists(SerializerScopedBackedEnum::class)) {
  61. enum SerializerScopedBackedEnum: string {
  62. case Admin = 'Administrator';
  63. case Guest = 'Guest';
  64. case Moderator = 'Moderator';
  65. }
  66. }
  67. $f = function () {
  68. return SerializerScopedBackedEnum::Admin;
  69. };
  70. $f = s($f);
  71. expect($f())->name->toBe('Admin')
  72. ->value->toBe('Administrator');
  73. $role = SerializerScopedBackedEnum::Admin;
  74. $f = function () use ($role) {
  75. return $role;
  76. };
  77. $f = s($f);
  78. expect($f())->toBe(SerializerScopedBackedEnum::Admin);
  79. })->with('serializers');
  80. test('backed enums properties', function () {
  81. $object = new ClassWithBackedEnumProperty();
  82. $f = $object->getClosure();
  83. $f = s($f);
  84. expect($f())
  85. ->name->toBe('Admin')
  86. ->value->toBe('Administrator');
  87. })->with('serializers');
  88. test('array unpacking', function () {
  89. $f = function () {
  90. $array1 = ['a' => 1];
  91. $array2 = ['b' => 2];
  92. return ['a' => 0, ...$array1, ...$array2];
  93. };
  94. $f = s($f);
  95. expect($f())->toBe([
  96. 'a' => 1,
  97. 'b' => 2,
  98. ]);
  99. })->with('serializers');
  100. test('new in initializers', function () {
  101. $f = function () {
  102. return new SerializerPhp81Controller();
  103. };
  104. $f = s($f);
  105. expect($f()->service)->toBeInstanceOf(
  106. SerializerPhp81Service::class,
  107. );
  108. })->with('serializers');
  109. test('readonly properties', function () {
  110. $f = function () {
  111. $controller = new SerializerPhp81Controller();
  112. $controller->service = 'foo';
  113. };
  114. $f = s($f);
  115. expect($f)->toThrow(function (Error $e) {
  116. expect($e->getMessage())->toBe(
  117. 'Cannot modify readonly property SerializerPhp81Controller::$service',
  118. );
  119. });
  120. })->with('serializers');
  121. test('first-class callable with closures', function () {
  122. $f = function ($value) {
  123. return $value;
  124. };
  125. $f = s($f);
  126. expect($f(...)('foo'))->toBe('foo');
  127. $f = function ($a) {
  128. $f = fn ($b) => $a + $b + 1;
  129. return $f(...);
  130. };
  131. $f = s($f);
  132. expect($f(1)(2))->toBe(4);
  133. })->with('serializers');
  134. test('first-class callable with methods', function () {
  135. $f = (new SerializerPhp81Controller())->publicGetter(...);
  136. $f = s($f);
  137. expect($f())->toBeInstanceOf(SerializerPhp81Service::class);
  138. $f = (new SerializerPhp81Controller())->publicGetterResolver(...);
  139. $f = s($f);
  140. expect($f()()())->toBeInstanceOf(SerializerPhp81Service::class);
  141. })->with('serializers');
  142. test('first-class callable with static methods', function () {
  143. $f = SerializerPhp81Controller::publicStaticGetter(...);
  144. $f = s($f);
  145. expect($f())->toBeInstanceOf(SerializerPhp81Service::class);
  146. $f = SerializerPhp81Controller::publicStaticGetterResolver(...);
  147. $f = s($f);
  148. expect($f()()())->toBeInstanceOf(SerializerPhp81Service::class);
  149. })->with('serializers');
  150. test('first-class callable final method', function () {
  151. $f = (new SerializerPhp81Controller())->finalPublicGetterResolver(...);
  152. $f = s($f);
  153. expect($f()()())->toBeInstanceOf(SerializerPhp81Service::class);
  154. $f = SerializerPhp81Controller::finalPublicStaticGetterResolver(...);
  155. $f = s($f);
  156. expect($f()()())->toBeInstanceOf(SerializerPhp81Service::class);
  157. })->with('serializers');
  158. test('first-class callable self return type', function () {
  159. $f = (new SerializerPhp81Controller())->getSelf(...);
  160. $f = s($f);
  161. $controller = new SerializerPhp81Controller();
  162. expect($f($controller))->toBeInstanceOf(SerializerPhp81Controller::class);
  163. })->with('serializers');
  164. test('intersection types', function () {
  165. $f = function (SerializerPhp81HasName&SerializerPhp81HasId $service): SerializerPhp81HasName&SerializerPhp81HasId {
  166. return $service;
  167. };
  168. $f = s($f);
  169. expect($f(new SerializerPhp81Service))->toBeInstanceOf(
  170. SerializerPhp81Service::class,
  171. );
  172. })->with('serializers');
  173. test('never type', function () {
  174. $f = function (): never {
  175. throw new RuntimeException('Something wrong happened.');
  176. };
  177. $f = s($f);
  178. expect($f)->toThrow(RuntimeException::class);
  179. })->with('serializers');
  180. test('array_is_list', function () {
  181. $f = function () {
  182. return array_is_list([]);
  183. };
  184. $f = s($f);
  185. expect($f())->toBeTrue();
  186. })->with('serializers');
  187. test('final class constants', function () {
  188. $f = function () {
  189. return SerializerPhp81Service::X;
  190. };
  191. $f = s($f);
  192. expect($f())->toBe('foo');
  193. })->with('serializers');
  194. test('constructor property promotion', function () {
  195. $class = new PropertyPromotion('public', 'protected', 'private');
  196. $f1 = fn () => $class;
  197. $object = s($f1)();
  198. expect($object->public)->toBe('public');
  199. expect($object->getProtected())->toBe('protected');
  200. expect($object->getPrivate())->toBe('private');
  201. })->with('serializers');
  202. test('first-class callable namespaces', function () {
  203. $model = new Model();
  204. $f = $model->make(...);
  205. $f = s($f);
  206. expect($f(new Model))->toBeInstanceOf(Model::class);
  207. })->with('serializers');
  208. test('static first-class callable namespaces', function () {
  209. $model = new Model();
  210. $f = $model->staticMake(...);
  211. $f = s($f);
  212. expect($f(new Model))->toBeInstanceOf(Model::class);
  213. })->with('serializers');
  214. test('function attributes without arguments', function () {
  215. $model = new Model();
  216. $f = #[MyAttribute] function () {
  217. return true;
  218. };
  219. $f = s($f);
  220. $reflector = new ReflectionFunction($f);
  221. expect($reflector->getAttributes())->sequence(
  222. fn ($attribute) => $attribute
  223. ->getName()->toBe(MyAttribute::class)
  224. ->getArguments()->toBeEmpty(),
  225. );
  226. expect($f())->toBeTrue();
  227. })->with('serializers');
  228. test('function attributes with arguments', function () {
  229. $model = new Model();
  230. $f = #[MyAttribute('My " \' Argument 1', Model::class)] function () {
  231. return false;
  232. };
  233. $f = s($f);
  234. $reflector = new ReflectionFunction($f);
  235. expect($reflector->getAttributes())->sequence(
  236. fn ($attribute) => $attribute
  237. ->getName()->toBe(MyAttribute::class)
  238. ->getArguments()->toBe([
  239. 'My " \' Argument 1', Model::class,
  240. ])
  241. );
  242. expect($f())->toBeFalse();
  243. })->with('serializers');
  244. test('named arguments', function () {
  245. $variable = 'variableValue';
  246. $f = function (string $a1) use ($variable) {
  247. return new RegularClass(
  248. a1: $a1,
  249. a2: 'string',
  250. a3: 1,
  251. a4: 1.1,
  252. a5: true,
  253. a6: null,
  254. a7: ['string'],
  255. a8: ['string', $a1, 1, null, true],
  256. a9: [[[[['string', $a1, 1, null, true]]]]],
  257. a10: new RegularClass(
  258. a1: $a1,
  259. a2: 'string',
  260. a3: 1,
  261. a4: 1.1,
  262. a5: true,
  263. a6: null,
  264. a7: ['string'],
  265. a8: ['string', $a1, 1, null, true],
  266. a9: [[[[['string', $a1, 1, null, true]]]]],
  267. a10: new RegularClass(),
  268. a11: RegularClass::C,
  269. a12: RegularClass::staticMethod(),
  270. a13: (new RegularClass())->instanceMethod(),
  271. a14: [new RegularClass(), RegularClass::C, RegularClass::staticMethod(), (new RegularClass())->instanceMethod()],
  272. ),
  273. a11: RegularClass::C,
  274. a12: [new RegularClass(), RegularClass::C],
  275. a13: RegularClass::staticMethod(),
  276. a14: (new RegularClass())->instanceMethod(),
  277. a15: fn () => new RegularClass(
  278. a1: $a1,
  279. a2: 'string',
  280. a3: 1,
  281. a4: 1.1,
  282. a5: true,
  283. a6: null,
  284. a7: ['string'],
  285. a8: ['string', $a1, 1, null, true],
  286. a9: [[[[['string', $a1, 1, null, true]]]]],
  287. a10: new RegularClass(
  288. a1: $a1,
  289. a2: 'string',
  290. a3: 1,
  291. a4: 1.1,
  292. a5: true,
  293. a6: null,
  294. a7: ['string'],
  295. a8: ['string', $a1, 1, null, true],
  296. a9: [[[[['string', $a1, 1, null, true]]]]],
  297. a10: new RegularClass(),
  298. a11: RegularClass::C,
  299. a12: RegularClass::staticMethod(),
  300. a13: (new RegularClass())->instanceMethod(),
  301. a14: [new RegularClass(), RegularClass::C, RegularClass::staticMethod(), (new RegularClass())->instanceMethod()],
  302. ),
  303. a11: RegularClass::C,
  304. a12: [new RegularClass(), RegularClass::C],
  305. a13: RegularClass::staticMethod(),
  306. a14: (new RegularClass())->instanceMethod(),
  307. ),
  308. a16: fn () => fn () => new RegularClass(
  309. a1: $a1,
  310. a2: 'string',
  311. a3: 1,
  312. a4: 1.1,
  313. a5: true,
  314. a6: null,
  315. a7: ['string'],
  316. a8: ['string', $a1, 1, null, true],
  317. a9: [[[[['string', $a1, 1, null, true]]]]],
  318. a10: new RegularClass(
  319. a1: $a1,
  320. a2: 'string',
  321. a3: 1,
  322. a4: 1.1,
  323. a5: true,
  324. a6: null,
  325. a7: ['string'],
  326. a8: ['string', $a1, 1, null, true],
  327. a9: [[[[['string', $a1, 1, null, true]]]]],
  328. a10: new RegularClass(),
  329. a11: RegularClass::C,
  330. a12: RegularClass::staticMethod(),
  331. a13: (new RegularClass())->instanceMethod(),
  332. a14: [new RegularClass(), RegularClass::C, RegularClass::staticMethod(), (new RegularClass())->instanceMethod()],
  333. ),
  334. a11: RegularClass::C,
  335. a12: [new RegularClass(), RegularClass::C],
  336. a13: RegularClass::staticMethod(),
  337. a14: (new RegularClass())->instanceMethod(),
  338. ),
  339. a17: function () use ($variable) {
  340. return new RegularClass(
  341. a1: $a1,
  342. a2: 'string',
  343. a3: 1,
  344. a4: 1.1,
  345. a5: true,
  346. a6: null,
  347. a7: ['string'],
  348. a8: ['string', $a1, 1, null, true],
  349. a9: [[[[['string', $a1, 1, null, true]]]]],
  350. a10: new RegularClass(
  351. a1: $a1,
  352. a2: 'string',
  353. a3: 1,
  354. a4: 1.1,
  355. a5: true,
  356. a6: null,
  357. a7: ['string'],
  358. a8: ['string', $a1, 1, null, true],
  359. a9: [[[[['string', $a1, 1, null, true]]]]],
  360. a10: new RegularClass(),
  361. a11: RegularClass::C,
  362. a12: RegularClass::staticMethod(),
  363. a13: (new RegularClass())->instanceMethod(),
  364. a14: [new RegularClass(), RegularClass::C, RegularClass::staticMethod(), (new RegularClass())->instanceMethod()],
  365. ),
  366. a11: RegularClass::C,
  367. a12: [new RegularClass(), RegularClass::C],
  368. a13: RegularClass::staticMethod(),
  369. a14: (new RegularClass())->instanceMethod(),
  370. );
  371. },
  372. a18: serializer_my_function(),
  373. a19: serializer_my_function(SerializerGlobalEnum::Guest),
  374. a20: serializer_my_function(enum: SerializerGlobalEnum::Guest),
  375. );
  376. };
  377. $f = s($f);
  378. $instance = $f('a1');
  379. $expectedArray = [
  380. 'a1' => 'a1',
  381. 'a2' => 'string',
  382. 'a3' => 1,
  383. 'a4' => 1.1,
  384. 'a5' => true,
  385. 'a6' => null,
  386. 'a7' => ['string'],
  387. 'a8' => ['string', 'a1', 1, null, true],
  388. 'a9' => [[[[['string', 'a1', 1, null, true]]]]],
  389. ];
  390. expect($instance)
  391. ->toMatchArray($expectedArray)
  392. ->toMatchArray([
  393. 'a18' => SerializerGlobalEnum::Admin,
  394. 'a19' => SerializerGlobalEnum::Guest,
  395. 'a20' => SerializerGlobalEnum::Guest,
  396. ])
  397. ->and($instance->a15->__invoke())->toMatchArray($expectedArray);
  398. })->with('serializers');
  399. test('function attributes with named arguments', function () {
  400. $model = new Model();
  401. $f = #[MyAttribute(string: 'My " \' Argument 1', model:Model::class)] function () {
  402. return false;
  403. };
  404. $f = s($f);
  405. $reflector = new ReflectionFunction($f);
  406. expect($reflector->getAttributes())->sequence(function ($attribute) {
  407. $attribute
  408. ->getName()->toBe(MyAttribute::class)
  409. ->getArguments()->toBe([
  410. 'string' => 'My " \' Argument 1',
  411. 'model' => Model::class,
  412. ]);
  413. expect($attribute->value->newInstance())
  414. ->string->toBe('My " \' Argument 1')
  415. ->model->toBe(Model::class);
  416. });
  417. expect($f())->toBeFalse();
  418. })->with('serializers');
  419. test('function attributes with first-class callable with methods', function () {
  420. $f = (new SerializerPhp81Controller())->publicGetter(...);
  421. $f = s($f);
  422. $reflector = new ReflectionFunction($f);
  423. expect($reflector->getAttributes())->sequence(
  424. fn ($attribute) => $attribute
  425. ->getName()->toBe(ModelAttribute::class)
  426. ->getArguments()->toBe([]),
  427. fn ($attribute) => $attribute
  428. ->getName()->toBe(MyAttribute::class)
  429. ->getArguments()->toBe([
  430. 'My " \' Argument 1', Model::class,
  431. ])
  432. );
  433. expect($f())->toBeInstanceOf(SerializerPhp81Service::class);
  434. })->with('serializers');
  435. interface SerializerPhp81HasId {}
  436. interface SerializerPhp81HasName {}
  437. class SerializerPhp81Service implements SerializerPhp81HasId, SerializerPhp81HasName
  438. {
  439. final public const X = 'foo';
  440. }
  441. class SerializerPhp81Controller
  442. {
  443. public function __construct(
  444. public readonly SerializerPhp81Service $service = new SerializerPhp81Service(),
  445. ) {
  446. // ..
  447. }
  448. #[ModelAttribute]
  449. #[MyAttribute('My " \' Argument 1', Model::class)]
  450. public function publicGetter()
  451. {
  452. return $this->privateGetter();
  453. }
  454. private function privateGetter()
  455. {
  456. return $this->service;
  457. }
  458. public static function publicStaticGetter()
  459. {
  460. return static::privateStaticGetter();
  461. }
  462. public static function privateStaticGetter()
  463. {
  464. return (new SerializerPhp81Controller())->service;
  465. }
  466. public function publicGetterResolver()
  467. {
  468. return $this->privateGetterResolver(...);
  469. }
  470. private function privateGetterResolver()
  471. {
  472. return fn () => $this->service;
  473. }
  474. public static function publicStaticGetterResolver()
  475. {
  476. return static::privateStaticGetterResolver(...);
  477. }
  478. public static function privateStaticGetterResolver()
  479. {
  480. return fn () => (new SerializerPhp81Controller())->service;
  481. }
  482. final public function finalPublicGetterResolver()
  483. {
  484. return $this->privateGetterResolver(...);
  485. }
  486. final public static function finalPublicStaticGetterResolver()
  487. {
  488. return static::privateStaticGetterResolver(...);
  489. }
  490. public function getSelf(self $instance): self
  491. {
  492. return $instance;
  493. }
  494. }
  495. enum SerializerGlobalEnum {
  496. case Admin;
  497. case Guest;
  498. case Moderator;
  499. }
  500. enum SerializerGlobalBackedEnum: string {
  501. case Admin = 'Administrator';
  502. case Guest = 'Guest';
  503. case Moderator = 'Moderator';
  504. }
  505. #[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION)]
  506. class MyAttribute
  507. {
  508. public function __construct(public $string, public $model)
  509. {
  510. // ..
  511. }
  512. }
  513. class ClassWithEnumProperty
  514. {
  515. public SerializerGlobalEnum $enum = SerializerGlobalEnum::Admin;
  516. public function getClosure()
  517. {
  518. return function () {
  519. return $this->enum;
  520. };
  521. }
  522. }
  523. test('named arguments with namespaced enum parameter', function () {
  524. $f1 = function () {
  525. return new RegularClass(a2: RegularClass::C);
  526. };
  527. expect(s($f1)())->toBeInstanceOf(RegularClass::class);
  528. })->with('serializers');
  529. class ClassWithBackedEnumProperty
  530. {
  531. public SerializerGlobalBackedEnum $enum = SerializerGlobalBackedEnum::Admin;
  532. public function getClosure()
  533. {
  534. return function () {
  535. return $this->enum;
  536. };
  537. }
  538. }
  539. function serializer_my_function(SerializerGlobalEnum $enum = SerializerGlobalEnum::Admin)
  540. {
  541. return $enum;
  542. }