myopensoft/laravel-jakim-esolat-api

Laravel package for JAKIM e-Solat prayer times API - fetch, cache, and manage Malaysian prayer times

Maintainers

Package info

gitlab.com/myopensoft/laravel-jakim-esolat-api

Homepage

Issues

pkg:composer/myopensoft/laravel-jakim-esolat-api

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

1.0.0 2026-03-17 05:29 UTC

This package is auto-updated.

Last update: 2026-03-16 21:31:48 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads License: MIT

A Laravel package that wraps the JAKIM (Jabatan Kemajuan Islam Malaysia) e-Solat API to fetch, cache, and manage Malaysian prayer times. It provides an intelligent fallback chain (Cache, Database, Live API), an expressive enum-based zone system covering all 58 Malaysian zones, and Artisan commands for automated data management.

Table of Contents

Requirements

  • PHP 8.3 or higher
  • Laravel 11 or 12

Installation

Install the package via Composer:

composer require myopensoft/laravel-jakim-esolat-api

Publish the configuration file:

php artisan vendor:publish --tag="jakim-esolat-api-config"

Publish and run the migration:

php artisan vendor:publish --tag="jakim-esolat-api-migrations"
php artisan migrate

Usage

Using the Facade

The EsolatApi facade provides quick access to prayer time data. It accepts either a SolatZone enum or a plain zone code string.

use Myopensoft\LaravelJakimEsolatApi\Facades\EsolatApi;
use Myopensoft\LaravelJakimEsolatApi\Enums\SolatZone;

// Get today's prayer times for Kuala Lumpur
$prayerTime = EsolatApi::getPrayerTime(SolatZone::WLY01);

// Get prayer times for a specific date
$prayerTime = EsolatApi::getPrayerTime('WLY01', '2026-03-17');

if ($prayerTime) {
    echo $prayerTime->fajr;     // "5:57"
    echo $prayerTime->dhuhr;    // "1:14"
    echo $prayerTime->maghrib;  // "7:18"
    echo $prayerTime->hijri;    // "18 Ramadan 1447"
}

Using Dependency Injection

You can inject the EsolatApi service directly via the contract interface or the concrete class:

use Myopensoft\LaravelJakimEsolatApi\Contracts\EsolatApi;
use Myopensoft\LaravelJakimEsolatApi\Exceptions\EsolatApiException;

class PrayerTimeController extends Controller
{
    public function __construct(
        private readonly EsolatApi $esolatApi,
    ) {}

    public function show(string $zone)
    {
        try {
            $prayerTime = $this->esolatApi->getPrayerTime($zone);
        } catch (EsolatApiException $e) {
            abort(422, $e->getMessage());
        }

        if (! $prayerTime) {
            abort(404, 'Prayer times not available for this zone.');
        }

        return response()->json($prayerTime->toArray());
    }
}

Note: Passing an invalid zone code string throws EsolatApiException. Use SolatZone enum for type-safe zone references.

Using SolatZone Enum

The SolatZone enum provides all 58 Malaysian prayer time zones as type-safe values with rich metadata.

use Myopensoft\LaravelJakimEsolatApi\Enums\SolatZone;

// Use a specific zone
$zone = SolatZone::WLY01;
echo $zone->value;     // "WLY01"
echo $zone->state();   // "Wilayah Persekutuan"
echo $zone->district();// "Kuala Lumpur, Putrajaya"
echo $zone->label();   // "Wilayah Persekutuan | Kuala Lumpur, Putrajaya"

// Get all zones as a key-value array (useful for dropdowns)
$allZones = SolatZone::toArray();
// ["JHR01" => "Johor | Pulau Aur dan Pulau Pemanggil", ...]

// Group zones by state
$grouped = SolatZone::groupByState();
// ["Johor" => [SolatZone::JHR01, SolatZone::JHR02, ...], ...]

// Create from string value
$zone = SolatZone::from('SGR01');    // throws ValueError if invalid
$zone = SolatZone::tryFrom('SGR01'); // returns null if invalid

Accessing Zone Info via Attributes

Each zone case is annotated with a #[ZoneInfo] PHP attribute that stores the state and district metadata.

use Myopensoft\LaravelJakimEsolatApi\Enums\SolatZone;
use Myopensoft\LaravelJakimEsolatApi\Attributes\ZoneInfo;

$zone = SolatZone::SGR01;
$info = $zone->info(); // Returns a ZoneInfo instance

echo $info->state;    // "Selangor"
echo $info->district; // "Gombak, Petaling, Sepang, Hulu Langat, Hulu Selangor, Rawang, S.Alam"

Working with PrayerTimeData DTO

The PrayerTimeData is a readonly data transfer object returned by getPrayerTime(). It contains formatted, display-ready values.

use Myopensoft\LaravelJakimEsolatApi\Facades\EsolatApi;

$data = EsolatApi::getPrayerTime('WLY01');

// Access individual properties
$data->zone;      // "WLY01"
$data->zoneLabel; // "Wilayah Persekutuan | Kuala Lumpur, Putrajaya"
$data->date;      // "2026-03-17"
$data->hijri;     // "18 Ramadan 1447"
$data->fajr;      // "5:57"
$data->syuruk;    // "7:08"
$data->dhuhr;     // "1:14"
$data->asr;       // "4:22"
$data->maghrib;   // "7:18"
$data->isha;      // "8:30"

// Convert to array (useful for JSON responses)
$array = $data->toArray();
// [
//     'zone' => 'WLY01',
//     'zone_label' => 'Wilayah Persekutuan | Kuala Lumpur, Putrajaya',
//     'date' => '2026-03-17',
//     'hijri' => '18 Ramadan 1447',
//     'fajr' => '5:57',
//     'syuruk' => '7:08',
//     'dhuhr' => '1:14',
//     'asr' => '4:22',
//     'maghrib' => '7:18',
//     'isha' => '8:30',
// ]

Using PrayerType Enum for Labels

The PrayerType enum maps prayer type identifiers to their Malay names. This is useful for building multilingual UI labels.

use Myopensoft\LaravelJakimEsolatApi\Enums\PrayerType;

echo PrayerType::Fajr->value;    // "fajr"
echo PrayerType::Fajr->label();  // "Subuh"

echo PrayerType::Dhuhr->value;   // "dhuhr"
echo PrayerType::Dhuhr->label(); // "Zohor"

echo PrayerType::Asr->value;     // "asr"
echo PrayerType::Asr->label();   // "Asar"

echo PrayerType::Maghrib->label(); // "Maghrib"
echo PrayerType::Isha->label();    // "Isyak"
echo PrayerType::Syuruk->label();  // "Syuruk"

// Iterate over all prayer types
foreach (PrayerType::cases() as $type) {
    echo "{$type->label()}: {$type->value}";
}

HijriDateConverter Usage

The HijriDateConverter converts Hijri date strings in YYYY-MM-DD format to a human-readable format.

use Myopensoft\LaravelJakimEsolatApi\Services\HijriDateConverter;

// Default format: "{d} {m} {y}"
echo HijriDateConverter::format('1447-09-18');
// "18 Ramadan 1447"

// Custom format
echo HijriDateConverter::format('1447-09-18', '{m} {d}, {y}');
// "Ramadan 18, 1447"

echo HijriDateConverter::format('1447-01-01');
// "01 Muharram 1447"

The converter supports all 12 Hijri months via the HijriMonth enum:

CodeMonth Name
01Muharram
02Safar
03Rabiulawwal
04Rabiulakhir
05Jamadilawwal
06Jamadilakhir
07Rejab
08Syaaban
09Ramadan
10Syawal
11Zulkaedah
12Zulhijjah

Artisan Commands

esolat:fetch

Fetches prayer times from the JAKIM e-Solat API and stores them in the database. By default, fetches data for all 58 zones. Zones that already have a full 7 days of data are automatically skipped.

# Fetch all zones
php artisan esolat:fetch

# Fetch a specific zone only
php artisan esolat:fetch --zone=WLY01

# Preview what would be fetched without making any changes
php artisan esolat:fetch --dry-run

esolat:prune

Removes prayer time records with dates in the past to keep the database lean.

# Delete all past records
php artisan esolat:prune

# Keep the last 7 days of history
php artisan esolat:prune --days=7

# Preview how many records would be deleted
php artisan esolat:prune --dry-run

Scheduling

For automated data management, add both commands to your Laravel scheduler:

// Laravel 11+ (routes/console.php)
use Illuminate\Support\Facades\Schedule;

Schedule::command('esolat:fetch')->daily();
Schedule::command('esolat:prune')->daily();

Configuration

After publishing the config file, you can modify config/jakim-esolat-api.php:

return [

    /*
    |--------------------------------------------------------------------------
    | JAKIM e-Solat API Base URL
    |--------------------------------------------------------------------------
    |
    | The base URL for the JAKIM e-Solat API endpoint. You should not need
    | to change this unless JAKIM changes their API location.
    |
    */

    'base_url' => env('ESOLAT_API_URL', 'https://www.e-solat.gov.my/index.php'),

    /*
    |--------------------------------------------------------------------------
    | Cache Configuration
    |--------------------------------------------------------------------------
    |
    | Configure how prayer time data is cached. By default, cached data
    | expires at end of day. Set ttl to a specific number of seconds
    | to override this behavior.
    |
    */

    'cache' => [
        'prefix' => env('ESOLAT_CACHE_PREFIX', 'esolat'),
        'ttl' => null, // null = end of day, or seconds (e.g. 3600)
    ],

    /*
    |--------------------------------------------------------------------------
    | HTTP Retry Configuration
    |--------------------------------------------------------------------------
    |
    | Configure retry behavior for API requests to handle transient failures.
    |
    */

    'timeout' => 15, // seconds

    'retry' => [
        'times' => 3,
        'delay' => 200, // milliseconds
    ],

    'fetch' => [
        'throttle' => 500, // milliseconds delay between API calls
    ],

];

Environment Variables

VariableDefaultDescription
ESOLAT_API_URLhttps://www.e-solat.gov.my/index.phpBase URL of the e-Solat API
ESOLAT_CACHE_PREFIXesolatPrefix for cache keys

Configuration Options

KeyTypeDefaultDescription
base_urlstringSee aboveJAKIM e-Solat API base URL
cache.prefixstringesolatCache key prefix
cache.ttlint\|nullnullCache TTL in seconds. null means cache until end of day.
timeoutint15HTTP request timeout in seconds
retry.timesint3Number of retry attempts for failed HTTP requests
retry.delayint200Delay between retries in milliseconds
fetch.throttleint500Delay between zones in esolat:fetch command (milliseconds)

Testing

The package uses Pest for testing.

composer test

To run tests with coverage:

composer test-coverage

To run static analysis:

composer analyse

To format code with Laravel Pint:

composer format

Credits

License

The MIT License (MIT). Please see License File for more information.