upside/attempt

Installs: 1

Dependents: 0

Suggesters: 0

Security: 0

pkg:composer/upside/attempt

dev-master 2025-09-20 20:22 UTC

This package is auto-updated.

Last update: 2025-10-20 20:32:44 UTC


README

Библиотека для функциональной обработки ошибок в PHP, вдохновленная типом Result из Rust. Предоставляет монадические контейнеры для обработки операций, которые могут завершиться успехом или ошибкой.

Установка

composer require upside/attempt

Класс Attempt

Класс Attempt предоставляет набор статических методов для безопасного выполнения операций, которые могут вызывать исключения. Все методы возвращают объект Result, содержащий либо успешный результат, либо исключение.

Методы Attempt

Attempt::of() - безопасное выполнение операции

use Upside\Std\Functional\Attempt;

// Базовое использование
$result = Attempt::of(function() {
    return file_get_contents('/path/to/file.txt');
});

if ($result->isOk()) {
    echo $result->unwrap();
} else {
    echo "Ошибка: " . $result->unwrapErr()->getMessage();
}

Attempt::all() - выполнение нескольких операций

use Upside\Std\Functional\Attempt;

// Все операции должны завершиться успешно
$results = Attempt::all([
    function() { return fetchUserData(1); },
    function() { return fetchUserPosts(1); },
    function() { return fetchUserSettings(1); },
]);

if ($results->isOk()) {
    [$user, $posts, $settings] = $results->unwrap();
    renderUserProfile($user, $posts, $settings);
}

Attempt::any() - первая успешная операция

use Upside\Std\Functional\Attempt;

// Возвращает первый успешный результат
$data = Attempt::any([
    function() { return fetchFromCache('user_1'); },
    function() { return fetchFromDatabase('user_1'); },
    function() { return fetchFromAPI('user_1'); },
]);

if ($data->isOk()) {
    displayUser($data->unwrap());
}

Attempt::sequence() - последовательные операции

use Upside\Std\Functional\Attempt;

// Цепочка операций, где каждая следующая зависит от предыдущей
$result = Attempt::sequence([
    function($value) { return login($credentials); },
    function($token) { return getUserProfile($token); },
    function($profile) { return getRecentActivity($profile['id']); },
]);

if ($result->isOk()) {
    $activity = $result->unwrap();
    showDashboard($activity);
}

Attempt::retry() - повторные попытки

use Upside\Std\Functional\Attempt;

// Повторение операции с задержкой
$result = Attempt::retry(
    function() { return sendPaymentRequest($order); },
    3,      // 3 попытки
    100,    // Задержка 100мс
    2.0     // Удваивать задержку после каждой попытки
);

if ($result->isOk()) {
    processPaymentConfirmation($result->unwrap());
}

Реальные примеры использования Attempt

Пример 1: Чтение конфигурации

use Upside\Std\Functional\Attempt;

// Чтение конфигурации с fallback
$config = Attempt::any([
    function() { return parseConfigFile('/etc/app/config.yaml'); },
    function() { return parseConfigFile('./config.yaml'); },
    function() { return getDefaultConfig(); },
])->unwrapOr([]);

Пример 2: Обработка платежа

use Upside\Std\Functional\Attempt;

// Попытка обработки платежа с повторными попытками
$paymentResult = Attempt::retry(
    function() use ($paymentData) {
        return $paymentGateway->process($paymentData);
    },
    5,      // 5 попыток
    500,    // Начальная задержка 500мс
    1.5     // Увеличивать задержку в 1.5 раза
);

if ($paymentResult->isErr()) {
    logError($paymentResult->unwrapErr());
    notifyAdmin('Payment processing failed');
}

Пример 3: Загрузка данных из нескольких источников

use Upside\Std\Functional\Attempt;

// Загрузка данных пользователя
$userData = Attempt::sequence([
    function() use ($userId) { return authenticate($userId); },
    function($token) use ($userId) { return fetchUserDetails($userId, $token); },
    function($details) { return enrichWithAdditionalData($details); },
]);

$userData->map(function($data) {
    Cache::set('user_' . $data['id'], $data, 3600);
    return $data;
});

Класс Result

Класс Result представляет результат операции, который может быть успешным (Ok) или содержать ошибку (Err). Это монадический контейнер, предоставляющий методы для функциональной обработки результатов.

Методы Result

Базовые методы

use Upside\Std\Functional\Result;

// Создание результатов
$success = Result::ok("Успех");
$error = Result::err(new RuntimeException("Ошибка"));

// Проверка состояния
if ($success->isOk()) {
    echo $success->unwrap();
}

if ($error->isErr()) {
    echo $error->unwrapErr()->getMessage();
}

map() - преобразование значения

use Upside\Std\Functional\Result;

// Преобразование успешного значения
$result = Result::ok(5)
    ->map(fn($x) => $x * 2)
    ->map(fn($x) => $x + 10);

echo $result->unwrap(); // 20

mapErr() - преобразование ошибки

use Upside\Std\Functional\Result;

// Преобразование ошибки
$result = Result::err(new RuntimeException("DB error"))
    ->mapErr(fn($e) => new ApplicationException("Application error", 0, $e));

andThen() - цепочка операций

use Upside\Std\Functional\Result;

// Последовательные операции
$result = Result::ok(10)
    ->andThen(fn($x) => Result::ok($x * 2))
    ->andThen(fn($x) => Result::ok($x - 5));

echo $result->unwrap(); // 15

orElse()- обработка ошибок

use Upside\Std\Functional\Result;

// Восстановление после ошибки
$result = Result::err(new Exception("Ошибка"))
    ->orElse(fn($e) => Result::ok("Значение по умолчанию"));

echo $result->unwrap(); // "Значение по умолчанию"

unwrapOr(), unwrapOrElse() - значения по умолчанию

use Upside\Std\Functional\Result;

// Значение по умолчанию
$value = Result::err(new Exception())->unwrapOr("default");
echo $value; // "default"

// Вычисляемое значение по умолчанию
$value = Result::err(new Exception())->unwrapOrElse(fn($e) => "Error: " . $e->getMessage());

Реальные примеры использования Result

Пример 1: Валидация данных

use Upside\Std\Functional\Result;

// Валидация и преобразование данных
function validateUserData(array $data): Result {
    return Result::ok($data)
        ->andThen(fn($data) => validateEmail($data['email']))
        ->andThen(fn($data) => validatePassword($data['password']))
        ->map(fn($data) => [
            'email' => strtolower($data['email']),
            'password' => password_hash($data['password'], PASSWORD_DEFAULT)
        ]);
}

$validationResult = validateUserData($_POST);
if ($validationResult->isOk()) {
    $user = createUser($validationResult->unwrap());
} else {
    showErrors($validationResult->unwrapErr());
}

Пример 2: Работа с базой данных

use Upside\Std\Functional\Result;

// Функциональная работа с базой данных
function getUserWithPosts($userId): Result {
    return getDatabaseConnection()
        ->andThen(fn($db) => $db->query("SELECT * FROM users WHERE id = ?", [$userId]))
        ->andThen(fn($user) => getPostsForUser($user['id'])->map(fn($posts) => [
            'user' => $user,
            'posts' => $posts
        ]));
}

getUserWithPosts(1)->map(function($data) {
    renderProfile($data['user'], $data['posts']);
})->mapErr(function($error) {
    logError($error);
    showErrorPage();
});

Пример 3: Комплексная обработка запроса

use Upside\Std\Functional\Result;

// Обработка API запроса
function handleApiRequest($request): Result {
    return validateRequest($request)
        ->andThen(fn($validated) => authenticateUser($validated))
        ->andThen(fn($user) => checkPermissions($user))
        ->andThen(fn($user) => processRequest($user, $request))
        ->map(fn($result) => [
            'status' => 'success',
            'data' => $result
        ])
        ->mapErr(fn($error) => [
            'status' => 'error',
            'message' => $error->getMessage()
        ]);
}

$response = handleApiRequest($_REQUEST);
if ($response->isOk()) {
    header('Content-Type: application/json');
    echo json_encode($response->unwrap());
} else {
    header('HTTP/1.1 500 Internal Server Error');
    echo json_encode($response->unwrapErr());
}

Преимущества подхода

  1. Явная обработка ошибок - ошибки становятся частью типа системы
  2. Композируемость - операции легко комбинируются в цепочки
  3. Избегание исключений - контроль потока выполнения без throw/catch
  4. Типобезопасность - полная поддержка статического анализа
  5. Функциональный стиль - чистые функции и неизменяемость

Заключение

Библиотека предоставляет мощный инструментарий для функциональной обработки ошибок в PHP, позволяя писать более надежный и выразительный код. Подход особенно полезен в сложных приложениях, где важно явно обрабатывать все возможные сценарии ошибок.

Лицензия

MIT