Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Swiss Ephemeris Ffi Laravel Package

jayeshmepani/swiss-ephemeris-ffi

PHP 8.3+ FFI wrapper exposing all 106 Swiss Ephemeris C API functions with full constant and signature parity. Zero abstraction or extra math, verified bit-level output parity against the official swetest CLI for planets, houses, eclipses, and edge dates.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Verify FFI before touching Composer. This package is dead on arrival without ext-ffi. Check with php -m | grep ffi and php -i | grep ffi.enable. If ffi.enable is not true (or preload), you cannot fix it with ini_set() at runtime — ffi.enable is PHP_INI_SYSTEM, so it must go in php.ini, a -d ffi.enable=1 CLI flag, or your container/FPM pool config. Confirm this in CI, staging, and production before anyone writes feature code; shared hosts and hardened containers often disallow FFI outright.

2. Install and publish.

composer require jayeshmepani/swiss-ephemeris-ffi
php artisan vendor:publish   # native libs + config, target: storage/app/swisseph/

Note the publish target is storage/app/swisseph/, not public/ (changed in v1.1.1). That is the correct behavior — ephemeris binaries and .se1 data must not be web-reachable. Add the directory to your deploy scripts and .gitignore if you fetch the larger sepl*.se1 / semo*.se1 archives separately.

3. Set the ephemeris path explicitly. The bundled library ships the C code; it does not guarantee the .se1 data files are found. Always call the path-setter once at bootstrap (a service provider is fine) pointing at storage_path('app/swisseph'). If you don't, Swiss Ephemeris silently falls back to the Moshier analytical theory — lower precision, no error.

4. First use case — a natal Sun position. The canonical flow is swe_julday → allocate buffers → swe_calc_ut → read double[6]:

$jd  = $sweph->swe_julday(2000, 1, 1, 12.0, SwissEphFFI::SE_GREG_CAL);
$xx  = $sweph->getFFI()->new("double[6]");
$err = $sweph->getFFI()->new("char[256]");

$ret = $sweph->swe_calc_ut($jd, SwissEphFFI::SE_SUN, SwissEphFFI::SEFLG_SPEED, $xx, $err);
if ($ret < 0) {
    throw new EphemerisException($sweph->getFFI()->string($err));
}
$longitude = $xx[0]; // degrees

Where to look first: the published docs site (installation per-OS, ephemeris file setup, Laravel integration, troubleshooting), then VERSION.md / UPSTREAM_SYNC.md to know exactly which upstream tag you're pinned against, then the benchmark page if you need to defend performance in a review.

Implementation Patterns

Put a typed service behind the FFI layer — always. "Zero abstraction" is the package's selling point and your integration hazard. Never call swe_calc_ut from a controller, job, or Blade template. Bind one interface in a service provider:

interface EphemerisService {
    public function bodyPosition(CarbonImmutable $utc, int $body, int $flags = 0): BodyPosition;
    public function houses(CarbonImmutable $utc, float $lat, float $lon, int $hsys): HouseCusps;
}

The implementation owns the SwissEphFFI singleton, buffer allocation, and error translation; callers get readonly DTOs (longitude, latitude, distance, lonSpeed, …). This is what makes a future fork of a six-star package a weekend instead of a quarter.

Return the raw doubles, round at the edges. The package performs no rounding or transformation, and neither should your service. Keep full double precision in the domain object and round to 2–6 decimal places only in the presenter/API resource.

Flags are compositional — treat them as a bitmask value object. SEFLG_SPEED fills xx[3..5] with velocities (retrograde detection comes from the sign of xx[3], not a separate call). Sidereal/Vedic work needs SEFLG_SIDEREAL plus a prior swe_set_sid_mode() call with the ayanamsa constant — forgetting the mode set silently yields tropical output. Topocentric work needs swe_set_topo() before swe_calc_ut.

Use swe_utc_to_jd / swe_utc_time_zone rather than rolling your own. swe_calc_ut wants UT; local birth times and leap-second handling are where chart bugs live. Convert user input to UTC in your application layer, then let the library do the UT→JD conversion. Julian day math done by hand will produce off-by-minutes errors that are invisible until a customer complains about a wrong ascendant.

Houses come as fixed-size buffers. swe_houses_ex writes into double[13] (cusps, 1-indexed in practice — cusp 1 is $cusps[1]) and double[10] (ascmc: Asc, MC, ARMC, Vertex, …). Wrap both into one HouseCusps DTO; do not expose array indices upward.

Cache aggressively by chart key. Natal charts are immutable. Key on hash(utc_timestamp, lat, lon, body_set, flags, ayanamsa), store the DTO in Redis for a long TTL. The FFI call itself is fast, but you are usually doing 10+ bodies and 12 houses per chart; the win is in not redoing it on every page load.

Precompute for bulk, FFI for on-demand. Transits across a year, solar returns, synastry grids — these are loops over hundreds to thousands of swe_calc_ut calls. Push them to queued jobs with chunking, write results to a table, and serve from there. The FFI path earns its keep on arbitrary-date, arbitrary-body, single-chart requests; high-volume consumer endpoints are better served by precomputed tables.

Testing: assert parity, not magic numbers you invented. Build fixtures by running the official swetest CLI with identical flags and ephemeris files, then assert your service matches within a tight epsilon. Keep a test that exercises the error path (invalid body ID) so your exception mapping doesn't rot. Mark FFI-dependent tests as a separate PHPUnit group so the suite still runs on machines without the extension.

Laravel facade is for tinker and prototypes only. It delegates directly to the FFI layer without reshaping buffers. It's fine for a one-off php artisan tinker sanity check; in application code, inject the interface so you can swap it in tests.

Gotchas and Tips

  • The FFI layer is not thread-safe. Correctness depends on synchronous, isolated execution. Under Octane/Swoole, do not fan out parallel swe_calc_ut calls inside a single worker — use queue workers, separate processes, or a dedicated ephemeris microservice. This is the single most consequential operational constraint.
  • ffi.enable is not runtime-configurable. ini_set('ffi.enable', '1') fails silently. Ship the setting in php.ini/Dockerfile/php-fpm pool, and add a boot-time extension_loaded('ffi') guard in your service provider that throws a clear exception rather than letting the first chart request fail cryptically.
  • Missing .se1 files fail soft. Swiss Ephemeris falls back to Moshier theory and returns success. Precision-critical features can ship silently degraded. Mitigate by setting the ephe path in a provider and adding a smoke test that asserts agreement with a known high-precision fixture.
  • Buffers are FFI\CData, not arrays. $xx[0] works, count($xx) does not. Allocate fresh buffers per call — reusing them across calls is a premature optimization that invites stale-data bugs. Read char[256] errors with $ffi->string($serr), and remember the buffer may be empty even when $ret < 0 (some codes signal via the return value alone).
  • swe_calc_ut returns the flag integer, not a boolean. Always test $ret < 0 for failure; if (!$ret) is a bug. The returned flag can differ from what you requested (e.g. fallbacks), so log it when it matters.
  • Custom library path after init is ignored. Per v1.1.1 notes, the FFI instance is a singleton; setting a custom native library path after the first initialization has no effect. If you're pointing at your own compiled build, do it in a bootstrap step before anything resolves the service.
  • Publish target is storage/app/swisseph/. If you copied an older tutorial that put binaries under public/, undo it. Also ensure the directory is readable by the FPM/queue user and included in your container build — vendor/ autoload won't carry the .se1 archives.
  • Licensing is a deployment blocker, not a footnote. The wrapper is AGPL-3.0 and upstream is dual-licensed AGPL / Swiss Ephemeris Professional License. Closed-source commercial or public SaaS usage requires the Professional License. Resolve this with legal before scoping the feature — it can force open-sourcing your service layer or change unit economics.
  • Pin the version. Six stars, zero dependents, single maintainer. That's not a reason to avoid it (the underlying C library is industrially trusted), but composer.lock should pin it, and your wrapper should stay thin. Watch for upstream .se1 data updates — DE441-rebuilt files landed April 2026 and remain compatible back to 1.67, but pinning data files per release is the reproducible choice.
  • Debug by diffing against swetest. When a number looks wrong, run swetest with the exact same date, flags, and ephemeris directory. If swetest agrees with you, the bug is in your flag composition (SEFLG_SIDEREAL without swe_set_sid_mode, SEFLG_TOPOCTR without swe_set_topo, wrong calendar flag on pre-1582 dates) — not in the wrapper.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo