hamcrest/hamcrest-php
Official PHP port of Hamcrest matchers for expressive assertions in tests. Use MatcherAssert::assertThat() or convenient global functions (assertThat, equalTo, is, both/andAlso, either/orElse) to build readable, composable matchers with PHP-friendly typing.
Start by installing via Composer:
composer require --dev hamcrest/hamcrest-php
Hamcrest integrates directly with PHPUnit — no additional setup is required. In your tests, import matchers via the Hamcrest namespace or use the PHPUnit-provided shortcuts (e.g., assertThat(), equalTo(), arrayHasKey() are auto-imported in modern PHPUnit versions).
Your first use case: replace a verbose assertion like
$this->assertTrue(in_array('foo', $items) && count($items) > 0);
with the intent-clear:
$this->assertThat($items, both(isArray())->and(hasItem('foo')));
Or simply:
$this->assertContains('foo', $items); // PHPUnit wrapper uses Hamcrest under the hood
Begin with the built-in matchers in Hamcrest\Matchers — especially equalTo(), isEmpty(), arrayHasKey(), stringContains(), and anything().
Note for PHP 8.4+ users: Version 2.1.1 resolves implicitly nullable parameter issues in core matchers — no code changes needed, but existing custom matchers relying on null defaults without explicit ?Type declarations may now trigger E_DEPRECATED warnings. If you maintain legacy matchers, ensure method signatures are fully typed (e.g., describeMismatch(?mixed $item)).
1. Composable fluent assertions
Chain matchers for complex conditions:
$this->assertThat($user->getEmail(), allOf(
notNullValue(),
stringContains('@'),
endsWith('.com')
));
2. Custom reusable matchers
Extend Hamcrest\Core\Matcher for domain-specific expectations:
class HasValidId extends Matcher {
public function matches($item): bool
{
return $item instanceof Id && $item->isValid();
}
public function describeTo(Description $description): void
{
$description->appendText('an ID with valid format');
}
public function describeMismatch($item, Description $description): void
{
if (!$item instanceof Id) {
$description->appendText('was ' . get_debug_type($item));
} else {
$description->appendText('had invalid checksum');
}
}
}
// Usage: $this->assertThat($id, new HasValidId());
3. Integration with PHPUnit assertions
Leverage PHPUnit’s assertThat() (aliased as assertThat() or assertThat_() in newer versions) and mix with native assertions:
$this->assertThat($result, arrayWithKey('data'));
$this->assertThat($result['data'], equalTo(['id' => 42]));
4. Better exception testing
Use throwException() or throws() for expressive exception assertions:
$this->assertThat(fn() => $service->process(), throws(\InvalidArgumentException::class));
Hamcrest globally; use explicit imports (e.g., use Hamcrest\Matchers\ArrayMatchers;) to prevent conflicts with Laravel/PHPUnit helpers.not(not(equalTo(1)))) hurts readability — prefer isNot(equalTo(1)) or direct assertNotEquals().hasItem() may be slower for large arrays — test edge cases to avoid O(n²) behavior.describeMismatch() to customize domain-specific mismatch descriptions in custom matchers.declare(strict_types=1);) to avoid runtime warnings.$this->assertThat(
$value,
equalTo(expected: 42, delta: 0.01)
);
How can I help you explore Laravel packages today?