{"id":19895330,"url":"https://github.com/razshare/catpaw-unsafe","last_synced_at":"2026-06-03T20:31:21.956Z","repository":{"id":222230057,"uuid":"756650342","full_name":"razshare/catpaw-unsafe","owner":"razshare","description":"Manage errors in php.","archived":false,"fork":false,"pushed_at":"2024-02-18T21:19:42.000Z","size":30,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-18T12:00:10.910Z","etag":null,"topics":["error-handling","php"],"latest_commit_sha":null,"homepage":"https://github.com/tncrazvan/catpaw-unsafe","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/razshare.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-02-13T03:00:59.000Z","updated_at":"2024-02-18T09:09:57.000Z","dependencies_parsed_at":"2024-02-18T22:25:53.809Z","dependency_job_id":null,"html_url":"https://github.com/razshare/catpaw-unsafe","commit_stats":null,"previous_names":["tncrazvan/catpaw-unsafe","razshare/catpaw-unsafe"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/razshare/catpaw-unsafe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razshare%2Fcatpaw-unsafe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razshare%2Fcatpaw-unsafe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razshare%2Fcatpaw-unsafe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razshare%2Fcatpaw-unsafe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/razshare","download_url":"https://codeload.github.com/razshare/catpaw-unsafe/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razshare%2Fcatpaw-unsafe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33878990,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["error-handling","php"],"created_at":"2024-11-12T18:36:25.709Z","updated_at":"2026-06-03T20:31:21.937Z","avatar_url":"https://github.com/razshare.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"Install with\n```sh\ncomposer require catpaw/unsafe\n```\n\n# Control flow is king\n\nI am of the opinion that control flow is one of the most important things to deal with as a programmer, it affects my thinking and at times it actually guides my problem solving process.\n\nManaging errors should not break the flow in which I control my program, I shouldn't have to jump up and down around my file to catch a new exception introduced by a new function I just invoked 20 lines above.\n\n\n# Try/Catch\n\nI found myself relying way too much on code like this\n\n```php\ntry {\n    // some code\n} catch(SomeException1 $e){\n    // manage error 1\n} catch(SomeException2 $e) {\n    // manage error 2\n} catch(SomeException3 $e) {\n    // manage error 3\n}\n```\n\nor even\n\n```php\ntry {\n    // some code\n} catch(SomeException1|SomeException2|SomeException3 $e){\n    // manage all errors in one place\n}\n```\n\nThe last one might make sense in theory, but in practice those exceptions might each mean something different, a different cause for an error.\n\nThe reality is that very often I lump those exceptions in together because I forget to manage them or because for some reason at 4 AM I decide on the spot \"yes, I should let my IDE dictate my error management\".\n\n\nTry/catch error handling has been (probably) the most popular way to manage errors in php, and I think it still is a valid way of dealing with errors in a global scope.\n\nI can't argue there is something nice about having one centralized place to manage all errors, but I don't want to be forced to approach error management all the time in that manner.\n\n\nIf you're anything like me you might prefer managing your error inline, directly at the source, so that you deal with it when it pops up and then you don't have to think about it anymore.\n\n# Unsafe\u003cT\u003e\n\nI have a solution.\n\nDo not throw exceptions in your code, instead return your errors as _Unsafe\u003cT\u003e_.\n\n```php\nnamespace CatPaw\\Unsafe;\n/**\n * @template T\n */\nreadonly class Unsafe {\n    /** @var T $value */\n    public $value;\n    public false|Error $error;\n}\n```\n\nUse the _ok()_ and _error()_ functions to create _Unsafe\u003cT\u003e_ objects.\n\n# ok()\n\n```php\nnamespace CatPaw\\Unsafe;\n/**\n * @template T\n * @param T $value\n * @return Unsafe\u003cT\u003e\n */\nfunction ok($value);\n```\nReturn _ok($value)_ whenever there are no errors in your program.\n\nThis function will create a new _Unsafe\u003cT\u003e_ with a valid _$value_ and no error.\n\n# error()\n\n```php\nnamespace CatPaw\\Core;\n/**\n * @param string|Error $error\n * @return Unsafe\u003cvoid\u003e\n */\nfunction error($error);\n```\nReturn _error($error)_ whenever you encounter an error in your program and want to propagate it upstream.\n\nThis function will create a new _Unsafe\u003cT\u003e_ with a _null $value_ and the given _error_.\n\n# Example\n\nThe following example tries to read the contents of a file while managing errors.\n\n\nFirst I'm declaring all entities involved, classes and functions.\n\n```php\n\u003c?php\nuse CatPaw\\Unsafe\\Unsafe;\nuse function CatPaw\\Unsafe\\anyError;\nuse function CatPaw\\Unsafe\\error;\nuse function CatPaw\\Unsafe\\ok;\n\n// This is not required, but you can return custom errors\nclass FileNotFoundError extends Error {\n    public function __construct(private string $fileName) {\n        parent::__construct('', 0, null);\n    }\n\n    public function __toString() {\n        return \"I'm looking for $this-\u003efileName, where's the file Lebowski????\";\n    }\n}\n\n/**\n * Attempt to open a file.\n * @param string $fileName \n * @return Unsafe\u003cresource\u003e \n */\nfunction openFile(string $fileName){\n    if(!file_exists($fileName)){\n        return error(new FileNotFoundError($fileName));\n    }\n    if(!$file = fopen('file.txt', 'r+')){\n        return error(\"Something went wrong while trying to open file $fileName.\");\n    }\n    return ok($file);\n}\n\n/**\n * Attempt to read 5 bytes from the file.\n * @param resource $stream \n * @return Unsafe\u003cstring\u003e \n */\nfunction readFile($stream){\n    $content = fread($stream, 5);\n    if(false === $content){\n        return error(\"Couldn't read from stream.\");\n    }\n\n    return ok($content);\n}\n\n/**\n * Attempt to close the file.\n * @param resource $stream \n * @return Unsafe\u003cvoid\u003e \n */\nfunction closeFile($stream){\n    if(!fclose($stream)){\n        return error(\"Couldn't close file.\");\n    }\n    return ok();\n}\n```\n\nthen \n\n1. open a file\n2. read its contents\n3. close the file\n\n```php\n\u003c?php\n// open file\n$file = openFile('file.txt')-\u003etry($error);\nif ($error) {\n    echo $error.PHP_EOL;\n    die();\n}\n\n// read contents\n$contents = readFile($file)-\u003etry($error);\nif ($error) {\n    echo $error.PHP_EOL;\n    die();\n}\n\n// close file\ncloseFile($file)-\u003etry($error);\nif ($error) {\n    echo $error.PHP_EOL;\n    die();\n}\n\necho $contents.PHP_EOL;\n```\nThis code will print the contents of `file.txt` if all operations succeed.\n\nEach time `-\u003etry($error)` is invoked the _Unsafe_ object tries to unwrap its value.\\\nIf the _Unsafe_ object contains an error, the value returned by `-\u003etry($error)` resolves to `null` and the variable `$error` is assigned the contained error by reference.\n\n\n# anyError()\n\nYou can use _anyError()_ to deal away with the repetitive snippet\n\n```php\nif($error){\n    echo $error.PHP_EOL;\n    // manage error here...\n}\n```\n\nHere's the same example but written using _anyError()_\n\n```php\n\u003c?php\n$contents = anyError(function() {\n    // open file\n    $file = openFile('file.txt')-\u003etry($error)\n    or yield $error;\n\n    // read contents\n    $contents = readFile($file)-\u003etry($error)\n    or yield $error;\n\n\n    // close file\n    closeFile($file)-\u003etry($error)\n    or yield $error;\n\n    return $contents;\n})-\u003etry($error);\n\nif($error){\n    echo $error.PHP_EOL;\n    die();\n}\n\necho $contents.PHP_EOL;\n```\n\nThe _anyError()_ function takes a generator function and it consumes it step by step.\n\nWhen the generator function `yield`s an _Error_ or an _Unsafe\u003cT\u003e_ containing an _Error_, the _anyError_ function will stop executing the generator immediately and return a new _Unsafe\u003cT\u003e_ containing the given error.\n\nEffectively, `or yield $error` acts like \n```php\nif($error){\n    return error($error);\n}\n```\nOn the other hand, if the result of `-\u003etry()` is valid, the `or \u003cexpression\u003e` is not executed and the generator keeps running until it reaches the next `yield error` statement, the next `return` statement or until the generator is consumed.\n\n\n# Matching\n\nSince errors are results, you can actually `match()` them\n\n```php\n$result = anyError(/* ... */)-\u003etry($error) or match($error:class){\n    FileNotFoundError::class =\u003e $error-\u003egetMessage(),\n    default =\u003e \"Let me explain something to you. Um, I am not Mr. Lebowski. You're Mr. Lebowski.\",\n\n};\n```\n\nor apply any sort of expression that you want _inline_.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frazshare%2Fcatpaw-unsafe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frazshare%2Fcatpaw-unsafe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frazshare%2Fcatpaw-unsafe/lists"}