jayeshmepani/swiss-ephemeris-ffi
PHP 8.3+ FFI wrapper for the Swiss Ephemeris C library. Exposes all 106 public API functions with 1:1 constant/signature parity and zero abstraction. No swetest shelling; outputs verified for parity via PHPUnit against swetest.
Installation:
composer require jayeshmepani/swiss-ephemeris-ffi
Ensure your php.ini has ffi.enable=true and PHP 8.3+.
Basic Usage:
use SwissEph\FFI\SwissEphFFI;
$sweph = new SwissEphFFI();
$jd = $sweph->swe_julday(2000, 1, 1, 12.0, SwissEphFFI::SE_GREG_CAL);
$xx = $sweph->getFFI()->new("double[6]");
$serr = $sweph->getFFI()->new("char[256]");
$result = $sweph->swe_calc_ut($jd, SwissEphFFI::SE_SUN, SwissEphFFI::SEFLG_SPEED, $xx, $serr);
Laravel Facade:
use SwissEph\FFI\Facades\SwissEph;
$sunPos = SwissEph::swe_calc_ut($jd, SwissEph::SE_SUN, SwissEph::SEFLG_SPEED, $xx, $serr);
config/swiss-ephemeris.php for custom paths or ephemeris file locations.storage/app/swisseph/ contains .se1 files (published via php artisan vendor:publish --provider="SwissEph\FFI\SwissEphFFIServiceProvider").Calculate planetary positions for a horoscope:
$jd = SwissEph::swe_julday(1990, 5, 17, 12.0, SwissEph::SE_GREG_CAL);
$xx = SwissEph::getFFI()->new("double[6]");
$serr = SwissEph::getFFI()->new("char[256]");
$sunPos = SwissEph::swe_calc_ut($jd, SwissEph::SE_SUN, 0, $xx, $serr);
$moonPos = SwissEph::swe_calc_ut($jd, SwissEph::SE_MOON, 0, $xx, $serr);
Initialization:
SwissEph::setLibraryPath('/custom/path/to/libswisseph.so');
Date Handling:
$jd = SwissEph::swe_julday($year, $month, $day, $hour, SwissEph::SE_GREG_CAL);
Planetary Calculations:
swe_calc_ut for positions/speeds:
$flags = SwissEph::SEFLG_SPEED | SwissEph::SEFLG_J2000;
$xx = SwissEph::getFFI()->new("double[6]");
$result = SwissEph::swe_calc_ut($jd, SwissEph::SE_MARS, $flags, $xx, $serr);
House Systems:
swe_house_pos:
$house = SwissEph::swe_house_pos($jd, SwissEph::SEFLG_SWIEPH, SwissEph::SE_PLACIDUS, $lat, $lon, $xx);
Error Handling:
$serr buffer:
if ($result < 0) {
throw new \RuntimeException(SwissEph::getFFI()->string($serr));
}
Laravel Service Providers:
Bind the FFI instance in AppServiceProvider:
public function register()
{
$this->app->singleton(SwissEphFFI::class, function ($app) {
return new SwissEphFFI($app['config']['swiss-ephemeris.library_path']);
});
}
Caching:
$jd = Cache::remember("jd_{$year}_{$month}_{$day}", now()->addHours(1), function () use ($year, $month, $day) {
return SwissEph::swe_julday($year, $month, $day, 0, SwissEph::SE_GREG_CAL);
});
Batch Processing:
swe_calc_ut in loops for multiple planets:
$planets = [SwissEph::SE_MERCURY, SwissEph::SE_VENUS, SwissEph::SE_EARTH];
foreach ($planets as $planet) {
$xx = SwissEph::getFFI()->new("double[6]");
SwissEph::swe_calc_ut($jd, $planet, 0, $xx, $serr);
// Process $xx[0] (longitude), $xx[1] (latitude), etc.
}
Asteroids/Comets:
swe_calc_ut with SE_AST_* constants:
$asteroid = SwissEph::SE_AST_433; // Eros
$xx = SwissEph::getFFI()->new("double[6]");
SwissEph::swe_calc_ut($jd, $asteroid, 0, $xx, $serr);
Publish Ephemeris Files:
php artisan vendor:publish --provider="SwissEph\FFI\SwissEphFFIServiceProvider" --tag="ephe-files"
Files land in storage/app/swisseph/.
Config Overrides:
// config/swiss-ephemeris.php
'library_path' => env('SWISS_EPHEMERIS_LIBRARY_PATH', null),
'ephe_path' => storage_path('app/swisseph'),
Queue Jobs:
dispatch(new CalculateHoroscopeJob($jd, $lat, $lon));
Singleton Behavior:
setLibraryPath() after instantiation may fail silently.$sweph = new SwissEphFFI('/custom/path/to/libswisseph.so');
FFI Memory Management:
double[6], char[256]) must be reallocated for each call or reused carefully.$xx = SwissEph::getFFI()->new("double[6]"); // Allocate once and reuse
Thread Safety:
Ephemeris File Paths:
.se1 files cause swe_calc_ut to return -1 with error "no ephemeris file".ephe_path in config:
SwissEph::setEphePath('/custom/ephe/path');
Date/Time Precision:
12.5 for 12:30 PM) must use . not , in some locales.12.5 (not 12,5) or enforce setlocale(LC_TIME, 'C').AGPL-3.0 Compliance:
How can I help you explore Laravel packages today?