<?php
/**
 * File Lock
 *
 * Provides file-based locking mechanism for concurrent access control.
 *
 * PHP version 8.2+
 *
 * @category Support
 * @package  IronKanban\Support
 * @author   Keith Solomon <keith@keithsolomon.net>
 * @license  Unlicense https://unlicense.org
 * @link     https://git.keithsolomon.net/keith/IronKanban
 */

declare(strict_types=1);

namespace IronKanban\Support;

use RuntimeException;

/**
 * FileLock provides file-based locking mechanism
 *
 * @category Support
 * @package  IronKanban\Support
 * @author   Keith Solomon <keith@keithsolomon.net>
 * @license  Unlicense https://unlicense.org
 * @link     https://git.keithsolomon.net/keith/IronKanban
 */
class FileLock {
    /**
     * Run a callback with a file lock
     *
     * @param string   $lockPath The path to the lock file
     * @param callable $callback The callback to execute
     *
     * @return mixed The result of the callback
     *
     * @throws RuntimeException If lock cannot be acquired
     */
    public static function run(string $lockPath, callable $callback): mixed {
        $handle = fopen($lockPath, 'c+');

        if ($handle === false) {
            throw new RuntimeException("Unable to open lock file: {$lockPath}");
        }

        try {
            if (!flock($handle, LOCK_EX)) {
                throw new RuntimeException("Unable to acquire lock: {$lockPath}");
            }

            return $callback();
        } finally {
            flock($handle, LOCK_UN);
            fclose($handle);
        }
    }
}
