intervention/zodiac
Intervention Zodiac is a lightweight PHP package for determining zodiac signs from dates. It supports tropical and sidereal (Vedic) systems, provides sign lookup by date and date ranges for each sign, and works seamlessly in Laravel or any PHP project. No configuration required.
intervention/zodiac is a small, framework-agnostic value-object package: give it a date, get back a Western tropical zodiac Sign with a name, HTML symbol, localized label, date range, and length. There is no service provider, config file, migration, or facade to publish — day-to-day use is a static factory call.
Minimal steps:
composer require intervention/zodiac:^7.1Intervention\Zodiac\Zodiac and Intervention\Zodiac\Sign first — those two classes are the whole public surface.php artisan tinker before wiring it into a model.First use case — deriving a sign from a user's birthdate:
use Intervention\Zodiac\Zodiac;
$sign = Zodiac::fromDate('1980-10-15');
$sign->name(); // "libra"
$sign->html(); // "♎"
$sign->localized(); // localized label
$sign->start(); // DateTime
$sign->end(); // DateTime
$sign->length(); // days in sign
Use name() as your stable key (it is a lowercase slug), and treat html() as a rendering detail.
Model accessor, computed not persisted. Store the birthdate, derive the sign on read. Sign definitions are package data and can shift with a release; persisting "libra" in a column means backfilling later.
protected function zodiac(): Attribute
{
return Attribute::get(fn () => $this->birthdate
? Zodiac::fromDate($this->birthdate)
: null);
}
API responses — 7.1.0 makes this safer. Sign now implements JsonSerializable, so json_encode($sign) / returning it from a controller produces a payload instead of an empty object. Dump the shape once in a test and pin it:
public function show(User $user)
{
return response()->json([
'birthdate' => $user->birthdate,
'zodiac' => $user->zodiac, // serializes via JsonSerializable
]);
}
If you want a contract you control, keep building the array yourself from name(), html(), localized(), start(), end(). That insulates your API from future serialization changes.
Blade rendering. html() returns an HTML entity, so escape-decoding matters:
<span class="zodiac">{!! $user->zodiac->html() !!}</span>
<span>{{ $user->zodiac->name() }}</span>
Translate from the slug, not the package. If your app is multilingual, use name() as the key in your own lang/{locale}/zodiac.php rather than depending on the package's localized strings. This keeps translation workflow in Laravel and avoids surprises when the app locale and package locale are resolved differently.
Wrap it for the container. Because the API is static, tests that need deterministic signs are easiest if you hide it behind a tiny service or interface binding (ZodiacResolver), then swap a fake in tests.
Batch/computed lists. The object is cheap; a collection map over birthdates is fine. For an index of thousands of users, compute in PHP after the query rather than issuing per-row lookups — there is no database involvement at all.
Validation layer. Pair the call with a date / before_or_equal:today rule, and decide explicitly how you handle null birthdates in the accessor so views never call methods on null.
json_encode($sign) effectively yielded nothing useful (private state, no JsonSerializable). Any test asserting an empty object, or any client relying on a hand-built array, should be updated deliberately. JsonSerializable output is now part of the package's observable behaviour — do not assume it will never change.Zodiac::fromDate() takes a date. If you pass a UTC-shifted Carbon instance for a user born at 23:30 local time, you can land on the wrong day and therefore the wrong sign. Normalize to a date-only value in the user's timezone before calling ($birthdate->toDateString()).{{ $sign->html() }} renders the literal string ♎. Use {!! !!} or html_entity_decode(). Do not run user input through html(), but also do not assume the output is safe to concatenate blindly.config/zodiac.php or a facade, stop — there isn't one. Configuration is "which date do I pass in." Container customization is your own wrapper.name() yourself.php-cs-fixer/phpcs setup.fromDate() and the resulting start()/end() range — nine times out of ten the input date, not the package, is the bug.How can I help you explore Laravel packages today?