# hamcrest-php: the official PHP port of Hamcrest matchers

> The official PHP port of Hamcrest keeps the Java matcher vocabulary and adapts it where PHP forces a change. It is a good fit for teams that want self-describing assertions in PHPUnit, and a poor one for anyone expecting full Java parity.

**hamcrest/hamcrest-php** — PHP Hamcrest implementation [Official]

- Repository: https://github.com/hamcrest/hamcrest-php
- Stars: 6,991 · Forks: 47
- Language: PHP
- License: NOASSERTION
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/hamcrest-hamcrest-php

## What hamcrest-php is for, and who should reach for it

hamcrest-php is the official PHP port of Hamcrest, a matching library originally written for Java and subsequently ported to many other languages. The README states that it essentially follows a literal translation of the original Java API, with a short list of exceptions that exist mostly because of PHP language barriers. That framing tells you who the library is for: PHP developers who already know Hamcrest from Java or from another port, and who want the same matcher vocabulary inside PHPUnit instead of PHPUnit's own assertion methods.

The problem it solves is the shape of an assertion failure. A plain boolean check tells you that something was false. A matcher carries a description of what was expected, so a failing assertion can report the expectation rather than only the outcome. The README's describedAs example makes the difference concrete: a bare is(notNullValue()) assertion reports "Expected: is not null but: was null", while describedAs($expected, notNullValue()) reports "Expected: Dog but: was null". That is the whole value proposition. If your test output matters to the people reading it, matchers give you a place to put the expectation in words.

It is not for everyone. The library is a matcher vocabulary, not a test framework. You still need PHPUnit or another runner to execute the tests. And if you have no prior exposure to Hamcrest, the naming (both/andAlso, either/orElse, hasItemInArray) is idiomatic to a Java tradition that PHP developers may find foreign.

## How the matcher mechanism works in PHP

The core call is MatcherAssert::assertThat, which the README shows taking a value and a matcher: \Hamcrest\MatcherAssert::assertThat('a', \Hamcrest\Matchers::equalToIgnoringCase('A')). The Matchers class is the factory for matcher objects, and assertThat is the evaluation point. Everything else in the library is a matcher that plugs into that call.

On top of the static API, the library offers global proxy-functions. The README gives four call forms: assertThat with an identifier string, assertThat with just a value and a matcher, assertThat on a boolean expression, and the is() sugar form assertThat(true, is(true)). These functions are not autoloaded. The README carries a warning that you must load them first with \Hamcrest\Util::registerGlobalFunctions(). Miss that call and the proxy functions simply do not exist.

Matchers compose. allOf, anyOf and noneOf take several matchers and combine them with AND, OR and negated-OR semantics respectively. both(...)->andAlso(...) and either(...)->orElse(...) are the fluent equivalents. The renames are documented: instanceOf($theClass) becomes anInstanceOf($theClass), and and/or become andAlso/orElse, because and and or are reserved words in PHP. The README also notes that unless it would be non-semantic for a matcher to do so, input is dynamically typed "in the PHP way", with stringContains() and greaterThan() listed as exceptions where the type itself carries meaning.

Matcher coverage is grouped in the README under Array, Collection, Object, Numbers, Type checking and XML. Array matchers include anArray, hasItemInArray (aliased hasValue), arrayContaining, contains, hasKeyInArray (aliased hasKey), hasKeyValuePair (aliased hasEntry), arrayWithSize, emptyArray and nonEmptyArray. Collection matchers work on Traversable objects: emptyTraversable, nonEmptyTraversable and traversableWithSize. That split matters, because arrays and iterators do not share one matcher set.

## Installing hamcrest-php and writing a first assertion

The repository is distributed as a Composer package, and composer.json sits at the top level alongside the hamcrest/ source directory and the tests/ directory. The README does not spell out the install command, so the package name is the thing to confirm on Packagist before adding it. The layout indicates the library ships from this repository as hamcrest/hamcrest-php.

Because the global proxy-functions are not autoloaded, the first real step in a test bootstrap or a test case is registering them. Without this call, the short-form assertions in the README will not resolve.

```php
\Hamcrest\Util::registerGlobalFunctions();
```

With that in place, the README's own examples run as written. This one checks that an array contains a value and has an exact size, and it is a reasonable first assertion because it exercises two matchers at once.

```php
assertThat([2,4,6], allOf(hasValue(2), arrayWithSize(3)));
```

If you use PHPUnit, the README flags a side effect worth handling early. Assertions made through Hamcrest are not counted by PHPUnit, so a test can be marked Risky with the message "This test did not perform any assertions". The README's fix is to add this to the tearDown method of the test case.

```php
$this->addToAssertionCount(\Hamcrest\MatcherAssert::getCount());
\Hamcrest\MatcherAssert::resetCount();
```

If you prefer not to touch the global namespace, the static form works without any registration step: \Hamcrest\MatcherAssert::assertThat('a', \Hamcrest\Matchers::equalToIgnoringCase('A')).

## Where hamcrest-php stops short of Java Hamcrest

The README is unusually direct about what was not ported. Several official matchers have no PHP equivalent because, in the project's own words, they don't make sense or don't apply in PHP: typeCompatibleWith($theClass), eventFrom($source), hasProperty($name) and samePropertyValuesAs($obj). The last two carry a footnote tied to the difference between JavaBeans and what the README calls POPOs, plain old PHP objects. If your Java test suite leans on property-based matchers, the port will not carry that part across.

The README also states that when most of the collection matchers are finally ported, PHP-specific aliases will probably be created, because Java's naming split across Arrays, Collections, Sets and Maps does not map cleanly onto PHP's single array type. As of the current README, the collection matchers that exist are the three Traversable ones. Anyone expecting the full Java collection matcher set should treat that as unfinished rather than merely renamed.

Two smaller friction points. First, the global functions require an explicit registration call, which is easy to forget in a new test bootstrap. Second, the assertion-count behaviour under PHPUnit means Hamcrest assertions are invisible to the runner unless you add the tearDown snippet. Neither is a design flaw, but both are things you discover by reading the README rather than by running the tests.

Finally, the tutorial link in the README points at a code.google.com archive URL. Google Code has been shut down, so that link is a historical reference rather than a live guide. The README itself is the working documentation.

## Hamcrest matchers versus AssertJ-style fluent assertions

The comparison people ask about is Hamcrest against AssertJ. The difference is in where the assertion lives. Hamcrest puts the value first and the matcher second: assertThat($actual, equalTo($expected)). AssertJ inverts that, hanging assertion methods off the object under test so the call reads as a chain on the actual value. Both produce descriptive failures, and both are assertion libraries rather than test frameworks.

In PHP specifically, the practical alternative is PHPUnit's own assertion methods. PHPUnit ships assertSame, assertContains, assertArrayHasKey and similar calls, and they require no extra dependency, no global function registration and no assertion-count workaround. If your suite is already written in that style, adding hamcrest-php means maintaining two assertion vocabularies side by side.

The case for Hamcrest is composition. Matchers nest: allOf(hasValue(2), arrayWithSize(3)) is one assertion expressing two conditions, and everyItem, hasItems and hasItem accept matchers as arguments rather than only literal values. PHPUnit's built-ins tend to take values. If you find yourself writing helper functions to express compound expectations, matchers are the more direct tool. If your assertions are mostly single comparisons, the built-ins are simpler and the port adds a dependency for little gain.

## Version 3.0.0, licensing and what maintenance costs you

The most recent release listed is v3.0.0, dated 2026-06-16, following v2.1.1 on 2025-04-30 and v2.1.0 on 2025-04-29. The last push to the default branch was on 2026-06-17, the day after the 3.0.0 tag. A major version bump is the signal that matters here: if you are on a 2.x line, the upgrade is a major-version move and the CHANGES.txt file at the repository root is where the project records what changed. The README does not document a rollback path or a deprecation policy, so treat the changelog as the source of truth before upgrading.

The licence field on the repository is reported as NOASSERTION, which means the platform could not match the file to a known licence identifier. A LICENSE.txt file does sit at the top level, so the text is present and readable, but the machine-readable classification is unresolved. If your organisation gates dependencies on an SPDX identifier in tooling, that is a check to run yourself against LICENSE.txt rather than something the repository metadata will answer. Nothing here is legal advice.

Maintenance cost beyond upgrades is low in the ordinary case: the library is a set of matchers with no runtime services, no configuration files and no background processes. The recurring cost is the PHPUnit tearDown snippet in every test case that uses the proxy functions, and the discipline of registering global functions once per bootstrap. Both are one-time per project, not per release.

## Conclusion

Adopt hamcrest-php if you want matcher-based assertions that read as sentences and you are willing to call \Hamcrest\Util::registerGlobalFunctions() or use the static MatcherAssert API. Do not adopt it if you expect Java parity for hasProperty, samePropertyValuesAs, typeCompatibleWith or eventFrom, or if you want a single fluent assertion library. Before writing tests, verify that the matcher you need is listed in the README's Available Matchers section, and check the v3.0.0 entry in CHANGES.txt for the current version's scope.

## FAQ

### What is hamcrest-php?

It is the official PHP port of Hamcrest, a matching library originally written for Java and ported to many other languages. The README states that it essentially follows a literal translation of the original Java API, with a few exceptions forced by PHP language barriers.

### What are the key differences between AssertJ and Hamcrest?

AssertJ and Hamcrest are both assertion libraries, but they place the assertion differently: Hamcrest reads assertThat($actual, $matcher), while AssertJ-style fluent assertions hang methods off the object under test. The README does not compare the two; it only documents the matcher API and its composition helpers such as allOf, anyOf and everyItem.

### Why do hamcrest-php assertions make my PHPUnit tests Risky?

PHPUnit does not count Hamcrest assertions, so a test can be marked Risky with the message "This test did not perform any assertions". The README's fix is to call $this->addToAssertionCount(\Hamcrest\MatcherAssert::getCount()) and then \Hamcrest\MatcherAssert::resetCount() in the test case tearDown method.

### Why are the hamcrest-php global functions undefined?

The README warns that the global proxy-functions are not autoloaded by default. You need to load them first with \Hamcrest\Util::registerGlobalFunctions(), or use the static \Hamcrest\MatcherAssert::assertThat call instead.

### Which Java Hamcrest matchers are missing from hamcrest-php?

The README lists typeCompatibleWith($theClass), eventFrom($source), hasProperty($name) and samePropertyValuesAs($obj) as official matchers that were not ported because they don't make sense or don't apply in PHP. The collection matchers are also only partly present, with emptyTraversable, nonEmptyTraversable and traversableWithSize documented so far.

## Sources

- [hamcrest/hamcrest-php on GitHub](https://github.com/hamcrest/hamcrest-php)
- [Issues](https://github.com/hamcrest/hamcrest-php/issues)
- [README](https://github.com/hamcrest/hamcrest-php/blob/master/README.md)
- [Releases](https://github.com/hamcrest/hamcrest-php/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hamcrest-hamcrest-php
