amethyst/address
Amethyst Address is a Laravel package for working with postal addresses, offering structured address fields and management for your models and forms. A lightweight option for adding address handling to Laravel applications.
Install with Composer:
composer require amethyst/address
There's no service provider, config file, or facade — this is a plain value-object package. Your first stop should be the src/ directory (typically src/Address.php and src/AddressInterface.php). Read the interface first to see the contract, then the concrete class to see the constructor/named constructors and the get* accessors.
A minimal first use case — attaching a shipment address to an order:
use Amethyst\Address\Address;
$address = new Address(
'123 Main St',
'Apt 4B', // depending on your field set
'Springfield',
'12345',
'US'
);
echo $address->getCity(); // "Springfield"
If the class exposes named constructors or with* methods, prefer those — most amethyst packages follow an immutable-ish value-object style, so $address->withCity('Shelbyville') returns a new instance rather than mutating in place. Confirm by inspecting the source; the exact constructor signature and method names are the one thing to verify before wiring it into your domain.
Entity composition. Treat Address as a value object embedded in your domain entities rather than a standalone record. A typical order model holds a shipping address and an invoice address:
class Order
{
private Address $shippingAddress;
private Address $invoiceAddress;
}
Since the package has no persistence layer of its own, map it yourself — either as a Doctrine @Embeddable, as a custom json-to-Address cast, or by flattening the fields into columns and rehydrating via the constructor.
Immutability with with* methods. When reassigning fields (e.g., a customer edits their shipping address during checkout), build a new value from the old one instead of mutating:
$updated = $address
->withCity($request->input('city'))
->withPostalCode($request->input('postal_code'));
This keeps the object safe to pass into events, mailables, and queued jobs without defensive copying.
Hydration from form data / requests. The constructor takes positional scalars, so wrap hydration in a small factory or static helper in your app layer. Keep request-mapping out of the value object itself — the package is a data holder, not a validator or form object.
Serialization boundaries. Because there's no built-in serializer, decide early how addresses cross boundaries (API responses, queue payloads). Either implement JsonSerializable in a thin subclass/wrapper, or map explicitly with a resource class. Avoid leaking get* calls into your controllers; map at the edges.
Testing. In unit tests, construct addresses directly with literal values — no mocking needed. In integration tests, use the object as a fixture and assert round-trips: build → persist → reload → compare field values. This catches mapping mistakes (wrong column order, trimmed fields) far faster than eyeballing.
It does not validate. The most important thing to understand: this package models an address shape, not rules. Postal codes, country codes, and required fields are your responsibility. Put validation in form requests or a domain validator; do not overload the value object with it.
Field ordering in the constructor. Positional scalar arguments are easy to confuse (city vs. postalCode are both strings). Prefer named arguments (PHP 8+) if the constructor is public, and otherwise centralize construction in a single factory in your codebase so a swapped argument only has one place to fix.
Country handling. Some versions treat the country as a plain string, others as a code or a Country value object from a sibling package. Check getCountry()'s return type before comparing against 'US' in templates — and normalize casing (ISO codes are usually uppercase) at the boundary if the package doesn't.
Immutability and ORMs. If with* returns a clone, beware of Doctrine's identity map: assigning $order->setShippingAddress($order->getShippingAddress()->withCity(...)) is fine, but mutating an address you pulled out of a collection without reassigning it will silently not persist. Always reassign.
Nullability of optional fields. Optional lines (address line 2, region, etc.) may or may not be nullable depending on the version. Decide in your app whether you pass null or '', and normalize to one convention — mixed usage produces messy template output and fragile comparisons.
Minimal footprint, minimal maintenance risk. With 2 stars and no dependents, assume no community support. The code is small and MIT-licensed, so vendoring it (or forking into your src/ as an internal value object) is a low-cost hedge if you need the field set to evolve. The interfaces are usually simple enough to re-implement in an afternoon if the package goes stale.
Extension points. Prefer implementing AddressInterface (or extending a base class) over reaching into internals. If you need extra fields (e.g., a delivery note or GPS coordinates), extend the concrete class in your own namespace — but avoid changing the constructor signature, or you lose the ability to swap back to the package version later.
How can I help you explore Laravel packages today?