Assert.php 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048
  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. }