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.
Architecture fit. The package is intentionally thin: a service provider, a facade, and a SwissEphFFI class that binds the Swiss Ephemeris C library through ext-ffi and exposes all 106 public C functions with 1:1 signatures and constants. For a Laravel application this is a correct shape but a dangerous default — the facade delegates raw FFI\CData buffers (double[6], char[256]) directly to the caller. That is infrastructure, not domain code. The architectural decision a TPM must enforce is that the FFI layer never leaves a dedicated service boundary: an internal EphemerisService/AstrologyCalculator interface returning typed value objects (longitude, latitude, distance, speed, retrograde flag, error enum). Every consumer — controllers, jobs, Blade, API resources — talks to that interface. This keeps the package swappable and makes a fork a weekend rather than a quarter.
Integration feasibility. Prerequisites are concrete and verifiable: PHP 8.3+, ext-ffi, ffi.enable=true (or correct preload configuration), and ephemeris .se1 data files on disk. The package bundles multi-platform native libraries and publishes them to storage/app/swisseph/ (v1.1.1 moved this off public/), which is the right deployment posture: outside the web root, no swetest CLI dependency baked into the container. Feasibility is therefore gated by environment, not by code — shared hosting and hardened container policies commonly disallow FFI. Validate host and CI capability before roadmap commitment.
Technical risk.
| Risk | Severity | Mitigation |
|---|---|---|
| AGPL-3.0 + Astrodienst dual license | High | Legal/procurement gate before scoping; budget Professional License into unit economics |
| Single maintainer, 6 stars, 0 dependents | Medium-High | Pin exact version, maintain internal fork readiness, keep wrapper thin |
Raw FFI\CData leaking into app code |
High | Typed DTO adapter from day one; code review rule |
FFI/ext-ffi unavailable in an environment |
Medium | Environment matrix test in CI; hosted-API fallback design |
| Non-thread-safe native engine | Medium | Synchronous isolated execution; queue workers or microservice, never parallel in-process |
| Memory ownership of FFI buffers | Medium | Centralize allocation/free in the adapter; no ad-hoc new("double[6]") scattered |
Missing/mismatched .se1 files at runtime |
Medium | Startup self-check; fail fast, not silently degraded accuracy |
| Parity claims are test-scoped ("verified scenarios") | Low-Medium | Own regression suite for our specific bodies/house systems/dates |
Key questions.
ffi.enable=true across local, CI, staging, and every production target (including any managed PHP hosting)?VERSION.md / UPSTREAM_SYNC.md, DE441 .se1 rebuilds)?Stack fit. Strong for Laravel on PHP 8.3+ with the facade and conventional publishing. The FFI boundary is the only exotic element; everything around it (Composer, service provider, config publish, storage paths) is idiomatic. Fit is poor for any environment where FFI is policy-disallowed, and poor for teams without a PHP engineer comfortable with C memory semantics.
Migration path. Three realistic origins:
swetest shell-out — the cleanest win. Replaces a subprocess-per-request pattern with in-process calls, removes CLI dependency from images, eliminates shell-escaping and output-parsing fragility. Ship behind the adapter interface so behaviour is provably identical before cutover.php-sweph C extension — viable only if FFI is easier to operate than a compiled extension. The published benchmark shows bit-parity and competitive performance, so the decision is operational (rebuild-on-PHP-upgrade vs. ffi.enable configuration), not accuracy.Starting from nothing, the path is: install → publish native libs and .se1 files to storage/app/swisseph/ → thin adapter → typed DTOs → feature work.
Compatibility. Constraints to validate explicitly: PHP version floor (8.3), OS/architecture coverage of the bundled binaries (Linux/macOS/Windows documented; confirm your container base image and any ARM targets), extension availability on the chosen PHP build, and ffi.enable/preload semantics per environment. Note the documented singleton behaviour: once the FFI instance is initialized, a later custom library path is not re-applied — configuration must be resolved at boot, not lazily mid-request. Also reconcile the internal SE_VERSION string (2.10.03) against the tracked upstream tag (v2.10.3final) so version reporting in our own telemetry is unambiguous.
Sequencing.
swetest for our target bodies/house systems; measure cold-start and per-call latency in the real container.EphemerisService interface + typed result objects + centralized buffer/error handling + startup self-check for .se1 presence.Maintenance. Low code churn, non-trivial stewardship. The wrapper changes slowly; the environment does not — PHP upgrades, base-image changes, and FFI policy shifts are the real maintenance events. Upstream tracking is a genuine recurring task: pinned tag, VERSION.md/UPSTREAM_SYNC.md to reconcile, and periodic .se1 data refreshes (~dated releases; note the DE441 rebuild and the compatibility claim back to 1.67). Budget explicitly for the abandon scenario: pin indefinitely, or fork and maintain. With six stars, zero dependents, and one maintainer, assume no vendor roadmap influence.
Support. No SLA, no managed support, no compliance paper trail. Internal ownership is mandatory. Escalation for correctness questions routes to the upstream C library documentation and the parity test suite, not to the package author. The published benchmark and swetest parity tests are unusually strong evidence for a package this size — use them in design reviews, but treat "bit-level parity in verified test scenarios" as scoped, and own tests for our actual scenarios.
Scaling. The work is CPU-bound native computation, not I/O. Two distinct scaling regimes:
Cost drivers: CPU per call, ephemeris file I/O and page cache (keep .se1 files on fast local disk, not network storage), and process count needed for isolation.
Failure modes.
.se1 files or library path → silent fallback to lower-precision or Moshier mode, or negative return with an error string in char[256]. This is the most insidious failure: the app keeps working with degraded accuracy. Enforce a boot-time data-file assertion and assert on swe_calc_ut return codes rather than truthiness.Ramp-up. Two distinct skill requirements: (a) a PHP engineer fluent in FFI/C interop — buffer allocation, pointer lifetime, error string handling, no exception safety across the boundary; (b) domain literacy in astronomical coordinates, Julian day conventions, house systems, sidereal modes, and return-flag semantics. Neither is common. Plan 2–4 weeks for a strong generalist to be productive behind the adapter, and insist the adapter exists before anyone else touches the package. The README quick-start is accurate but the surrounding documentation (deployment, ephemeris setup, Laravel integration, troubleshooting) is external to the repo — treat onboarding as a documentation task we own, not one we inherit.
How can I help you explore Laravel packages today?