Skip to content

Repository files navigation

PHP Datatypes

CI Release Release Latest Version on Packagist Total Downloads PHP Version License Last Commit Open Issues Contributors Stars Code Size PHPStan Code Style Conventional Commits

PHP Datatypes builds your Value Objects, Entities and Aggregates from predefined, tested Traits - one per attribute - leaving the class definition free for the business logic that belongs to it.

Getters return Value Objects rather than primitives wherever possible, so a value carries its own validation and normalization instead of every caller remembering it. There is no 3rd party dependency beyond PHP itself, so these values work without a framework, an HTTP request or a template engine.

  • AbstractValueObject - the base a Value Object, Entity or Aggregate extends, giving it construction from an array, full serialization and toArray() out of the box.
  • Entity Traits - one Trait per attribute (HasPositiveIntegerIDTrait, HasActiveTrait, HasNameTrait, ...), each carrying the type hints and normalization for that attribute.
  • Scalar datatypes - immutable Str, integer and float wrappers, with a fluent API that returns a new instance on every operation rather than mutating the value in place.
  • Web datatypes - Slug, Url and EmailAddress, each validating and normalising itself.
  • Datetime datatypes - date and time values, without pulling in a datetime library.
  • Exception vocabulary - a typed exception per datatype under Exceptions\Datatypes\*, so a rejected value says what it rejected and why.

Scope

This package holds datatypes: values that validate and normalise themselves, and that any PHP application can use without a framework, an HTTP request or a template engine. The "No 3rd party dependency" rule is a scope rule as much as a dependency rule.

Behaviour that renders, parses documents, or exists to serve a web page is not a datatype, and lives in hradigital/php-markup instead. php-markup depends on this package - never the other way round.

Inspiration

Some of the projects that inspired this one, are mainly Nikita Popov's Scalar Objects, but also Martin Helmich's Scalar Classes and Michael Hall's Datatypes.

Due to the "No 3rd party dependency" rule, this package will use some simplified versions of more popular datatypes. Some examples are:

Requirements & Installation

  • PHP >= 8.1
  • ext-intl
composer require hradigital/php-datatypes

Scope reference

Concern Package
Slug, Url, EmailAddress, Money, Datetime, … php-datatypes
Exception vocabulary (Exceptions\Datatypes\*) php-datatypes
Markup - plain text to structured HTML php-markup
SocialPreviewImageExtractor - reads an HTML <head> php-markup
SeoMetadata, SocialImage, ArticleMetadata, Open Graph / Twitter enums php-markup
schema.org JSON-LD nodes and their builders php-markup

A datatype that needed Illuminate\* to work would stop being usable in the non-Laravel hosts this package exists to serve.

Moved in 3.0.0. Web\Markup\Markup, Web\Markup\MarkupConfiguration, Web\Seo\SocialPreviewImageExtractor, Web\Seo\SeoMetadata, Web\Seo\SocialImage, Web\Seo\ArticleMetadata, Web\Seo\OpenGraphType and Web\Seo\TwitterCardType were removed from this package and now live in php-markup, under HraDigital\Components\Markup\ and HraDigital\Components\Markup\Seo\. Web\Seo\Slug, Web\Url and Web\EmailAddress are unaffected and stay here.

Code Usage

This package is mean to provide you an easy way to do this (and much more):

$user = new User([
    'id' => 123,
    'active' => true,
    'name' => ' John Doe ',
]);

echo $user->getId(); // (int) 123
echo $user->isActive(); // (bool) true
echo $user->getName(); // Prints ' John Doe '
echo $user->getName()->trim()->toUpper()->replace(' ', '-'); // Prints 'JOHN-DOE'
echo $user->getName(); // Prints ' John Doe ' again, as Attribute is immutable.

... just by building your object like this:

class User extends AbstractValueObject
{
    use HasPositiveIntegerIDTrait,
        HasActiveTrait,
        HasNameTrait;
}

Also, out-of-box, it will allow you to do the following:

$user = new User([
    'id' => 123,
    'active' => true,
    'name' => ' John Doe ',
]);

echo json_encode($user); // {"id":123,"active":true,"name":"John Doe"}

$serialized = serialize($user);
$otherUser = unserialize($serialized);

printf($otherUser->toArray());
/*
[
    'id' => 123,
    'active' => true,
    'name' => 'John Doe',
]
*/

... and much more. This will leave your objects clean from repetitive state management code, which frees you to implement your business logic in them.

In order to learn more about the code, please go here.

An Aggregate/Entity/ValueObject that extends AbstractValueObject will be built using predefined/tested Traits for each of the class attributes, leaving your class definition cleaned/free for your business logic implementation.

This will also allow you to reuse/load your objects with data that can come from a Database, Webservice, Event payload, etc...

To learn how to use this package, please go to AbstractValueObject documentation.

Usage

For more information about how to to use these Datatypes, please see the project's usage notes and some implementation examples in here.

Continuous Integration & Testing

The project is validated on every push and pull request through GitHub Actions. The CI pipeline runs:

  • Semantic Commits - validates that new commit messages follow Conventional Commits, via commitlint and the rules in commitlint.config.mjs. Only the commits introduced by the push/pull request are checked - existing history is never re-validated.
  • Coding Standards - PSR2 checks via PHP_CodeSniffer.
  • Tests - the full PHPUnit suite against PHP 8.1, 8.2, 8.3, 8.4 and 8.5, each running inside its own official php:<version>-cli Docker container.

Composer scripts are available to run the same checks locally:

composer run test-cs    # Coding standards (PSR2) over src/
composer run test-code  # PHPUnit suite with JUnit report (written to ci/)
composer run test-all   # Runs both of the above

Makefile targets

A Makefile wraps the same gates with a consistent interface. Run make help for the full list:

make lint       # PHPCS code-style check (report only)
make lint-fix   # Apply PHPCBF code-style fixes
make validate   # Run every report gate concurrently
make test       # Full PHPUnit suite
make test-unit  # Unit testsuite only

Scope any target with FILES and narrow a test run with FILTER. Append QUIET=1 for silent-on-success - gates print only on failure, test targets print only their final summary:

make lint FILES="src/Web/Url.php" QUIET=1
make test-unit FILTER=UrlTest QUIET=1

The targets run natively against vendor/. Override the EXEC prefix to run them elsewhere, e.g. make lint EXEC="docker exec <container>".

Versioning & Releases

Releases are cut automatically by GitHub Actions. The workflow is gated on CI: it only runs once the CI workflow completes for a master push, and it tags the exact commit CI validated - so a red commit is never released.

The next version is derived entirely from the commit messages since the last tag:

Commit Bump
A BREAKING CHANGE: footer Major
feat: Minor
fix:, perf:, refactor:, revert:, build: Patch
ci:, chore:, docs:, style:, test: No release

Every change to shipped code therefore bumps at least the revision number, while a docs-only or tooling-only push does not burn a version. A commit whose message is not a valid Conventional Commit is skipped entirely by the version calculation - which is what the commitlint CI gate prevents.

Breaking changes must use the footer. The release action detects a major bump only from a BREAKING CHANGE: note - the shorthand ! suffix (feat!: ...) is not recognised and would silently release a minor instead. Write it on its own line, after a blank line:

feat: change Url::getHash() to return a Str

BREAKING CHANGE: getHash() now returns Str instead of string. Cast with (string) at call sites.

Contributing

Contributing to the project is easy and contributions are welcomed and appreciated.

Commit messages must follow Conventional Commits - CI rejects anything else, and the type you pick decides the next release version (see Versioning & Releases above).

It's obviously harder to maintain the project alone, but efforts will be made to keep and improve it, as I plan to use it as a dependency in other projects.

License

Mozilla Public License 2.0. See LICENSE.

You may use this package in closed-source and commercial products. If you modify and distribute the package's own files, those files must remain under the MPL-2.0.

The HRADigital name and package names are not covered by that licence - see TRADEMARK.md.

About

DDD building blocks for PHP: one tested Trait per attribute, plus immutable Str/int/float, Slug, Url, EmailAddress, Money and datetime Value Objects. Framework-free, no 3rd party dependency.

Topics

Resources

Stars

1 star

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages