Assert.php 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049
  1. <?php
  2. /*
  3. * This file is part of the webmozart/assert package.
  4. *
  5. * (c) Bernhard Schussek <bschussek@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Webmozart\Assert;
  11. use ArrayAccess;
  12. use BadMethodCallException;
  13. use Closure;
  14. use Countable;
  15. use DateTime;
  16. use DateTimeImmutable;
  17. use Exception;
  18. use InvalidArgumentException;
  19. use ResourceBundle;
  20. use SimpleXMLElement;
  21. use Throwable;
  22. use Traversable;
  23. /**
  24. * Efficient assertions to validate the input/output of your methods.
  25. *
  26. * @mixin Mixin
  27. *
  28. * @since 1.0
  29. *
  30. * @author Bernhard Schussek <bschussek@gmail.com>
  31. */
  32. class Assert
  33. {
  34. /**
  35. * @psalm-pure
  36. * @psalm-assert string $value
  37. *
  38. * @param mixed $value
  39. * @param string $message
  40. *
  41. * @throws InvalidArgumentException
  42. */
  43. public static function string($value, $message = '')
  44. {
  45. if (!\is_string($value)) {
  46. static::reportInvalidArgument(\sprintf(
  47. $message ?: 'Expected a string. Got: %s',
  48. static::typeToString($value)
  49. ));
  50. }
  51. }
  52. /**
  53. * @psalm-pure
  54. * @psalm-assert non-empty-string $value
  55. *
  56. * @param mixed $value
  57. * @param string $message
  58. *
  59. * @throws InvalidArgumentException
  60. */
  61. public static function stringNotEmpty($value, $message = '')
  62. {
  63. static::string($value, $message);
  64. static::notEq($value, '', $message);
  65. }
  66. /**
  67. * @psalm-pure
  68. * @psalm-assert int $value
  69. *
  70. * @param mixed $value
  71. * @param string $message
  72. *
  73. * @throws InvalidArgumentException
  74. */
  75. public static function integer($value, $message = '')
  76. {
  77. if (!\is_int($value)) {
  78. static::reportInvalidArgument(\sprintf(
  79. $message ?: 'Expected an integer. Got: %s',
  80. static::typeToString($value)
  81. ));
  82. }
  83. }
  84. /**
  85. * @psalm-pure
  86. * @psalm-assert numeric $value
  87. *
  88. * @param mixed $value
  89. * @param string $message
  90. *
  91. * @throws InvalidArgumentException
  92. */
  93. public static function integerish($value, $message = '')
  94. {
  95. if (!\is_numeric($value) || $value != (int) $value) {
  96. static::reportInvalidArgument(\sprintf(
  97. $message ?: 'Expected an integerish value. Got: %s',
  98. static::typeToString($value)
  99. ));
  100. }
  101. }
  102. /**
  103. * @psalm-pure
  104. * @psalm-assert float $value
  105. *
  106. * @param mixed $value
  107. * @param string $message
  108. *
  109. * @throws InvalidArgumentException
  110. */
  111. public static function float($value, $message = '')
  112. {
  113. if (!\is_float($value)) {
  114. static::reportInvalidArgument(\sprintf(
  115. $message ?: 'Expected a float. Got: %s',
  116. static::typeToString($value)
  117. ));
  118. }
  119. }
  120. /**
  121. * @psalm-pure
  122. * @psalm-assert numeric $value
  123. *
  124. * @param mixed $value
  125. * @param string $message
  126. *
  127. * @throws InvalidArgumentException
  128. */
  129. public static function numeric($value, $message = '')
  130. {
  131. if (!\is_numeric($value)) {
  132. static::reportInvalidArgument(\sprintf(
  133. $message ?: 'Expected a numeric. Got: %s',
  134. static::typeToString($value)
  135. ));
  136. }
  137. }
  138. /**
  139. * @psalm-pure
  140. * @psalm-assert int $value
  141. *
  142. * @param mixed $value
  143. * @param string $message
  144. *
  145. * @throws InvalidArgumentException
  146. */
  147. public static function natural($value, $message = '')
  148. {
  149. if (!\is_int($value) || $value < 0) {
  150. static::reportInvalidArgument(\sprintf(
  151. $message ?: 'Expected a non-negative integer. Got: %s',
  152. static::valueToString($value)
  153. ));
  154. }
  155. }
  156. /**
  157. * @psalm-pure
  158. * @psalm-assert bool $value
  159. *
  160. * @param mixed $value
  161. * @param string $message
  162. *
  163. * @throws InvalidArgumentException
  164. */
  165. public static function boolean($value, $message = '')
  166. {
  167. if (!\is_bool($value)) {
  168. static::reportInvalidArgument(\sprintf(
  169. $message ?: 'Expected a boolean. Got: %s',
  170. static::typeToString($value)
  171. ));
  172. }
  173. }
  174. /**
  175. * @psalm-pure
  176. * @psalm-assert scalar $value
  177. *
  178. * @param mixed $value
  179. * @param string $message
  180. *
  181. * @throws InvalidArgumentException
  182. */
  183. public static function scalar($value, $message = '')
  184. {
  185. if (!\is_scalar($value)) {
  186. static::reportInvalidArgument(\sprintf(
  187. $message ?: 'Expected a scalar. Got: %s',
  188. static::typeToString($value)
  189. ));
  190. }
  191. }
  192. /**
  193. * @psalm-pure
  194. * @psalm-assert object $value
  195. *
  196. * @param mixed $value
  197. * @param string $message
  198. *
  199. * @throws InvalidArgumentException
  200. */
  201. public static function object($value, $message = '')
  202. {
  203. if (!\is_object($value)) {
  204. static::reportInvalidArgument(\sprintf(
  205. $message ?: 'Expected an object. Got: %s',
  206. static::typeToString($value)
  207. ));
  208. }
  209. }
  210. /**
  211. * @psalm-pure
  212. * @psalm-assert resource $value
  213. *
  214. * @param mixed $value
  215. * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php
  216. * @param string $message
  217. *
  218. * @throws InvalidArgumentException
  219. */
  220. public static function resource($value, $type = null, $message = '')
  221. {
  222. if (!\is_resource($value)) {
  223. static::reportInvalidArgument(\sprintf(
  224. $message ?: 'Expected a resource. Got: %s',
  225. static::typeToString($value)
  226. ));
  227. }
  228. if ($type && $type !== \get_resource_type($value)) {
  229. static::reportInvalidArgument(\sprintf(
  230. $message ?: 'Expected a resource of type %2$s. Got: %s',
  231. static::typeToString($value),
  232. $type
  233. ));
  234. }
  235. }
  236. /**
  237. * @psalm-pure
  238. * @psalm-assert callable $value
  239. *
  240. * @param mixed $value
  241. * @param string $message
  242. *
  243. * @throws InvalidArgumentException
  244. */
  245. public static function isCallable($value, $message = '')
  246. {
  247. if (!\is_callable($value)) {
  248. static::reportInvalidArgument(\sprintf(
  249. $message ?: 'Expected a callable. Got: %s',
  250. static::typeToString($value)
  251. ));
  252. }
  253. }
  254. /**
  255. * @psalm-pure
  256. * @psalm-assert array $value
  257. *
  258. * @param mixed $value
  259. * @param string $message
  260. *
  261. * @throws InvalidArgumentException
  262. */
  263. public static function isArray($value, $message = '')
  264. {
  265. if (!\is_array($value)) {
  266. static::reportInvalidArgument(\sprintf(
  267. $message ?: 'Expected an array. Got: %s',
  268. static::typeToString($value)
  269. ));
  270. }
  271. }
  272. /**
  273. * @psalm-pure
  274. * @psalm-assert iterable $value
  275. *
  276. * @deprecated use "isIterable" or "isInstanceOf" instead
  277. *
  278. * @param mixed $value
  279. * @param string $message
  280. *
  281. * @throws InvalidArgumentException
  282. */
  283. public static function isTraversable($value, $message = '')
  284. {
  285. @\trigger_error(
  286. \sprintf(
  287. 'The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.',
  288. __METHOD__
  289. ),
  290. \E_USER_DEPRECATED
  291. );
  292. if (!\is_array($value) && !($value instanceof Traversable)) {
  293. static::reportInvalidArgument(\sprintf(
  294. $message ?: 'Expected a traversable. Got: %s',
  295. static::typeToString($value)
  296. ));
  297. }
  298. }
  299. /**
  300. * @psalm-pure
  301. * @psalm-assert array|ArrayAccess $value
  302. *
  303. * @param mixed $value
  304. * @param string $message
  305. *
  306. * @throws InvalidArgumentException
  307. */
  308. public static function isArrayAccessible($value, $message = '')
  309. {
  310. if (!\is_array($value) && !($value instanceof ArrayAccess)) {
  311. static::reportInvalidArgument(\sprintf(
  312. $message ?: 'Expected an array accessible. Got: %s',
  313. static::typeToString($value)
  314. ));
  315. }
  316. }
  317. /**
  318. * @psalm-pure
  319. * @psalm-assert countable $value
  320. *
  321. * @param mixed $value
  322. * @param string $message
  323. *
  324. * @throws InvalidArgumentException
  325. */
  326. public static function isCountable($value, $message = '')
  327. {
  328. if (
  329. !\is_array($value)
  330. && !($value instanceof Countable)
  331. && !($value instanceof ResourceBundle)
  332. && !($value instanceof SimpleXMLElement)
  333. ) {
  334. static::reportInvalidArgument(\sprintf(
  335. $message ?: 'Expected a countable. Got: %s',
  336. static::typeToString($value)
  337. ));
  338. }
  339. }
  340. /**
  341. * @psalm-pure
  342. * @psalm-assert iterable $value
  343. *
  344. * @param mixed $value
  345. * @param string $message
  346. *
  347. * @throws InvalidArgumentException
  348. */
  349. public static function isIterable($value, $message = '')
  350. {
  351. if (!\is_array($value) && !($value instanceof Traversable)) {
  352. static::reportInvalidArgument(\sprintf(
  353. $message ?: 'Expected an iterable. Got: %s',
  354. static::typeToString($value)
  355. ));
  356. }
  357. }
  358. /**
  359. * @psalm-pure
  360. * @psalm-template ExpectedType of object
  361. * @psalm-param class-string<ExpectedType> $class
  362. * @psalm-assert ExpectedType $value
  363. *
  364. * @param mixed $value
  365. * @param string|object $class
  366. * @param string $message
  367. *
  368. * @throws InvalidArgumentException
  369. */
  370. public static function isInstanceOf($value, $class, $message = '')
  371. {
  372. if (!($value instanceof $class)) {
  373. static::reportInvalidArgument(\sprintf(
  374. $message ?: 'Expected an instance of %2$s. Got: %s',
  375. static::typeToString($value),
  376. $class
  377. ));
  378. }
  379. }
  380. /**
  381. * @psalm-pure
  382. * @psalm-template ExpectedType of object
  383. * @psalm-param class-string<ExpectedType> $class
  384. * @psalm-assert !ExpectedType $value
  385. *
  386. * @param mixed $value
  387. * @param string|object $class
  388. * @param string $message
  389. *
  390. * @throws InvalidArgumentException
  391. */
  392. public static function notInstanceOf($value, $class, $message = '')
  393. {
  394. if ($value instanceof $class) {
  395. static::reportInvalidArgument(\sprintf(
  396. $message ?: 'Expected an instance other than %2$s. Got: %s',
  397. static::typeToString($value),
  398. $class
  399. ));
  400. }
  401. }
  402. /**
  403. * @psalm-pure
  404. * @psalm-param array<class-string> $classes
  405. *
  406. * @param mixed $value
  407. * @param array<object|string> $classes
  408. * @param string $message
  409. *
  410. * @throws InvalidArgumentException
  411. */
  412. public static function isInstanceOfAny($value, array $classes, $message = '')
  413. {
  414. foreach ($classes as $class) {
  415. if ($value instanceof $class) {
  416. return;
  417. }
  418. }
  419. static::reportInvalidArgument(\sprintf(
  420. $message ?: 'Expected an instance of any of %2$s. Got: %s',
  421. static::typeToString($value),
  422. \implode(', ', \array_map(array('static', 'valueToString'), $classes))
  423. ));
  424. }
  425. /**
  426. * @psalm-pure
  427. * @psalm-template ExpectedType of object
  428. * @psalm-param class-string<ExpectedType> $class
  429. * @psalm-assert ExpectedType|class-string<ExpectedType> $value
  430. *
  431. * @param object|string $value
  432. * @param string $class
  433. * @param string $message
  434. *
  435. * @throws InvalidArgumentException
  436. */
  437. public static function isAOf($value, $class, $message = '')
  438. {
  439. static::string($class, 'Expected class as a string. Got: %s');
  440. if (!\is_a($value, $class, \is_string($value))) {
  441. static::reportInvalidArgument(sprintf(
  442. $message ?: 'Expected an instance of this class or to this class among his parents %2$s. Got: %s',
  443. static::typeToString($value),
  444. $class
  445. ));
  446. }
  447. }
  448. /**
  449. * @psalm-pure
  450. * @psalm-template UnexpectedType of object
  451. * @psalm-param class-string<UnexpectedType> $class
  452. * @psalm-assert !UnexpectedType $value
  453. * @psalm-assert !class-string<UnexpectedType> $value
  454. *
  455. * @param object|string $value
  456. * @param string $class
  457. * @param string $message
  458. *
  459. * @throws InvalidArgumentException
  460. */
  461. public static function isNotA($value, $class, $message = '')
  462. {
  463. static::string($class, 'Expected class as a string. Got: %s');
  464. if (\is_a($value, $class, \is_string($value))) {
  465. static::reportInvalidArgument(sprintf(
  466. $message ?: 'Expected an instance of this class or to this class among his parents other than %2$s. Got: %s',
  467. static::typeToString($value),
  468. $class
  469. ));
  470. }
  471. }
  472. /**
  473. * @psalm-pure
  474. * @psalm-param array<class-string> $classes
  475. *
  476. * @param object|string $value
  477. * @param string[] $classes
  478. * @param string $message
  479. *
  480. * @throws InvalidArgumentException
  481. */
  482. public static function isAnyOf($value, array $classes, $message = '')
  483. {
  484. foreach ($classes as $class) {
  485. static::string($class, 'Expected class as a string. Got: %s');
  486. if (\is_a($value, $class, \is_string($value))) {
  487. return;
  488. }
  489. }
  490. static::reportInvalidArgument(sprintf(
  491. $message ?: 'Expected an any of instance of this class or to this class among his parents other than %2$s. Got: %s',
  492. static::typeToString($value),
  493. \implode(', ', \array_map(array('static', 'valueToString'), $classes))
  494. ));
  495. }
  496. /**
  497. * @psalm-pure
  498. * @psalm-assert empty $value
  499. *
  500. * @param mixed $value
  501. * @param string $message
  502. *
  503. * @throws InvalidArgumentException
  504. */
  505. public static function isEmpty($value, $message = '')
  506. {
  507. if (!empty($value)) {
  508. static::reportInvalidArgument(\sprintf(
  509. $message ?: 'Expected an empty value. Got: %s',
  510. static::valueToString($value)
  511. ));
  512. }
  513. }
  514. /**
  515. * @psalm-pure
  516. * @psalm-assert !empty $value
  517. *
  518. * @param mixed $value
  519. * @param string $message
  520. *
  521. * @throws InvalidArgumentException
  522. */
  523. public static function notEmpty($value, $message = '')
  524. {
  525. if (empty($value)) {
  526. static::reportInvalidArgument(\sprintf(
  527. $message ?: 'Expected a non-empty value. Got: %s',
  528. static::valueToString($value)
  529. ));
  530. }
  531. }
  532. /**
  533. * @psalm-pure
  534. * @psalm-assert null $value
  535. *
  536. * @param mixed $value
  537. * @param string $message
  538. *
  539. * @throws InvalidArgumentException
  540. */
  541. public static function null($value, $message = '')
  542. {
  543. if (null !== $value) {
  544. static::reportInvalidArgument(\sprintf(
  545. $message ?: 'Expected null. Got: %s',
  546. static::valueToString($value)
  547. ));
  548. }
  549. }
  550. /**
  551. * @psalm-pure
  552. * @psalm-assert !null $value
  553. *
  554. * @param mixed $value
  555. * @param string $message
  556. *
  557. * @throws InvalidArgumentException
  558. */
  559. public static function notNull($value, $message = '')
  560. {
  561. if (null === $value) {
  562. static::reportInvalidArgument(
  563. $message ?: 'Expected a value other than null.'
  564. );
  565. }
  566. }
  567. /**
  568. * @psalm-pure
  569. * @psalm-assert true $value
  570. *
  571. * @param mixed $value
  572. * @param string $message
  573. *
  574. * @throws InvalidArgumentException
  575. */
  576. public static function true($value, $message = '')
  577. {
  578. if (true !== $value) {
  579. static::reportInvalidArgument(\sprintf(
  580. $message ?: 'Expected a value to be true. Got: %s',
  581. static::valueToString($value)
  582. ));
  583. }
  584. }
  585. /**
  586. * @psalm-pure
  587. * @psalm-assert false $value
  588. *
  589. * @param mixed $value
  590. * @param string $message
  591. *
  592. * @throws InvalidArgumentException
  593. */
  594. public static function false($value, $message = '')
  595. {
  596. if (false !== $value) {
  597. static::reportInvalidArgument(\sprintf(
  598. $message ?: 'Expected a value to be false. Got: %s',
  599. static::valueToString($value)
  600. ));
  601. }
  602. }
  603. /**
  604. * @psalm-pure
  605. * @psalm-assert !false $value
  606. *
  607. * @param mixed $value
  608. * @param string $message
  609. *
  610. * @throws InvalidArgumentException
  611. */
  612. public static function notFalse($value, $message = '')
  613. {
  614. if (false === $value) {
  615. static::reportInvalidArgument(
  616. $message ?: 'Expected a value other than false.'
  617. );
  618. }
  619. }
  620. /**
  621. * @param mixed $value
  622. * @param string $message
  623. *
  624. * @throws InvalidArgumentException
  625. */
  626. public static function ip($value, $message = '')
  627. {
  628. if (false === \filter_var($value, \FILTER_VALIDATE_IP)) {
  629. static::reportInvalidArgument(\sprintf(
  630. $message ?: 'Expected a value to be an IP. Got: %s',
  631. static::valueToString($value)
  632. ));
  633. }
  634. }
  635. /**
  636. * @param mixed $value
  637. * @param string $message
  638. *
  639. * @throws InvalidArgumentException
  640. */
  641. public static function ipv4($value, $message = '')
  642. {
  643. if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
  644. static::reportInvalidArgument(\sprintf(
  645. $message ?: 'Expected a value to be an IPv4. Got: %s',
  646. static::valueToString($value)
  647. ));
  648. }
  649. }
  650. /**
  651. * @param mixed $value
  652. * @param string $message
  653. *
  654. * @throws InvalidArgumentException
  655. */
  656. public static function ipv6($value, $message = '')
  657. {
  658. if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
  659. static::reportInvalidArgument(\sprintf(
  660. $message ?: 'Expected a value to be an IPv6. Got: %s',
  661. static::valueToString($value)
  662. ));
  663. }
  664. }
  665. /**
  666. * @param mixed $value
  667. * @param string $message
  668. *
  669. * @throws InvalidArgumentException
  670. */
  671. public static function email($value, $message = '')
  672. {
  673. if (false === \filter_var($value, FILTER_VALIDATE_EMAIL)) {
  674. static::reportInvalidArgument(\sprintf(
  675. $message ?: 'Expected a value to be a valid e-mail address. Got: %s',
  676. static::valueToString($value)
  677. ));
  678. }
  679. }
  680. /**
  681. * Does non strict comparisons on the items, so ['3', 3] will not pass the assertion.
  682. *
  683. * @param array $values
  684. * @param string $message
  685. *
  686. * @throws InvalidArgumentException
  687. */
  688. public static function uniqueValues(array $values, $message = '')
  689. {
  690. $allValues = \count($values);
  691. $uniqueValues = \count(\array_unique($values));
  692. if ($allValues !== $uniqueValues) {
  693. $difference = $allValues - $uniqueValues;
  694. static::reportInvalidArgument(\sprintf(
  695. $message ?: 'Expected an array of unique values, but %s of them %s duplicated',
  696. $difference,
  697. (1 === $difference ? 'is' : 'are')
  698. ));
  699. }
  700. }
  701. /**
  702. * @param mixed $value
  703. * @param mixed $expect
  704. * @param string $message
  705. *
  706. * @throws InvalidArgumentException
  707. */
  708. public static function eq($value, $expect, $message = '')
  709. {
  710. if ($expect != $value) {
  711. static::reportInvalidArgument(\sprintf(
  712. $message ?: 'Expected a value equal to %2$s. Got: %s',
  713. static::valueToString($value),
  714. static::valueToString($expect)
  715. ));
  716. }
  717. }
  718. /**
  719. * @param mixed $value
  720. * @param mixed $expect
  721. * @param string $message
  722. *
  723. * @throws InvalidArgumentException
  724. */
  725. public static function notEq($value, $expect, $message = '')
  726. {
  727. if ($expect == $value) {
  728. static::reportInvalidArgument(\sprintf(
  729. $message ?: 'Expected a different value than %s.',
  730. static::valueToString($expect)
  731. ));
  732. }
  733. }
  734. /**
  735. * @psalm-pure
  736. *
  737. * @param mixed $value
  738. * @param mixed $expect
  739. * @param string $message
  740. *
  741. * @throws InvalidArgumentException
  742. */
  743. public static function same($value, $expect, $message = '')
  744. {
  745. if ($expect !== $value) {
  746. static::reportInvalidArgument(\sprintf(
  747. $message ?: 'Expected a value identical to %2$s. Got: %s',
  748. static::valueToString($value),
  749. static::valueToString($expect)
  750. ));
  751. }
  752. }
  753. /**
  754. * @psalm-pure
  755. *
  756. * @param mixed $value
  757. * @param mixed $expect
  758. * @param string $message
  759. *
  760. * @throws InvalidArgumentException
  761. */
  762. public static function notSame($value, $expect, $message = '')
  763. {
  764. if ($expect === $value) {
  765. static::reportInvalidArgument(\sprintf(
  766. $message ?: 'Expected a value not identical to %s.',
  767. static::valueToString($expect)
  768. ));
  769. }
  770. }
  771. /**
  772. * @psalm-pure
  773. *
  774. * @param mixed $value
  775. * @param mixed $limit
  776. * @param string $message
  777. *
  778. * @throws InvalidArgumentException
  779. */
  780. public static function greaterThan($value, $limit, $message = '')
  781. {
  782. if ($value <= $limit) {
  783. static::reportInvalidArgument(\sprintf(
  784. $message ?: 'Expected a value greater than %2$s. Got: %s',
  785. static::valueToString($value),
  786. static::valueToString($limit)
  787. ));
  788. }
  789. }
  790. /**
  791. * @psalm-pure
  792. *
  793. * @param mixed $value
  794. * @param mixed $limit
  795. * @param string $message
  796. *
  797. * @throws InvalidArgumentException
  798. */
  799. public static function greaterThanEq($value, $limit, $message = '')
  800. {
  801. if ($value < $limit) {
  802. static::reportInvalidArgument(\sprintf(
  803. $message ?: 'Expected a value greater than or equal to %2$s. Got: %s',
  804. static::valueToString($value),
  805. static::valueToString($limit)
  806. ));
  807. }
  808. }
  809. /**
  810. * @psalm-pure
  811. *
  812. * @param mixed $value
  813. * @param mixed $limit
  814. * @param string $message
  815. *
  816. * @throws InvalidArgumentException
  817. */
  818. public static function lessThan($value, $limit, $message = '')
  819. {
  820. if ($value >= $limit) {
  821. static::reportInvalidArgument(\sprintf(
  822. $message ?: 'Expected a value less than %2$s. Got: %s',
  823. static::valueToString($value),
  824. static::valueToString($limit)
  825. ));
  826. }
  827. }
  828. /**
  829. * @psalm-pure
  830. *
  831. * @param mixed $value
  832. * @param mixed $limit
  833. * @param string $message
  834. *
  835. * @throws InvalidArgumentException
  836. */
  837. public static function lessThanEq($value, $limit, $message = '')
  838. {
  839. if ($value > $limit) {
  840. static::reportInvalidArgument(\sprintf(
  841. $message ?: 'Expected a value less than or equal to %2$s. Got: %s',
  842. static::valueToString($value),
  843. static::valueToString($limit)
  844. ));
  845. }
  846. }
  847. /**
  848. * Inclusive range, so Assert::(3, 3, 5) passes.
  849. *
  850. * @psalm-pure
  851. *
  852. * @param mixed $value
  853. * @param mixed $min
  854. * @param mixed $max
  855. * @param string $message
  856. *
  857. * @throws InvalidArgumentException
  858. */
  859. public static function range($value, $min, $max, $message = '')
  860. {
  861. if ($value < $min || $value > $max) {
  862. static::reportInvalidArgument(\sprintf(
  863. $message ?: 'Expected a value between %2$s and %3$s. Got: %s',
  864. static::valueToString($value),
  865. static::valueToString($min),
  866. static::valueToString($max)
  867. ));
  868. }
  869. }
  870. /**
  871. * A more human-readable alias of Assert::inArray().
  872. *
  873. * @psalm-pure
  874. *
  875. * @param mixed $value
  876. * @param array $values
  877. * @param string $message
  878. *
  879. * @throws InvalidArgumentException
  880. */
  881. public static function oneOf($value, array $values, $message = '')
  882. {
  883. static::inArray($value, $values, $message);
  884. }
  885. /**
  886. * Does strict comparison, so Assert::inArray(3, ['3']) does not pass the assertion.
  887. *
  888. * @psalm-pure
  889. *
  890. * @param mixed $value
  891. * @param array $values
  892. * @param string $message
  893. *
  894. * @throws InvalidArgumentException
  895. */
  896. public static function inArray($value, array $values, $message = '')
  897. {
  898. if (!\in_array($value, $values, true)) {
  899. static::reportInvalidArgument(\sprintf(
  900. $message ?: 'Expected one of: %2$s. Got: %s',
  901. static::valueToString($value),
  902. \implode(', ', \array_map(array('static', 'valueToString'), $values))
  903. ));
  904. }
  905. }
  906. /**
  907. * @psalm-pure
  908. *
  909. * @param string $value
  910. * @param string $subString
  911. * @param string $message
  912. *
  913. * @throws InvalidArgumentException
  914. */
  915. public static function contains($value, $subString, $message = '')
  916. {
  917. if (false === \strpos($value, $subString)) {
  918. static::reportInvalidArgument(\sprintf(
  919. $message ?: 'Expected a value to contain %2$s. Got: %s',
  920. static::valueToString($value),
  921. static::valueToString($subString)
  922. ));
  923. }
  924. }
  925. /**
  926. * @psalm-pure
  927. *
  928. * @param string $value
  929. * @param string $subString
  930. * @param string $message
  931. *
  932. * @throws InvalidArgumentException
  933. */
  934. public static function notContains($value, $subString, $message = '')
  935. {
  936. if (false !== \strpos($value, $subString)) {
  937. static::reportInvalidArgument(\sprintf(
  938. $message ?: '%2$s was not expected to be contained in a value. Got: %s',
  939. static::valueToString($value),
  940. static::valueToString($subString)
  941. ));
  942. }
  943. }
  944. /**
  945. * @psalm-pure
  946. *
  947. * @param string $value
  948. * @param string $message
  949. *
  950. * @throws InvalidArgumentException
  951. */
  952. public static function notWhitespaceOnly($value, $message = '')
  953. {
  954. if (\preg_match('/^\s*$/', $value)) {
  955. static::reportInvalidArgument(\sprintf(
  956. $message ?: 'Expected a non-whitespace string. Got: %s',
  957. static::valueToString($value)
  958. ));
  959. }
  960. }
  961. /**
  962. * @psalm-pure
  963. *
  964. * @param string $value
  965. * @param string $prefix
  966. * @param string $message
  967. *
  968. * @throws InvalidArgumentException
  969. */
  970. public static function startsWith($value, $prefix, $message = '')
  971. {
  972. if (0 !== \strpos($value, $prefix)) {
  973. static::reportInvalidArgument(\sprintf(
  974. $message ?: 'Expected a value to start with %2$s. Got: %s',
  975. static::valueToString($value),
  976. static::valueToString($prefix)
  977. ));
  978. }
  979. }
  980. /**
  981. * @psalm-pure
  982. *
  983. * @param string $value
  984. * @param string $prefix
  985. * @param string $message
  986. *
  987. * @throws InvalidArgumentException
  988. */
  989. public static function notStartsWith($value, $prefix, $message = '')
  990. {
  991. if (0 === \strpos($value, $prefix)) {
  992. static::reportInvalidArgument(\sprintf(
  993. $message ?: 'Expected a value not to start with %2$s. Got: %s',
  994. static::valueToString($value),
  995. static::valueToString($prefix)
  996. ));
  997. }
  998. }
  999. /**
  1000. * @psalm-pure
  1001. *
  1002. * @param mixed $value
  1003. * @param string $message
  1004. *
  1005. * @throws InvalidArgumentException
  1006. */
  1007. public static function startsWithLetter($value, $message = '')
  1008. {
  1009. static::string($value);
  1010. $valid = isset($value[0]);
  1011. if ($valid) {
  1012. $locale = \setlocale(LC_CTYPE, 0);
  1013. \setlocale(LC_CTYPE, 'C');
  1014. $valid = \ctype_alpha($value[0]);
  1015. \setlocale(LC_CTYPE, $locale);
  1016. }
  1017. if (!$valid) {
  1018. static::reportInvalidArgument(\sprintf(
  1019. $message ?: 'Expected a value to start with a letter. Got: %s',
  1020. static::valueToString($value)
  1021. ));
  1022. }
  1023. }
  1024. /**
  1025. * @psalm-pure
  1026. *
  1027. * @param string $value
  1028. * @param string $suffix
  1029. * @param string $message
  1030. *
  1031. * @throws InvalidArgumentException
  1032. */
  1033. public static function endsWith($value, $suffix, $message = '')
  1034. {
  1035. if ($suffix !== \substr($value, -\strlen($suffix))) {
  1036. static::reportInvalidArgument(\sprintf(
  1037. $message ?: 'Expected a value to end with %2$s. Got: %s',
  1038. static::valueToString($value),
  1039. static::valueToString($suffix)
  1040. ));
  1041. }
  1042. }
  1043. /**
  1044. * @psalm-pure
  1045. *
  1046. * @param string $value
  1047. * @param string $suffix
  1048. * @param string $message
  1049. *
  1050. * @throws InvalidArgumentException
  1051. */
  1052. public static function notEndsWith($value, $suffix, $message = '')
  1053. {
  1054. if ($suffix === \substr($value, -\strlen($suffix))) {
  1055. static::reportInvalidArgument(\sprintf(
  1056. $message ?: 'Expected a value not to end with %2$s. Got: %s',
  1057. static::valueToString($value),
  1058. static::valueToString($suffix)
  1059. ));
  1060. }
  1061. }
  1062. /**
  1063. * @psalm-pure
  1064. *
  1065. * @param string $value
  1066. * @param string $pattern
  1067. * @param string $message
  1068. *
  1069. * @throws InvalidArgumentException
  1070. */
  1071. public static function regex($value, $pattern, $message = '')
  1072. {
  1073. if (!\preg_match($pattern, $value)) {
  1074. static::reportInvalidArgument(\sprintf(
  1075. $message ?: 'The value %s does not match the expected pattern.',
  1076. static::valueToString($value)
  1077. ));
  1078. }
  1079. }
  1080. /**
  1081. * @psalm-pure
  1082. *
  1083. * @param string $value
  1084. * @param string $pattern
  1085. * @param string $message
  1086. *
  1087. * @throws InvalidArgumentException
  1088. */
  1089. public static function notRegex($value, $pattern, $message = '')
  1090. {
  1091. if (\preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) {
  1092. static::reportInvalidArgument(\sprintf(
  1093. $message ?: 'The value %s matches the pattern %s (at offset %d).',
  1094. static::valueToString($value),
  1095. static::valueToString($pattern),
  1096. $matches[0][1]
  1097. ));
  1098. }
  1099. }
  1100. /**
  1101. * @psalm-pure
  1102. *
  1103. * @param mixed $value
  1104. * @param string $message
  1105. *
  1106. * @throws InvalidArgumentException
  1107. */
  1108. public static function unicodeLetters($value, $message = '')
  1109. {
  1110. static::string($value);
  1111. if (!\preg_match('/^\p{L}+$/u', $value)) {
  1112. static::reportInvalidArgument(\sprintf(
  1113. $message ?: 'Expected a value to contain only Unicode letters. Got: %s',
  1114. static::valueToString($value)
  1115. ));
  1116. }
  1117. }
  1118. /**
  1119. * @psalm-pure
  1120. *
  1121. * @param mixed $value
  1122. * @param string $message
  1123. *
  1124. * @throws InvalidArgumentException
  1125. */
  1126. public static function alpha($value, $message = '')
  1127. {
  1128. static::string($value);
  1129. $locale = \setlocale(LC_CTYPE, 0);
  1130. \setlocale(LC_CTYPE, 'C');
  1131. $valid = !\ctype_alpha($value);
  1132. \setlocale(LC_CTYPE, $locale);
  1133. if ($valid) {
  1134. static::reportInvalidArgument(\sprintf(
  1135. $message ?: 'Expected a value to contain only letters. Got: %s',
  1136. static::valueToString($value)
  1137. ));
  1138. }
  1139. }
  1140. /**
  1141. * @psalm-pure
  1142. *
  1143. * @param string $value
  1144. * @param string $message
  1145. *
  1146. * @throws InvalidArgumentException
  1147. */
  1148. public static function digits($value, $message = '')
  1149. {
  1150. $locale = \setlocale(LC_CTYPE, 0);
  1151. \setlocale(LC_CTYPE, 'C');
  1152. $valid = !\ctype_digit($value);
  1153. \setlocale(LC_CTYPE, $locale);
  1154. if ($valid) {
  1155. static::reportInvalidArgument(\sprintf(
  1156. $message ?: 'Expected a value to contain digits only. Got: %s',
  1157. static::valueToString($value)
  1158. ));
  1159. }
  1160. }
  1161. /**
  1162. * @psalm-pure
  1163. *
  1164. * @param string $value
  1165. * @param string $message
  1166. *
  1167. * @throws InvalidArgumentException
  1168. */
  1169. public static function alnum($value, $message = '')
  1170. {
  1171. $locale = \setlocale(LC_CTYPE, 0);
  1172. \setlocale(LC_CTYPE, 'C');
  1173. $valid = !\ctype_alnum($value);
  1174. \setlocale(LC_CTYPE, $locale);
  1175. if ($valid) {
  1176. static::reportInvalidArgument(\sprintf(
  1177. $message ?: 'Expected a value to contain letters and digits only. Got: %s',
  1178. static::valueToString($value)
  1179. ));
  1180. }
  1181. }
  1182. /**
  1183. * @psalm-pure
  1184. * @psalm-assert lowercase-string $value
  1185. *
  1186. * @param string $value
  1187. * @param string $message
  1188. *
  1189. * @throws InvalidArgumentException
  1190. */
  1191. public static function lower($value, $message = '')
  1192. {
  1193. $locale = \setlocale(LC_CTYPE, 0);
  1194. \setlocale(LC_CTYPE, 'C');
  1195. $valid = !\ctype_lower($value);
  1196. \setlocale(LC_CTYPE, $locale);
  1197. if ($valid) {
  1198. static::reportInvalidArgument(\sprintf(
  1199. $message ?: 'Expected a value to contain lowercase characters only. Got: %s',
  1200. static::valueToString($value)
  1201. ));
  1202. }
  1203. }
  1204. /**
  1205. * @psalm-pure
  1206. * @psalm-assert !lowercase-string $value
  1207. *
  1208. * @param string $value
  1209. * @param string $message
  1210. *
  1211. * @throws InvalidArgumentException
  1212. */
  1213. public static function upper($value, $message = '')
  1214. {
  1215. $locale = \setlocale(LC_CTYPE, 0);
  1216. \setlocale(LC_CTYPE, 'C');
  1217. $valid = !\ctype_upper($value);
  1218. \setlocale(LC_CTYPE, $locale);
  1219. if ($valid) {
  1220. static::reportInvalidArgument(\sprintf(
  1221. $message ?: 'Expected a value to contain uppercase characters only. Got: %s',
  1222. static::valueToString($value)
  1223. ));
  1224. }
  1225. }
  1226. /**
  1227. * @psalm-pure
  1228. *
  1229. * @param string $value
  1230. * @param int $length
  1231. * @param string $message
  1232. *
  1233. * @throws InvalidArgumentException
  1234. */
  1235. public static function length($value, $length, $message = '')
  1236. {
  1237. if ($length !== static::strlen($value)) {
  1238. static::reportInvalidArgument(\sprintf(
  1239. $message ?: 'Expected a value to contain %2$s characters. Got: %s',
  1240. static::valueToString($value),
  1241. $length
  1242. ));
  1243. }
  1244. }
  1245. /**
  1246. * Inclusive min.
  1247. *
  1248. * @psalm-pure
  1249. *
  1250. * @param string $value
  1251. * @param int|float $min
  1252. * @param string $message
  1253. *
  1254. * @throws InvalidArgumentException
  1255. */
  1256. public static function minLength($value, $min, $message = '')
  1257. {
  1258. if (static::strlen($value) < $min) {
  1259. static::reportInvalidArgument(\sprintf(
  1260. $message ?: 'Expected a value to contain at least %2$s characters. Got: %s',
  1261. static::valueToString($value),
  1262. $min
  1263. ));
  1264. }
  1265. }
  1266. /**
  1267. * Inclusive max.
  1268. *
  1269. * @psalm-pure
  1270. *
  1271. * @param string $value
  1272. * @param int|float $max
  1273. * @param string $message
  1274. *
  1275. * @throws InvalidArgumentException
  1276. */
  1277. public static function maxLength($value, $max, $message = '')
  1278. {
  1279. if (static::strlen($value) > $max) {
  1280. static::reportInvalidArgument(\sprintf(
  1281. $message ?: 'Expected a value to contain at most %2$s characters. Got: %s',
  1282. static::valueToString($value),
  1283. $max
  1284. ));
  1285. }
  1286. }
  1287. /**
  1288. * Inclusive , so Assert::lengthBetween('asd', 3, 5); passes the assertion.
  1289. *
  1290. * @psalm-pure
  1291. *
  1292. * @param string $value
  1293. * @param int|float $min
  1294. * @param int|float $max
  1295. * @param string $message
  1296. *
  1297. * @throws InvalidArgumentException
  1298. */
  1299. public static function lengthBetween($value, $min, $max, $message = '')
  1300. {
  1301. $length = static::strlen($value);
  1302. if ($length < $min || $length > $max) {
  1303. static::reportInvalidArgument(\sprintf(
  1304. $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s',
  1305. static::valueToString($value),
  1306. $min,
  1307. $max
  1308. ));
  1309. }
  1310. }
  1311. /**
  1312. * Will also pass if $value is a directory, use Assert::file() instead if you need to be sure it is a file.
  1313. *
  1314. * @param mixed $value
  1315. * @param string $message
  1316. *
  1317. * @throws InvalidArgumentException
  1318. */
  1319. public static function fileExists($value, $message = '')
  1320. {
  1321. static::string($value);
  1322. if (!\file_exists($value)) {
  1323. static::reportInvalidArgument(\sprintf(
  1324. $message ?: 'The file %s does not exist.',
  1325. static::valueToString($value)
  1326. ));
  1327. }
  1328. }
  1329. /**
  1330. * @param mixed $value
  1331. * @param string $message
  1332. *
  1333. * @throws InvalidArgumentException
  1334. */
  1335. public static function file($value, $message = '')
  1336. {
  1337. static::fileExists($value, $message);
  1338. if (!\is_file($value)) {
  1339. static::reportInvalidArgument(\sprintf(
  1340. $message ?: 'The path %s is not a file.',
  1341. static::valueToString($value)
  1342. ));
  1343. }
  1344. }
  1345. /**
  1346. * @param mixed $value
  1347. * @param string $message
  1348. *
  1349. * @throws InvalidArgumentException
  1350. */
  1351. public static function directory($value, $message = '')
  1352. {
  1353. static::fileExists($value, $message);
  1354. if (!\is_dir($value)) {
  1355. static::reportInvalidArgument(\sprintf(
  1356. $message ?: 'The path %s is no directory.',
  1357. static::valueToString($value)
  1358. ));
  1359. }
  1360. }
  1361. /**
  1362. * @param string $value
  1363. * @param string $message
  1364. *
  1365. * @throws InvalidArgumentException
  1366. */
  1367. public static function readable($value, $message = '')
  1368. {
  1369. if (!\is_readable($value)) {
  1370. static::reportInvalidArgument(\sprintf(
  1371. $message ?: 'The path %s is not readable.',
  1372. static::valueToString($value)
  1373. ));
  1374. }
  1375. }
  1376. /**
  1377. * @param string $value
  1378. * @param string $message
  1379. *
  1380. * @throws InvalidArgumentException
  1381. */
  1382. public static function writable($value, $message = '')
  1383. {
  1384. if (!\is_writable($value)) {
  1385. static::reportInvalidArgument(\sprintf(
  1386. $message ?: 'The path %s is not writable.',
  1387. static::valueToString($value)
  1388. ));
  1389. }
  1390. }
  1391. /**
  1392. * @psalm-assert class-string $value
  1393. *
  1394. * @param mixed $value
  1395. * @param string $message
  1396. *
  1397. * @throws InvalidArgumentException
  1398. */
  1399. public static function classExists($value, $message = '')
  1400. {
  1401. if (!\class_exists($value)) {
  1402. static::reportInvalidArgument(\sprintf(
  1403. $message ?: 'Expected an existing class name. Got: %s',
  1404. static::valueToString($value)
  1405. ));
  1406. }
  1407. }
  1408. /**
  1409. * @psalm-pure
  1410. * @psalm-template ExpectedType of object
  1411. * @psalm-param class-string<ExpectedType> $class
  1412. * @psalm-assert class-string<ExpectedType>|ExpectedType $value
  1413. *
  1414. * @param mixed $value
  1415. * @param string|object $class
  1416. * @param string $message
  1417. *
  1418. * @throws InvalidArgumentException
  1419. */
  1420. public static function subclassOf($value, $class, $message = '')
  1421. {
  1422. if (!\is_subclass_of($value, $class)) {
  1423. static::reportInvalidArgument(\sprintf(
  1424. $message ?: 'Expected a sub-class of %2$s. Got: %s',
  1425. static::valueToString($value),
  1426. static::valueToString($class)
  1427. ));
  1428. }
  1429. }
  1430. /**
  1431. * @psalm-assert class-string $value
  1432. *
  1433. * @param mixed $value
  1434. * @param string $message
  1435. *
  1436. * @throws InvalidArgumentException
  1437. */
  1438. public static function interfaceExists($value, $message = '')
  1439. {
  1440. if (!\interface_exists($value)) {
  1441. static::reportInvalidArgument(\sprintf(
  1442. $message ?: 'Expected an existing interface name. got %s',
  1443. static::valueToString($value)
  1444. ));
  1445. }
  1446. }
  1447. /**
  1448. * @psalm-pure
  1449. * @psalm-template ExpectedType of object
  1450. * @psalm-param class-string<ExpectedType> $interface
  1451. * @psalm-assert class-string<ExpectedType> $value
  1452. *
  1453. * @param mixed $value
  1454. * @param mixed $interface
  1455. * @param string $message
  1456. *
  1457. * @throws InvalidArgumentException
  1458. */
  1459. public static function implementsInterface($value, $interface, $message = '')
  1460. {
  1461. if (!\in_array($interface, \class_implements($value))) {
  1462. static::reportInvalidArgument(\sprintf(
  1463. $message ?: 'Expected an implementation of %2$s. Got: %s',
  1464. static::valueToString($value),
  1465. static::valueToString($interface)
  1466. ));
  1467. }
  1468. }
  1469. /**
  1470. * @psalm-pure
  1471. * @psalm-param class-string|object $classOrObject
  1472. *
  1473. * @param string|object $classOrObject
  1474. * @param mixed $property
  1475. * @param string $message
  1476. *
  1477. * @throws InvalidArgumentException
  1478. */
  1479. public static function propertyExists($classOrObject, $property, $message = '')
  1480. {
  1481. if (!\property_exists($classOrObject, $property)) {
  1482. static::reportInvalidArgument(\sprintf(
  1483. $message ?: 'Expected the property %s to exist.',
  1484. static::valueToString($property)
  1485. ));
  1486. }
  1487. }
  1488. /**
  1489. * @psalm-pure
  1490. * @psalm-param class-string|object $classOrObject
  1491. *
  1492. * @param string|object $classOrObject
  1493. * @param mixed $property
  1494. * @param string $message
  1495. *
  1496. * @throws InvalidArgumentException
  1497. */
  1498. public static function propertyNotExists($classOrObject, $property, $message = '')
  1499. {
  1500. if (\property_exists($classOrObject, $property)) {
  1501. static::reportInvalidArgument(\sprintf(
  1502. $message ?: 'Expected the property %s to not exist.',
  1503. static::valueToString($property)
  1504. ));
  1505. }
  1506. }
  1507. /**
  1508. * @psalm-pure
  1509. * @psalm-param class-string|object $classOrObject
  1510. *
  1511. * @param string|object $classOrObject
  1512. * @param mixed $method
  1513. * @param string $message
  1514. *
  1515. * @throws InvalidArgumentException
  1516. */
  1517. public static function methodExists($classOrObject, $method, $message = '')
  1518. {
  1519. if (!(\is_string($classOrObject) || \is_object($classOrObject)) || !\method_exists($classOrObject, $method)) {
  1520. static::reportInvalidArgument(\sprintf(
  1521. $message ?: 'Expected the method %s to exist.',
  1522. static::valueToString($method)
  1523. ));
  1524. }
  1525. }
  1526. /**
  1527. * @psalm-pure
  1528. * @psalm-param class-string|object $classOrObject
  1529. *
  1530. * @param string|object $classOrObject
  1531. * @param mixed $method
  1532. * @param string $message
  1533. *
  1534. * @throws InvalidArgumentException
  1535. */
  1536. public static function methodNotExists($classOrObject, $method, $message = '')
  1537. {
  1538. if ((\is_string($classOrObject) || \is_object($classOrObject)) && \method_exists($classOrObject, $method)) {
  1539. static::reportInvalidArgument(\sprintf(
  1540. $message ?: 'Expected the method %s to not exist.',
  1541. static::valueToString($method)
  1542. ));
  1543. }
  1544. }
  1545. /**
  1546. * @psalm-pure
  1547. *
  1548. * @param array $array
  1549. * @param string|int $key
  1550. * @param string $message
  1551. *
  1552. * @throws InvalidArgumentException
  1553. */
  1554. public static function keyExists($array, $key, $message = '')
  1555. {
  1556. if (!(isset($array[$key]) || \array_key_exists($key, $array))) {
  1557. static::reportInvalidArgument(\sprintf(
  1558. $message ?: 'Expected the key %s to exist.',
  1559. static::valueToString($key)
  1560. ));
  1561. }
  1562. }
  1563. /**
  1564. * @psalm-pure
  1565. *
  1566. * @param array $array
  1567. * @param string|int $key
  1568. * @param string $message
  1569. *
  1570. * @throws InvalidArgumentException
  1571. */
  1572. public static function keyNotExists($array, $key, $message = '')
  1573. {
  1574. if (isset($array[$key]) || \array_key_exists($key, $array)) {
  1575. static::reportInvalidArgument(\sprintf(
  1576. $message ?: 'Expected the key %s to not exist.',
  1577. static::valueToString($key)
  1578. ));
  1579. }
  1580. }
  1581. /**
  1582. * Checks if a value is a valid array key (int or string).
  1583. *
  1584. * @psalm-pure
  1585. * @psalm-assert array-key $value
  1586. *
  1587. * @param mixed $value
  1588. * @param string $message
  1589. *
  1590. * @throws InvalidArgumentException
  1591. */
  1592. public static function validArrayKey($value, $message = '')
  1593. {
  1594. if (!(\is_int($value) || \is_string($value))) {
  1595. static::reportInvalidArgument(\sprintf(
  1596. $message ?: 'Expected string or integer. Got: %s',
  1597. static::typeToString($value)
  1598. ));
  1599. }
  1600. }
  1601. /**
  1602. * Does not check if $array is countable, this can generate a warning on php versions after 7.2.
  1603. *
  1604. * @param Countable|array $array
  1605. * @param int $number
  1606. * @param string $message
  1607. *
  1608. * @throws InvalidArgumentException
  1609. */
  1610. public static function count($array, $number, $message = '')
  1611. {
  1612. static::eq(
  1613. \count($array),
  1614. $number,
  1615. \sprintf(
  1616. $message ?: 'Expected an array to contain %d elements. Got: %d.',
  1617. $number,
  1618. \count($array)
  1619. )
  1620. );
  1621. }
  1622. /**
  1623. * Does not check if $array is countable, this can generate a warning on php versions after 7.2.
  1624. *
  1625. * @param Countable|array $array
  1626. * @param int|float $min
  1627. * @param string $message
  1628. *
  1629. * @throws InvalidArgumentException
  1630. */
  1631. public static function minCount($array, $min, $message = '')
  1632. {
  1633. if (\count($array) < $min) {
  1634. static::reportInvalidArgument(\sprintf(
  1635. $message ?: 'Expected an array to contain at least %2$d elements. Got: %d',
  1636. \count($array),
  1637. $min
  1638. ));
  1639. }
  1640. }
  1641. /**
  1642. * Does not check if $array is countable, this can generate a warning on php versions after 7.2.
  1643. *
  1644. * @param Countable|array $array
  1645. * @param int|float $max
  1646. * @param string $message
  1647. *
  1648. * @throws InvalidArgumentException
  1649. */
  1650. public static function maxCount($array, $max, $message = '')
  1651. {
  1652. if (\count($array) > $max) {
  1653. static::reportInvalidArgument(\sprintf(
  1654. $message ?: 'Expected an array to contain at most %2$d elements. Got: %d',
  1655. \count($array),
  1656. $max
  1657. ));
  1658. }
  1659. }
  1660. /**
  1661. * Does not check if $array is countable, this can generate a warning on php versions after 7.2.
  1662. *
  1663. * @param Countable|array $array
  1664. * @param int|float $min
  1665. * @param int|float $max
  1666. * @param string $message
  1667. *
  1668. * @throws InvalidArgumentException
  1669. */
  1670. public static function countBetween($array, $min, $max, $message = '')
  1671. {
  1672. $count = \count($array);
  1673. if ($count < $min || $count > $max) {
  1674. static::reportInvalidArgument(\sprintf(
  1675. $message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d',
  1676. $count,
  1677. $min,
  1678. $max
  1679. ));
  1680. }
  1681. }
  1682. /**
  1683. * @psalm-pure
  1684. * @psalm-assert list $array
  1685. *
  1686. * @param mixed $array
  1687. * @param string $message
  1688. *
  1689. * @throws InvalidArgumentException
  1690. */
  1691. public static function isList($array, $message = '')
  1692. {
  1693. if (!\is_array($array) || $array !== \array_values($array)) {
  1694. static::reportInvalidArgument(
  1695. $message ?: 'Expected list - non-associative array.'
  1696. );
  1697. }
  1698. }
  1699. /**
  1700. * @psalm-pure
  1701. * @psalm-assert non-empty-list $array
  1702. *
  1703. * @param mixed $array
  1704. * @param string $message
  1705. *
  1706. * @throws InvalidArgumentException
  1707. */
  1708. public static function isNonEmptyList($array, $message = '')
  1709. {
  1710. static::isList($array, $message);
  1711. static::notEmpty($array, $message);
  1712. }
  1713. /**
  1714. * @psalm-pure
  1715. * @psalm-template T
  1716. * @psalm-param mixed|array<T> $array
  1717. * @psalm-assert array<string, T> $array
  1718. *
  1719. * @param mixed $array
  1720. * @param string $message
  1721. *
  1722. * @throws InvalidArgumentException
  1723. */
  1724. public static function isMap($array, $message = '')
  1725. {
  1726. if (
  1727. !\is_array($array) ||
  1728. \array_keys($array) !== \array_filter(\array_keys($array), '\is_string')
  1729. ) {
  1730. static::reportInvalidArgument(
  1731. $message ?: 'Expected map - associative array with string keys.'
  1732. );
  1733. }
  1734. }
  1735. /**
  1736. * @psalm-pure
  1737. * @psalm-template T
  1738. * @psalm-param mixed|array<T> $array
  1739. * @psalm-assert array<string, T> $array
  1740. * @psalm-assert !empty $array
  1741. *
  1742. * @param mixed $array
  1743. * @param string $message
  1744. *
  1745. * @throws InvalidArgumentException
  1746. */
  1747. public static function isNonEmptyMap($array, $message = '')
  1748. {
  1749. static::isMap($array, $message);
  1750. static::notEmpty($array, $message);
  1751. }
  1752. /**
  1753. * @psalm-pure
  1754. *
  1755. * @param string $value
  1756. * @param string $message
  1757. *
  1758. * @throws InvalidArgumentException
  1759. */
  1760. public static function uuid($value, $message = '')
  1761. {
  1762. $value = \str_replace(array('urn:', 'uuid:', '{', '}'), '', $value);
  1763. // The nil UUID is special form of UUID that is specified to have all
  1764. // 128 bits set to zero.
  1765. if ('00000000-0000-0000-0000-000000000000' === $value) {
  1766. return;
  1767. }
  1768. if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) {
  1769. static::reportInvalidArgument(\sprintf(
  1770. $message ?: 'Value %s is not a valid UUID.',
  1771. static::valueToString($value)
  1772. ));
  1773. }
  1774. }
  1775. /**
  1776. * @psalm-param class-string<Throwable> $class
  1777. *
  1778. * @param Closure $expression
  1779. * @param string $class
  1780. * @param string $message
  1781. *
  1782. * @throws InvalidArgumentException
  1783. */
  1784. public static function throws(Closure $expression, $class = 'Exception', $message = '')
  1785. {
  1786. static::string($class);
  1787. $actual = 'none';
  1788. try {
  1789. $expression();
  1790. } catch (Exception $e) {
  1791. $actual = \get_class($e);
  1792. if ($e instanceof $class) {
  1793. return;
  1794. }
  1795. } catch (Throwable $e) {
  1796. $actual = \get_class($e);
  1797. if ($e instanceof $class) {
  1798. return;
  1799. }
  1800. }
  1801. static::reportInvalidArgument($message ?: \sprintf(
  1802. 'Expected to throw "%s", got "%s"',
  1803. $class,
  1804. $actual
  1805. ));
  1806. }
  1807. /**
  1808. * @throws BadMethodCallException
  1809. */
  1810. public static function __callStatic($name, $arguments)
  1811. {
  1812. if ('nullOr' === \substr($name, 0, 6)) {
  1813. if (null !== $arguments[0]) {
  1814. $method = \lcfirst(\substr($name, 6));
  1815. \call_user_func_array(array('static', $method), $arguments);
  1816. }
  1817. return;
  1818. }
  1819. if ('all' === \substr($name, 0, 3)) {
  1820. static::isIterable($arguments[0]);
  1821. $method = \lcfirst(\substr($name, 3));
  1822. $args = $arguments;
  1823. foreach ($arguments[0] as $entry) {
  1824. $args[0] = $entry;
  1825. \call_user_func_array(array('static', $method), $args);
  1826. }
  1827. return;
  1828. }
  1829. throw new BadMethodCallException('No such method: '.$name);
  1830. }
  1831. /**
  1832. * @param mixed $value
  1833. *
  1834. * @return string
  1835. */
  1836. protected static function valueToString($value)
  1837. {
  1838. if (null === $value) {
  1839. return 'null';
  1840. }
  1841. if (true === $value) {
  1842. return 'true';
  1843. }
  1844. if (false === $value) {
  1845. return 'false';
  1846. }
  1847. if (\is_array($value)) {
  1848. return 'array';
  1849. }
  1850. if (\is_object($value)) {
  1851. if (\method_exists($value, '__toString')) {
  1852. return \get_class($value).': '.self::valueToString($value->__toString());
  1853. }
  1854. if ($value instanceof DateTime || $value instanceof DateTimeImmutable) {
  1855. return \get_class($value).': '.self::valueToString($value->format('c'));
  1856. }
  1857. return \get_class($value);
  1858. }
  1859. if (\is_resource($value)) {
  1860. return 'resource';
  1861. }
  1862. if (\is_string($value)) {
  1863. return '"'.$value.'"';
  1864. }
  1865. return (string) $value;
  1866. }
  1867. /**
  1868. * @param mixed $value
  1869. *
  1870. * @return string
  1871. */
  1872. protected static function typeToString($value)
  1873. {
  1874. return \is_object($value) ? \get_class($value) : \gettype($value);
  1875. }
  1876. protected static function strlen($value)
  1877. {
  1878. if (!\function_exists('mb_detect_encoding')) {
  1879. return \strlen($value);
  1880. }
  1881. if (false === $encoding = \mb_detect_encoding($value)) {
  1882. return \strlen($value);
  1883. }
  1884. return \mb_strlen($value, $encoding);
  1885. }
  1886. /**
  1887. * @param string $message
  1888. *
  1889. * @throws InvalidArgumentException
  1890. *
  1891. * @psalm-pure this method is not supposed to perform side-effects
  1892. */
  1893. protected static function reportInvalidArgument($message)
  1894. {
  1895. throw new InvalidArgumentException($message);
  1896. }
  1897. private function __construct()
  1898. {
  1899. }
  1900. }