Skip to content

Instantly share code, notes, and snippets.

@jaketoolson
Created January 26, 2026 22:30
Show Gist options
  • Select an option

  • Save jaketoolson/daaea7df12e435366b88b58b1a7f5128 to your computer and use it in GitHub Desktop.

Select an option

Save jaketoolson/daaea7df12e435366b88b58b1a7f5128 to your computer and use it in GitHub Desktop.
<?php
declare(strict_types=1);
namespace Platform\Application;
use Throwable;
/**
* @template T
*/
readonly class Result
{
/**
* @param T|null $value
*/
public function __construct(
private bool $isSuccess = false,
private mixed $value = null,
private ?string $errorMessage = null,
private ?Throwable $exception = null,
private ?int $code = null
) {}
/**
* Create a successful result with a value
*
* @template TValue
*
* @param TValue|null $value
* @return Result<TValue>
*/
public static function ok(mixed $value = null): self
{
return new self(true, $value);
}
/**
* Alias for ok()
*
* @template TValue
*
* @param TValue|null $value
* @return Result<TValue>
*/
public static function success(mixed $value = null): self
{
return self::ok($value);
}
/**
* Create a failed result with an error message
*
* @template TValue
*
* @param string $errorMessage The error message
* @param int|TValue|null $codeOrValue HTTP status code (int) or value (mixed). If int, treated as code; otherwise as value
* @param TValue|Throwable|null $valueOrException Value or exception. If previous param was int (code), this is value or exception. If previous was value, this must be exception or null
* @return Result<TValue>
*/
public static function fail(string $errorMessage, mixed $codeOrValue = null, mixed $valueOrException = null): self
{
// Handle different call signatures:
// 1. fail('message') -> code: null, value: null, exception: null
// 2. fail('message', 404) -> code: 404, value: null, exception: null
// 3. fail('message', 404, $exception) -> code: 404, value: null, exception: $exception
// 4. fail('message', ['data']) -> code: null, value: ['data'], exception: null
// 5. fail('message', ['data'], $exception) -> code: null, value: ['data'], exception: $exception
$code = null;
$value = null;
$exception = null;
if ($codeOrValue !== null) {
if (is_int($codeOrValue)) {
// Parameter is HTTP status code
$code = $codeOrValue;
// Third parameter could be value or exception
if ($valueOrException !== null) {
if ($valueOrException instanceof Throwable) {
$exception = $valueOrException;
} else {
$value = $valueOrException;
}
}
} else {
// Parameter is value (not a code)
$value = $codeOrValue;
// Third parameter must be exception if provided
if ($valueOrException !== null) {
if ($valueOrException instanceof Throwable) {
$exception = $valueOrException;
} else {
// Invalid usage - third param should be exception
throw new \InvalidArgumentException('When second parameter is a value, third parameter must be a Throwable or null');
}
}
}
}
return new self(false, $value, $errorMessage, $exception, $code);
}
/**
* Alias for fail()
*
* @template TValue
*
* @param int|TValue|null $codeOrValue
* @param TValue|Throwable|null $valueOrException
* @return Result<TValue>
*/
public static function error(string $errorMessage, mixed $codeOrValue = null, mixed $valueOrException = null): self
{
return self::fail($errorMessage, $codeOrValue, $valueOrException);
}
final public function isSuccess(): bool
{
return $this->isSuccess === true;
}
final public function isError(): bool
{
return $this->isSuccess === false;
}
final public function getErrorMessage(): ?string
{
return $this->errorMessage;
}
final public function getErrorException(): ?Throwable
{
return $this->exception;
}
/**
* Get the value of the result
*
* @return T|null
*/
final public function getValue(): mixed
{
return $this->value;
}
/**
* Get the HTTP status code (if set)
*/
final public function code(): ?int
{
return $this->code;
}
/**
* Get the error message (alias for getErrorMessage())
*
* Note: Cannot use error() as method name since there's already a static error() factory method
*/
final public function getError(): ?string
{
return $this->errorMessage;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment