FunctionsTest.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 3.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Core;
  17. use Cake\Core\Configure;
  18. use Cake\Http\Response;
  19. use Cake\ORM\Entity;
  20. use Cake\TestSuite\TestCase;
  21. use PHPUnit\Framework\Attributes\DataProvider;
  22. use stdClass;
  23. use function Cake\Core\deprecationWarning;
  24. use function Cake\Core\env;
  25. use function Cake\Core\h;
  26. use function Cake\Core\namespaceSplit;
  27. use function Cake\Core\pathCombine;
  28. use function Cake\Core\pluginSplit;
  29. use function Cake\Core\toBool;
  30. use function Cake\Core\toFloat;
  31. use function Cake\Core\toInt;
  32. use function Cake\Core\toString;
  33. use function Cake\Core\triggerWarning;
  34. /**
  35. * Test cases for functions in Core\functions.php
  36. */
  37. class FunctionsTest extends TestCase
  38. {
  39. public function testPathCombine(): void
  40. {
  41. $this->assertSame('', pathCombine([]));
  42. $this->assertSame('', pathCombine(['']));
  43. $this->assertSame('', pathCombine(['', '']));
  44. $this->assertSame('/', pathCombine(['/', '/']));
  45. $this->assertSame('path/to/file', pathCombine(['path', 'to', 'file']));
  46. $this->assertSame('path/to/file', pathCombine(['path/', 'to', 'file']));
  47. $this->assertSame('path/to/file', pathCombine(['path', 'to/', 'file']));
  48. $this->assertSame('path/to/file', pathCombine(['path/', 'to/', 'file']));
  49. $this->assertSame('path/to/file', pathCombine(['path/', '/to/', 'file']));
  50. $this->assertSame('/path/to/file', pathCombine(['/', 'path', 'to', 'file']));
  51. $this->assertSame('/path/to/file', pathCombine(['/', '/path', 'to', 'file']));
  52. $this->assertSame('/path/to/file/', pathCombine(['/path', 'to', 'file/']));
  53. $this->assertSame('/path/to/file/', pathCombine(['/path', 'to', 'file', '/']));
  54. $this->assertSame('/path/to/file/', pathCombine(['/path', 'to', 'file/', '/']));
  55. // Test adding trailing slash
  56. $this->assertSame('/', pathCombine([], trailing: true));
  57. $this->assertSame('/', pathCombine([''], trailing: true));
  58. $this->assertSame('/', pathCombine(['/'], trailing: true));
  59. $this->assertSame('/path/to/file/', pathCombine(['/path', 'to', 'file/'], trailing: true));
  60. $this->assertSame('/path/to/file/', pathCombine(['/path', 'to', 'file/', '/'], trailing: true));
  61. // Test removing trailing slash
  62. $this->assertSame('', pathCombine([''], trailing: false));
  63. $this->assertSame('', pathCombine(['/'], trailing: false));
  64. $this->assertSame('/path/to/file', pathCombine(['/path', 'to', 'file/'], trailing: false));
  65. $this->assertSame('/path/to/file', pathCombine(['/path', 'to', 'file/', '/'], trailing: false));
  66. // Test Windows-style backslashes
  67. $this->assertSame('/path/to\\file', pathCombine(['/', '\\path', 'to', '\\file']));
  68. $this->assertSame('/path\\to\\file/', pathCombine(['/', 'path', '\\to\\', 'file'], trailing: true));
  69. $this->assertSame('/path\\to\\file\\', pathCombine(['/', 'path', '\\to\\', 'file', '\\'], trailing: true));
  70. $this->assertSame('/path\\to\\file', pathCombine(['/', 'path', '\\to\\', 'file'], trailing: false));
  71. $this->assertSame('/path\\to\\file', pathCombine(['/', 'path', '\\to\\', 'file', '\\'], trailing: false));
  72. }
  73. /**
  74. * Test cases for env()
  75. */
  76. public function testEnv(): void
  77. {
  78. $_ENV['DOES_NOT_EXIST'] = null;
  79. $this->assertNull(env('DOES_NOT_EXIST'));
  80. $this->assertSame('default', env('DOES_NOT_EXIST', 'default'));
  81. $_ENV['DOES_EXIST'] = 'some value';
  82. $this->assertSame('some value', env('DOES_EXIST'));
  83. $this->assertSame('some value', env('DOES_EXIST', 'default'));
  84. $_ENV['EMPTY_VALUE'] = '';
  85. $this->assertSame('', env('EMPTY_VALUE'));
  86. $this->assertSame('', env('EMPTY_VALUE', 'default'));
  87. $_ENV['ZERO'] = '0';
  88. $this->assertSame('0', env('ZERO'));
  89. $this->assertSame('0', env('ZERO', '1'));
  90. $_ENV['ZERO'] = 0;
  91. $this->assertSame(0, env('ZERO'));
  92. $this->assertSame(0, env('ZERO', 1));
  93. $_ENV['ZERO'] = 0.0;
  94. $this->assertSame(0.0, env('ZERO'));
  95. $this->assertSame(0.0, env('ZERO', 1));
  96. $this->assertSame('', env('DOCUMENT_ROOT'));
  97. $this->assertStringContainsString('phpunit', env('PHP_SELF'));
  98. }
  99. public function testEnv2(): void
  100. {
  101. $this->skipIf(!function_exists('ini_get') || ini_get('safe_mode') === '1', 'Safe mode is on.');
  102. $server = $_SERVER;
  103. $env = $_ENV;
  104. $_SERVER = [];
  105. $_ENV = [];
  106. $_SERVER['SCRIPT_NAME'] = '/a/test/test.php';
  107. $this->assertSame(env('SCRIPT_NAME'), '/a/test/test.php');
  108. $_SERVER = [];
  109. $_ENV = [];
  110. $_ENV['CGI_MODE'] = 'BINARY';
  111. $_ENV['SCRIPT_URL'] = '/a/test/test.php';
  112. $this->assertSame(env('SCRIPT_NAME'), '/a/test/test.php');
  113. $_SERVER = [];
  114. $_ENV = [];
  115. $this->assertFalse(env('HTTPS'));
  116. $_SERVER['HTTPS'] = 'on';
  117. $this->assertTrue(env('HTTPS'));
  118. $_SERVER['HTTPS'] = '1';
  119. $this->assertTrue(env('HTTPS'));
  120. $_SERVER['HTTPS'] = 'I am not empty';
  121. $this->assertTrue(env('HTTPS'));
  122. $_SERVER['HTTPS'] = 1;
  123. $this->assertTrue(env('HTTPS'));
  124. $_SERVER['HTTPS'] = 'off';
  125. $this->assertFalse(env('HTTPS'));
  126. $_SERVER['HTTPS'] = false;
  127. $this->assertFalse(env('HTTPS'));
  128. $_SERVER['HTTPS'] = '';
  129. $this->assertFalse(env('HTTPS'));
  130. $_SERVER = [];
  131. $_ENV['SCRIPT_URI'] = 'https://domain.test/a/test.php';
  132. $this->assertTrue(env('HTTPS'));
  133. $_ENV['SCRIPT_URI'] = 'http://domain.test/a/test.php';
  134. $this->assertFalse(env('HTTPS'));
  135. $_SERVER = [];
  136. $_ENV = [];
  137. $this->assertNull(env('TEST_ME'));
  138. $_ENV['TEST_ME'] = 'a';
  139. $this->assertSame(env('TEST_ME'), 'a');
  140. $_SERVER['TEST_ME'] = 'b';
  141. $this->assertSame(env('TEST_ME'), 'b');
  142. unset($_ENV['TEST_ME']);
  143. $this->assertSame(env('TEST_ME'), 'b');
  144. $_SERVER = $server;
  145. $_ENV = $env;
  146. }
  147. /**
  148. * Test cases for h()
  149. *
  150. * @param mixed $value
  151. * @param mixed $expected
  152. */
  153. #[DataProvider('hInputProvider')]
  154. public function testH($value, $expected): void
  155. {
  156. $result = h($value);
  157. $this->assertSame($expected, $result);
  158. }
  159. public static function hInputProvider(): array
  160. {
  161. return [
  162. ['i am clean', 'i am clean'],
  163. ['i "need" escaping', 'i &quot;need&quot; escaping'],
  164. [null, null],
  165. [1, 1],
  166. [1.1, 1.1],
  167. [new stdClass(), '(object)stdClass'],
  168. [new Response(), ''],
  169. [['clean', '"clean-me'], ['clean', '&quot;clean-me']],
  170. ];
  171. }
  172. public function testH2(): void
  173. {
  174. $string = '<foo>';
  175. $result = h($string);
  176. $this->assertSame('&lt;foo&gt;', $result);
  177. $in = ['this & that', '<p>Which one</p>'];
  178. $result = h($in);
  179. $expected = ['this &amp; that', '&lt;p&gt;Which one&lt;/p&gt;'];
  180. $this->assertSame($expected, $result);
  181. $string = '<foo> & &nbsp;';
  182. $result = h($string);
  183. $this->assertSame('&lt;foo&gt; &amp; &amp;nbsp;', $result);
  184. $string = '<foo> & &nbsp;';
  185. $result = h($string, false);
  186. $this->assertSame('&lt;foo&gt; &amp; &nbsp;', $result);
  187. $string = "An invalid\x80string";
  188. $result = h($string);
  189. $this->assertStringContainsString('string', $result);
  190. $arr = ['<foo>', '&nbsp;'];
  191. $result = h($arr);
  192. $expected = [
  193. '&lt;foo&gt;',
  194. '&amp;nbsp;',
  195. ];
  196. $this->assertSame($expected, $result);
  197. $arr = ['<foo>', '&nbsp;'];
  198. $result = h($arr, false);
  199. $expected = [
  200. '&lt;foo&gt;',
  201. '&nbsp;',
  202. ];
  203. $this->assertSame($expected, $result);
  204. $arr = ['f' => '<foo>', 'n' => '&nbsp;'];
  205. $result = h($arr, false);
  206. $expected = [
  207. 'f' => '&lt;foo&gt;',
  208. 'n' => '&nbsp;',
  209. ];
  210. $this->assertSame($expected, $result);
  211. $arr = ['invalid' => "\x99An invalid\x80string", 'good' => 'Good string'];
  212. $result = h($arr);
  213. $this->assertStringContainsString('An invalid', $result['invalid']);
  214. $this->assertSame('Good string', $result['good']);
  215. // Test that boolean values are not converted to strings
  216. $result = h(false);
  217. $this->assertFalse($result);
  218. $arr = ['foo' => false, 'bar' => true];
  219. $result = h($arr);
  220. $this->assertFalse($result['foo']);
  221. $this->assertTrue($result['bar']);
  222. $obj = new stdClass();
  223. $result = h($obj);
  224. $this->assertSame('(object)stdClass', $result);
  225. $obj = new Response(['body' => 'Body content']);
  226. $result = h($obj);
  227. $this->assertSame('Body content', $result);
  228. }
  229. /**
  230. * Test splitting plugin names.
  231. */
  232. public function testPluginSplit(): void
  233. {
  234. $result = pluginSplit('Something.else');
  235. $this->assertSame(['Something', 'else'], $result);
  236. $result = pluginSplit('Something.else.more.dots');
  237. $this->assertSame(['Something', 'else.more.dots'], $result);
  238. $result = pluginSplit('Somethingelse');
  239. $this->assertSame([null, 'Somethingelse'], $result);
  240. $result = pluginSplit('Something.else', true);
  241. $this->assertSame(['Something.', 'else'], $result);
  242. $result = pluginSplit('Something.else.more.dots', true);
  243. $this->assertSame(['Something.', 'else.more.dots'], $result);
  244. $result = pluginSplit('Post', false, 'Blog');
  245. $this->assertSame(['Blog', 'Post'], $result);
  246. $result = pluginSplit('Blog.Post', false, 'Ultimate');
  247. $this->assertSame(['Blog', 'Post'], $result);
  248. }
  249. /**
  250. * test namespaceSplit
  251. */
  252. public function testNamespaceSplit(): void
  253. {
  254. $result = namespaceSplit('Something');
  255. $this->assertSame(['', 'Something'], $result);
  256. $result = namespaceSplit('\Something');
  257. $this->assertSame(['', 'Something'], $result);
  258. $result = namespaceSplit('Cake\Something');
  259. $this->assertSame(['Cake', 'Something'], $result);
  260. $result = namespaceSplit('Cake\Test\Something');
  261. $this->assertSame(['Cake\Test', 'Something'], $result);
  262. }
  263. /**
  264. * Test error messages coming out when deprecated level is on, manually setting the stack frame
  265. */
  266. public function testDeprecationWarningEnabled(): void
  267. {
  268. $this->expectDeprecationMessageMatches('/Since 5.0.0: This is going away\n(.*?)[\/\\\]FunctionsTest.php, line\: \d+/', function (): void {
  269. $this->withErrorReporting(E_ALL, function (): void {
  270. deprecationWarning('5.0.0', 'This is going away', 2);
  271. });
  272. });
  273. }
  274. /**
  275. * Test error messages coming out when deprecated level is on, not setting the stack frame manually
  276. */
  277. public function testDeprecationWarningEnabledDefaultFrame(): void
  278. {
  279. $this->expectDeprecationMessageMatches('/Since 5.0.0: This is going away too\n(.*?)[\/\\\]TestCase.php, line\: \d+/', function (): void {
  280. $this->withErrorReporting(E_ALL, function (): void {
  281. deprecationWarning('5.0.0', 'This is going away too');
  282. });
  283. });
  284. }
  285. /**
  286. * Test no error when deprecation matches ignore paths.
  287. */
  288. public function testDeprecationWarningPathDisabled(): void
  289. {
  290. $this->expectNotToPerformAssertions();
  291. Configure::write('Error.ignoredDeprecationPaths', ['src/TestSuite/*']);
  292. $this->withErrorReporting(E_ALL, function (): void {
  293. deprecationWarning('5.0.0', 'This will be gone soon');
  294. });
  295. }
  296. /**
  297. * Test no error when deprecated level is off.
  298. */
  299. public function testDeprecationWarningLevelDisabled(): void
  300. {
  301. $this->expectNotToPerformAssertions();
  302. $this->withErrorReporting(E_ALL ^ E_USER_DEPRECATED, function (): void {
  303. deprecationWarning('5.0.0', 'This is leaving');
  304. });
  305. }
  306. /**
  307. * Test error messages coming out when warning level is on.
  308. */
  309. public function testTriggerWarningEnabled(): void
  310. {
  311. $this->expectWarningMessageMatches('/This will be gone one day - (.*?)[\/\\\]TestCase.php, line\: \d+/', function (): void {
  312. $this->withErrorReporting(E_ALL, function (): void {
  313. triggerWarning('This will be gone one day');
  314. $this->assertTrue(true);
  315. });
  316. });
  317. }
  318. /**
  319. * Test no error when warning level is off.
  320. */
  321. public function testTriggerWarningLevelDisabled(): void
  322. {
  323. $this->withErrorReporting(E_ALL ^ E_USER_WARNING, function (): void {
  324. triggerWarning('This was a mistake.');
  325. $this->assertTrue(true);
  326. });
  327. }
  328. #[DataProvider('toStringProvider')]
  329. public function testToString(mixed $rawValue, ?string $expected): void
  330. {
  331. $this->assertSame($expected, toString($rawValue));
  332. }
  333. /**
  334. * @return array The array of test cases.
  335. */
  336. public static function toStringProvider(): array
  337. {
  338. return [
  339. // input like string
  340. '(string) empty' => ['', ''],
  341. '(string) space' => [' ', ' '],
  342. '(string) dash' => ['-', '-'],
  343. '(string) zero' => ['0', '0'],
  344. '(string) number' => ['55', '55'],
  345. '(string) partially2 number' => ['5x', '5x'],
  346. // input like int
  347. '(int) number' => [55, '55'],
  348. '(int) negative number' => [-5, '-5'],
  349. '(int) PHP_INT_MAX + 2' => [9223372036854775809, '9223372036854775808'], //is float: see IEEE 754
  350. '(int) PHP_INT_MAX + 1' => [9223372036854775808, '9223372036854775808'], //is float: see IEEE 754
  351. '(int) PHP_INT_MAX + 0' => [9223372036854775807, '9223372036854775807'],
  352. '(int) PHP_INT_MAX - 1' => [9223372036854775806, '9223372036854775806'],
  353. '(int) PHP_INT_MIN + 1' => [-9223372036854775807, '-9223372036854775807'],
  354. '(int) PHP_INT_MIN + 0' => [-9223372036854775808, '-9223372036854775808'],
  355. '(int) PHP_INT_MIN - 1' => [-9223372036854775809, '-9223372036854775808'], //is float: see IEEE 754
  356. '(int) PHP_INT_MIN - 2' => [-9223372036854775810, '-9223372036854775808'], //is float: see IEEE 754
  357. // input like float
  358. '(float) zero' => [0.0, '0'],
  359. '(float) positive' => [5.5, '5.5'],
  360. '(float) round' => [5.0, '5'],
  361. '(float) negative' => [-5.5, '-5.5'],
  362. '(float) round negative' => [-5.0, '-5'],
  363. '(float) small' => [0.000000000003, '0.000000000003'],
  364. '(float) small2' => [64321.0000003, '64321.0000003'],
  365. '(float) fractions' => [-9223372036778.2233, '-9223372036778.223'], //is float: see IEEE 754
  366. '(float) NaN' => [acos(8), null],
  367. '(float) INF' => [INF, null],
  368. '(float) -INF' => [-INF, null],
  369. // boolean input types
  370. '(bool) true' => [true, '1'],
  371. '(bool) false' => [false, '0'],
  372. // other input types
  373. '(other) null' => [null, null],
  374. '(other) empty-array' => [[], null],
  375. '(other) int-array' => [[5], null],
  376. '(other) string-array' => [['5'], null],
  377. '(other) simple object' => [new stdClass(), null],
  378. '(other) Stringable object' => [new Entity(), '[]'],
  379. ];
  380. }
  381. #[DataProvider('toIntProvider')]
  382. public function testToInt(mixed $rawValue, null|int $expected): void
  383. {
  384. $this->assertSame($expected, toInt($rawValue));
  385. }
  386. /**
  387. * @return array The array of test cases.
  388. */
  389. public static function toIntProvider(): array
  390. {
  391. return [
  392. // string input types
  393. '(string) empty' => ['', null],
  394. '(string) space' => [' ', null],
  395. '(string) null' => ['null', null],
  396. '(string) dash' => ['-', null],
  397. '(string) ctz' => ['čťž', null],
  398. '(string) hex' => ['0x539', null],
  399. '(string) binary' => ['0b10100111001', null],
  400. '(string) scientific e' => ['1.2e+2', null],
  401. '(string) scientific E' => ['1.2E+2', null],
  402. '(string) octal old' => ['0123', null],
  403. '(string) octal new' => ['0o123', null],
  404. '(string) decimal php74' => ['1_234_567', null],
  405. '(string) zero' => ['0', 0],
  406. '(string) number' => ['55', 55],
  407. '(string) number_space_before' => [' 55', 55],
  408. '(string) number_space_after' => ['55 ', 55],
  409. '(string) negative number' => ['-5', -5],
  410. '(string) float round' => ['5.0', null],
  411. '(string) float round negative' => ['-5.0', null],
  412. '(string) float real' => ['5.1', null],
  413. '(string) float round slovak' => ['5,0', null],
  414. '(string) money' => ['5 €', null],
  415. '(string) PHP_INT_MAX + 1' => ['9223372036854775808', null],
  416. '(string) PHP_INT_MAX + 0' => ['9223372036854775807', 9223372036854775807],
  417. '(string) PHP_INT_MAX - 1' => ['9223372036854775806', 9223372036854775806],
  418. '(string) PHP_INT_MIN + 1' => ['-9223372036854775807', -9223372036854775807],
  419. '(string) PHP_INT_MIN + 0' => ['-9223372036854775808', null],
  420. '(string) PHP_INT_MIN - 1' => ['-9223372036854775809', null],
  421. '(string) string' => ['f', null],
  422. '(string) partially1 number' => ['5 5', null],
  423. '(string) partially2 number' => ['5x', null],
  424. '(string) partially3 number' => ['x4', null],
  425. '(string) double dot' => ['5.1.0', null],
  426. // int input types
  427. '(int) number' => [55, 55],
  428. '(int) negative number' => [-5, -5],
  429. '(int) PHP_INT_MAX + 1' => [9223372036854775808, -9223372036854775807 - 1], // ¯\_(ツ)_/¯
  430. '(int) PHP_INT_MAX + 0' => [9223372036854775807, 9223372036854775807],
  431. '(int) PHP_INT_MAX - 1' => [9223372036854775806, 9223372036854775806],
  432. '(int) PHP_INT_MIN + 1' => [-9223372036854775807, -9223372036854775807],
  433. // PHP_INT_MIN is float -> PHP inconsistency https://bugs.php.net/bug.php?id=53934
  434. '(int) PHP_INT_MIN + 0' => [-9223372036854775808, -9223372036854775807 - 1], // ¯\_(ツ)_/¯,
  435. '(int) PHP_INT_MIN - 1' => [-9223372036854775809, -9223372036854775807 - 1], // ¯\_(ツ)_/¯,
  436. // float input types
  437. '(float) zero' => [0.0, 0],
  438. '(float) positive' => [5.5, 5],
  439. '(float) round' => [5.0, 5],
  440. '(float) negative' => [-5.5, -5],
  441. '(float) round negative' => [-5.0, -5],
  442. '(float) PHP_INT_MAX + 1' => [9223372036854775808.0, -9223372036854775807 - 1], // ¯\_(ツ)_/¯
  443. '(float) PHP_INT_MAX + 0' => [9223372036854775807.0, -9223372036854775807 - 1], // ¯\_(ツ)_/¯
  444. '(float) PHP_INT_MAX - 1' => [9223372036854775806.0, -9223372036854775807 - 1], // ¯\_(ツ)_/¯
  445. '(float) PHP_INT_MIN + 1' => [-9223372036854775807.0, -9223372036854775807 - 1], // ¯\_(ツ)_/¯
  446. '(float) PHP_INT_MIN + 0' => [-9223372036854775808.0, -9223372036854775807 - 1], // ¯\_(ツ)_/¯
  447. '(float) PHP_INT_MIN - 1' => [-9223372036854775809.0, -9223372036854775807 - 1], // ¯\_(ツ)_/¯
  448. '(float) 2^53 + 2' => [9007199254740994.0, 9007199254740994],
  449. '(float) 2^53 + 1' => [9007199254740993.0, 9007199254740992], // see IEEE 754
  450. '(float) 2^53 + 0' => [9007199254740992.0, 9007199254740992],
  451. '(float) 2^53 - 1' => [9007199254740991.0, 9007199254740991],
  452. '(float) 2^53 - 2' => [9007199254740990.0, 9007199254740990],
  453. '(float) -(2^53) + 2' => [-9007199254740990.0, -9007199254740990],
  454. '(float) -(2^53) + 1' => [-9007199254740991.0, -9007199254740991],
  455. '(float) -(2^53) + 0' => [-9007199254740992.0, -9007199254740992],
  456. '(float) -(2^53) - 1' => [-9007199254740993.0, -9007199254740992], // see IEEE 754
  457. '(float) -(2^53) - 2' => [-9007199254740994.0, -9007199254740994],
  458. '(float) NaN' => [acos(8), null],
  459. '(float) INF' => [INF, null],
  460. '(float) -INF' => [-INF, null],
  461. // boolean input types
  462. '(bool) true' => [true, 1],
  463. '(bool) false' => [false, 0],
  464. // other input types
  465. '(other) null' => [null, null],
  466. '(other) empty-array' => [[], null],
  467. '(other) int-array' => [[5], null],
  468. '(other) string-array' => [['5'], null],
  469. '(other) simple object' => [new stdClass(), null],
  470. ];
  471. }
  472. #[DataProvider('toFloatProvider')]
  473. public function testToFloat(mixed $rawValue, null|float $expected): void
  474. {
  475. $this->assertSame($expected, toFloat($rawValue));
  476. }
  477. /**
  478. * @return array The array of test cases.
  479. */
  480. public static function toFloatProvider(): array
  481. {
  482. return [
  483. // string input types
  484. '(string) empty' => ['', null],
  485. '(string) space' => [' ', null],
  486. '(string) null' => ['null', null],
  487. '(string) dash' => ['-', null],
  488. '(string) ctz' => ['čťž', null],
  489. '(string) hex' => ['0x539', null],
  490. '(string) binary' => ['0b10100111001', null],
  491. '(string) scientific e' => ['1.2e+2', 120.0],
  492. '(string) scientific E' => ['1.2E+2', 120.],
  493. '(string) octal old' => ['0123', 123.0],
  494. '(string) octal new' => ['0o123', null],
  495. '(string) decimal php74' => ['1_234_567', null],
  496. '(string) zero' => ['0', 0.0],
  497. '(string) number' => ['55', 55.0],
  498. '(string) number_space_before' => [' 55', 55.0],
  499. '(string) number_space_after' => ['55 ', 55.0],
  500. '(string) negative number' => ['-5', -5.0],
  501. '(string) float round' => ['5.0', 5.0],
  502. '(string) float round negative' => ['-5.0', -5.0],
  503. '(string) float real' => ['5.1', 5.1],
  504. '(string) float round slovak' => ['5,0', null],
  505. '(string) money' => ['5 €', null],
  506. '(string) PHP_INT_MAX + 1' => ['9223372036854775808', PHP_INT_MAX],
  507. '(string) PHP_INT_MAX + 0' => ['9223372036854775807', 9223372036854775807],
  508. '(string) PHP_INT_MAX - 1' => ['9223372036854775806', 9223372036854775806],
  509. '(string) PHP_INT_MIN + 1' => ['-9223372036854775807', -9223372036854775807],
  510. '(string) PHP_INT_MIN + 0' => ['-9223372036854775808', -9223372036854775807],
  511. '(string) PHP_INT_MIN - 1' => ['-9223372036854775809', -9223372036854775807],
  512. '(string) string' => ['f', null],
  513. '(string) partially1 number' => ['5 5', null],
  514. '(string) partially2 number' => ['5x', null],
  515. '(string) partially3 number' => ['x4', null],
  516. '(string) double dot' => ['5.1.0', null],
  517. // int input types
  518. '(int) number' => [55, 55.0],
  519. '(int) negative number' => [-5, -5.0],
  520. '(int) PHP_INT_MAX + 1' => [9223372036854775808, 9223372036854775807 - 1],
  521. '(int) PHP_INT_MAX + 0' => [9223372036854775807, 9223372036854775807],
  522. '(int) PHP_INT_MAX - 1' => [9223372036854775806, 9223372036854775806],
  523. '(int) PHP_INT_MIN + 1' => [-9223372036854775807, -9223372036854775807],
  524. // PHP_INT_MIN is float -> PHP inconsistency https://bugs.php.net/bug.php?id=53934
  525. '(int) PHP_INT_MIN + 0' => [-9223372036854775808, -9223372036854775807 - 1], // ¯\_(ツ)_/¯,
  526. '(int) PHP_INT_MIN - 1' => [-9223372036854775809, -9223372036854775807 - 1], // ¯\_(ツ)_/¯,
  527. // float input types
  528. '(float) zero' => [0.0, 0.0],
  529. '(float) positive' => [5.5, 5.5],
  530. '(float) round' => [5.0, 5.0],
  531. '(float) negative' => [-5.5, -5.5],
  532. '(float) round negative' => [-5.0, -5.0],
  533. '(float) PHP_INT_MAX + 1' => [9223372036854775808.0, 9223372036854775807 - 1],
  534. '(float) PHP_INT_MAX + 0' => [9223372036854775807.0, 9223372036854775807 - 1],
  535. '(float) PHP_INT_MAX - 1' => [9223372036854775806.0, 9223372036854775807 - 1],
  536. '(float) PHP_INT_MIN + 1' => [-9223372036854775807.0, -9223372036854775807 - 1], // ¯\_(ツ)_/¯
  537. '(float) PHP_INT_MIN + 0' => [-9223372036854775808.0, -9223372036854775807 - 1], // ¯\_(ツ)_/¯
  538. '(float) PHP_INT_MIN - 1' => [-9223372036854775809.0, -9223372036854775807 - 1], // ¯\_(ツ)_/¯
  539. '(float) 2^53 + 2' => [9007199254740994.0, 9007199254740994],
  540. '(float) 2^53 + 1' => [9007199254740993.0, 9007199254740992], // see IEEE 754
  541. '(float) 2^53 + 0' => [9007199254740992.0, 9007199254740992],
  542. '(float) 2^53 - 1' => [9007199254740991.0, 9007199254740991],
  543. '(float) 2^53 - 2' => [9007199254740990.0, 9007199254740990],
  544. '(float) -(2^53) + 2' => [-9007199254740990.0, -9007199254740990],
  545. '(float) -(2^53) + 1' => [-9007199254740991.0, -9007199254740991],
  546. '(float) -(2^53) + 0' => [-9007199254740992.0, -9007199254740992],
  547. '(float) -(2^53) - 1' => [-9007199254740993.0, -9007199254740992], // see IEEE 754
  548. '(float) -(2^53) - 2' => [-9007199254740994.0, -9007199254740994],
  549. '(float) NaN' => [acos(8), null],
  550. '(float) INF' => [INF, null],
  551. '(float) -INF' => [-INF, null],
  552. // boolean input types
  553. '(bool) true' => [true, 1.0],
  554. '(bool) false' => [false, 0.0],
  555. // other input types
  556. '(other) null' => [null, null],
  557. '(other) empty-array' => [[], null],
  558. '(other) int-array' => [[5], null],
  559. '(other) string-array' => [['5'], null],
  560. '(other) simple object' => [new stdClass(), null],
  561. ];
  562. }
  563. #[DataProvider('toBoolProvider')]
  564. public function testToBool(mixed $rawValue, ?bool $expected): void
  565. {
  566. $this->assertSame($expected, toBool($rawValue));
  567. }
  568. /**
  569. * @return array The array of test cases.
  570. */
  571. public static function toBoolProvider(): array
  572. {
  573. return [
  574. // string input types
  575. '(string) empty string' => ['', null],
  576. '(string) space' => [' ', null],
  577. '(string) some word' => ['abc', null],
  578. '(string) double 0' => ['00', null],
  579. '(string) single 0' => ['0', false],
  580. '(string) false' => ['false', null],
  581. '(string) double 1' => ['11', null],
  582. '(string) single 1' => ['1', true],
  583. '(string) true-string' => ['true', null],
  584. // int input types
  585. '(int) 0' => [0, false],
  586. '(int) 1' => [1, true],
  587. '(int) -1' => [-1, null],
  588. '(int) 55' => [55, null],
  589. '(int) negative number' => [-5, null],
  590. // float input types
  591. '(float) positive' => [5.5, null],
  592. '(float) round' => [5.0, null],
  593. '(float) 0.0' => [0.0, false],
  594. '(float) 1.0' => [1.0, true],
  595. '(float) NaN' => [acos(8), null],
  596. '(float) INF' => [INF, null],
  597. '(float) -INF' => [-INF, null],
  598. // boolean input types
  599. '(bool) true' => [true, true],
  600. '(bool) false' => [false, false],
  601. // other input types
  602. '(other) null' => [null, null],
  603. '(other) empty-array' => [[], null],
  604. '(other) int-array' => [[5], null],
  605. '(other) string-array' => [['5'], null],
  606. '(other) simple object' => [new stdClass(), null],
  607. ];
  608. }
  609. }