{"id":34010592,"url":"https://github.com/bumpcore/court","last_synced_at":"2025-12-13T12:08:54.943Z","repository":{"id":317066151,"uuid":"1065536537","full_name":"bumpcore/court","owner":"bumpcore","description":"A featherweight, complementary validation pipeline. ","archived":false,"fork":false,"pushed_at":"2025-09-28T17:38:32.000Z","size":69,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"1.x","last_synced_at":"2025-11-01T00:17:39.879Z","etag":null,"topics":["laravel","library","php","validation"],"latest_commit_sha":null,"homepage":"https://bumpcore.com/docs/court","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bumpcore.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-27T23:28:50.000Z","updated_at":"2025-10-06T10:33:57.000Z","dependencies_parsed_at":"2025-09-28T17:19:36.081Z","dependency_job_id":"8ea538fe-94ef-4af6-a04a-b6709bf168be","html_url":"https://github.com/bumpcore/court","commit_stats":null,"previous_names":["bumpcore/court"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/bumpcore/court","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bumpcore%2Fcourt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bumpcore%2Fcourt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bumpcore%2Fcourt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bumpcore%2Fcourt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bumpcore","download_url":"https://codeload.github.com/bumpcore/court/tar.gz/refs/heads/1.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bumpcore%2Fcourt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27705442,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-13T02:00:09.769Z","response_time":147,"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":["laravel","library","php","validation"],"created_at":"2025-12-13T12:08:50.979Z","updated_at":"2025-12-13T12:08:54.935Z","avatar_url":"https://github.com/bumpcore.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Court - A Plain PHP Validation Library\n\nCourt is an extremely simple and lightweight validation library for PHP. It provides a featherweight framework to validate any kind of data in a structured and reusable way. It is dependency-free and works out of the box with Laravel.\n\nYou can imagine Court as a set of building blocks that you can use to create your own validation logic. It doesn't impose any specific structure or pattern on your code, allowing you to use it in a way that best fits your needs.\n\nThis package works out of the box, though it is also highly extensible. You can create your own objection types, guard resolvers, objection collectors and the objection bag returned by the verdict. We encourage you to extend the package to fit your specific use cases.\n\n## Version Table\n\n| Court | PHP  | Laravel |\n|-------|------|---------|\n| 1.x   | ^8.2 | ^11.0   |\n\n## Installation\n\n```bash\ncomposer require bumpcore/court\n```\n\n## Quick Start\n\n```php\nuse BumpCore\\Court\\Court;\n\n$subject = \"I love elephants and bears\";\n\n$verdict = Court::of($subject)\n    -\u003eguards([\n        function ($subject, $objection) {\n            if (str_contains($subject, 'elephants')) {\n                $objection('No elephants allowed!');\n            }\n        },\n        function ($subject, $objection) {\n            if (str_contains($subject, 'bears')) {\n                $objection('No bears allowed!');\n            }\n        },\n    ])\n    -\u003everdict();\n\nif ($verdict-\u003eisNotEmpty()) {\n    foreach ($verdict-\u003eall() as $objection) {\n        echo $objection-\u003evalue() . PHP_EOL;\n    }\n}\n```\n\n## Philosophy\n\nCourt is not built for replacing existing validation solutions like Laravel's or Symfony's validation components. Instead, it's meant to be used alongside them, providing a simple way to enforce business rules and complex validation logic that doesn't fit neatly into traditional validation paradigms. Nonetheless, the extendible nature of Court allows you to build your own validation framework on top of it if you wish.\n\n## Core Concepts\n\n### Subject\n\nThe subject is the data that you want to validate. It can be of any type: string, integer, array, object, etc.\n\n### Court\n\nThe main orchestrator that runs guards against the subject and collects objections.\n\n### Guards\n\nFunctions or classes that examine the subject and raise objections if validation rules are violated. Guards can be:\n\n- Closures / callables\n- Invokable classes\n- Classes with `verdict()` method\n- Classes with `handle()` method\n\n### Objections\n\nIssues raised by guards when the subject fails validation. Each objection contains a value describing the problem. Though, virtually they can hold any type of data.\n\n### Verdict\n\nThe final result of the validation process, containing all objections raised by the guards.\n\n## Basic Usage\n\n### Creating a Court\n\n```php\n$court = Court::of($subject);\n// or\n$court = new Court($subject);\n```\n\n### Adding Guards\n\n#### Array of Guards\n\n```php\n$court-\u003eguards([\n    $guard1,\n    $guard2,\n]);\n```\n\n#### Variadic Arguments\n\n```php\n$court-\u003eguards($guard1, $guard2);\n```\n\n### Getting the Verdict\n\n```php\n$verdict = $court-\u003everdict();\n\nif ($verdict-\u003eisNotEmpty()) {\n    foreach ($verdict-\u003eall() as $objection) {\n        echo $objection-\u003evalue() . PHP_EOL;\n    }\n}\n```\n\n## Guards\n\nBy default, Court supports various types of guards. You can use any of the following:\n\n### Closure / Callable Guards\n\n```php\n$guard = function ($subject, $objection) {\n    if ($subject \u003c 18) {\n        $objection('Subject must be at least 18 years old.');\n    }\n};\n```\n\n### Invokable Class Guards\n\n```php\nclass AgeGuard {\n    public function __invoke($subject, $objection) {\n        if ($subject \u003c 18) {\n            $objection('Subject must be at least 18 years old.');\n        }\n    }\n}\n\n$guard = new AgeGuard();\n```\n\n### Class with `verdict()` Method\n\n```php\nclass AgeGuard {\n    public function verdict($subject, $objection) {\n        if ($subject \u003c 18) {\n            $objection('Subject must be at least 18 years old.');\n        }\n    }\n}\n\n$guard = new AgeGuard();\n```\n\n### Class with `handle()` Method\n\n```php\nclass AgeGuard {\n    public function handle($subject, $objection) {\n        if ($subject \u003c 18) {\n            $objection('Subject must be at least 18 years old.');\n        }\n    }\n}\n\n$guard = new AgeGuard();\n```\n\n## Working with Verdicts\n\nThe `verdict()` method returns an `ObjectionBag` instance containing all objections raised by the guards. You can interact with the verdict in several ways:\n\n```php\n$verdict = $court-\u003everdict();\n\n// Get all objections\n$objections = $verdict-\u003eall();\n\n// Check if there are any objections\n$hasIssues = $verdict-\u003eisNotEmpty();\n$hasNoIssues = $verdict-\u003eisEmpty();\n\n// Merge with another verdict\n$verdict-\u003emerge($anotherCourt-\u003everdict());\n```\n\n## Laravel Integration\n\nCourt has out-of-the-box integration with Laravel. Everything works as expected, but while providing guards, you can leverage Laravel's service container to resolve dependencies.\n\nYou can use dependency injection in your guards:\n\n```php\nuse BumpCore\\Court\\Court;\n\n$subject = \"I love elephants and bears\";\n\n$verdict = Court::of($subject)\n    -\u003eguards([\n        \\App\\Guards\\ElephantGuard::class,\n        \\App\\Guards\\BearGuard::class,\n    ])\n    -\u003everdict();\n```\n\n```php\n// app/Guards/ElephantGuard.php\nnamespace App\\Guards;\n\nuse App\\Services\\ElephantService;\n\nclass ElephantGuard {\n    public function __construct(protected ElephantService $elephantService) {}\n\n    public function __invoke($subject, $objection) {\n        if ($this-\u003eelephantService-\u003ehasElephants($subject)) {\n            $objection('No elephants allowed!');\n        }\n    }\n}\n```\n\n## Extending Court\n\nAs we mentioned earlier, Court is highly extensible and it is encouraged to extend the package to fit your specific use cases.\n\nFor instance, the default `Objection` implementation holds any value. Instead, you can create your own objection type to hold a translatable message.\n\nIn the following we are using Laravel's `PotentiallyTranslatedString` class as `Objection`.\n\n```php\nuse Illuminate\\Translation\\PotentiallyTranslatedString;\nuse BumpCore\\Court\\Contracts\\Objection as ObjectionContract;\n\nclass PotentiallyTranslatedObjection extends PotentiallyTranslatedString implements ObjectionContract {\n    // You can add custom methods or properties if needed\n}\n```\n\nThen, you can instruct Court to use your custom objection type by providing a custom objection factory.\n\n```php\nuse BumpCore\\Court\\Court;\nuse BumpCore\\Court\\Factory;\n\n$verdict = Court::of($subject)\n    -\u003esetObjectionFactory(\n        fn($value) =\u003e new PotentiallyTranslatedObjection($value)\n    )\n    -\u003eguards([\n        fn($subject, $objection) =\u003e $objection('Translate this!')-\u003etranslate()\n    ])\n    -\u003everdict();\n\n// or if you want to set it globally\n\nFactory::setObjectionFactory(\n    fn($value) =\u003e new PotentiallyTranslatedObjection($value)\n);\n```\n\nIsn't this neat? The true power of Court lies in its extensibility. Besides objections, you can also create your own guard resolvers, objection collectors and objection bags.\n\n### Custom Objection Collector\n\nBy default, Court uses a simple objection collector that collects objections in an array. You can create your own objection collector by implementing the `ObjectionCollector` interface.\n\n```php\nuse BumpCore\\Court\\Contracts\\ObjectionCollector as ObjectionCollectorContract;\n\nclass CustomObjectionCollector implements ObjectionCollectorContract {\n    protected array $objections = [];\n\n    public function all(): array {\n        return $this-\u003eobjections;\n    }\n\n    public function closure() {\n        return function ($value) {\n            $this-\u003eobjections[] = $value;\n        };\n    }\n}\n\n// Then, you can instruct Court to use your custom objection collector\n\nCourt::of($subject)\n    -\u003esetObjectionCollectorFactory(fn() =\u003e new CustomObjectionCollector())\n    -\u003eguards([\n        fn($subject, $objection) =\u003e $objection('Custom objection collector!')\n    ])\n    -\u003everdict();\n\n// or if you want to set it globally\n\nFactory::setObjectionCollectorFactory(fn() =\u003e new CustomObjectionCollector());\n```\n\n\u003e **Note**: Since the default objection collector is also responsible for creating objections, when a custom collector is provided, the default objection factory will be ignored. So, if you want to use a custom objection type, you need to create the objection inside your custom collector.\n\n### Custom Guard Resolver\n\nThe default guard resolver can resolve guards of various types (closures, invokable classes, classes with `verdict()` or `handle()` methods). You can provide your own guard resolver by simply providing a callable that takes a guard and returns a callable with the signature `callable($subject, $objection)`.\n\n```php\n$resolver = function ($guard) {\n    if ($guard === 'required') {\n        return function($subject, $objection) {\n            if (empty($subject)) {\n                $objection('This field is required.');\n            }\n        };\n    }\n    elseif ($guard === 'email') {\n        return function($subject, $objection) {\n            if (!filter_var($subject, FILTER_VALIDATE_EMAIL)) {\n                $objection('This field must be a valid email address.');\n            }\n        };\n    }\n\n    if (is_callable($guard)) {\n        return $guard;\n    }\n    \n    throw new \\InvalidArgumentException('Unsupported guard type');\n};\n\nCourt::of('john.doe@example.com')\n    -\u003esetGuardResolver($resolver)\n    -\u003eguards([\n        'required',\n        'email',\n        fn($subject, $objection) =\u003e User::where('email', $subject)-\u003eexists() \n            \u0026\u0026 $objection('Email is already taken.')\n    ])\n    -\u003everdict();\n\n// or if you want to set it globally\n\nFactory::setGuardResolver($resolver);\n```\n\n### Custom Objection Bag\n\nIt is also possible to create your own objection bags by setting a custom objection bag factory. The objection bag must implement the `ObjectionBag` interface.\n\n```php\nuse BumpCore\\Court\\Contracts\\ObjectionBag as ObjectionBagContract;\nuse Illuminate\\Contracts\\Support\\MessageProvider;\nuse Illuminate\\Support\\MessageBag;\n\nclass CustomObjectionBag implements ObjectionBagContract, MessageProvider {\n    protected array $objections = [];\n\n    public function __construct(array $objections = []) {\n        $this-\u003eobjections = $objections;\n    }\n\n    public function all(): array {\n        return $this-\u003eobjections;\n    }\n\n    public function isEmpty(): bool {\n        return empty($this-\u003eobjections);\n    }\n\n    public function isNotEmpty(): bool {\n        return !$this-\u003eisEmpty();\n    }\n\n    public function merge($objections) {\n        if ($objections instanceof ObjectionBagContract) {\n            $objections = $objections-\u003eall();\n        }\n        \n        $this-\u003eobjections = array_merge($this-\u003eobjections, $objections);\n        \n        return $this;\n    }\n\n    public function messages() {\n        return new MessageBag(array_map(fn($objection) =\u003e (string) $objection-\u003evalue(), $this-\u003eobjections));\n    }\n}\n\n// Then, you can instruct Court to use your custom objection bag\n\n$verdict = Court::of($subject)\n    -\u003esetObjectionBagFactory(fn(array $objections) =\u003e new CustomObjectionBag($objections))\n    -\u003eguards([\n        fn($subject, $objection) =\u003e $objection('Custom objection bag!')\n    ])\n    -\u003everdict();\n\necho $verdict-\u003emessages()-\u003efirst();\n\n// or if you want to set it globally\nFactory::setObjectionBagFactory(fn(array $objections) =\u003e new CustomObjectionBag($objections));\n```\n\n## Real-World Examples\n\nThe aim of this package is providing a simple \u0026 structured \"validation pipeline\". The subject doesn't necessarily need to be user input. It can be any kind of data that needs to be validated. You may find the following examples useful.\n\n### Validating Customer Deletion\n\n```php\nuse BumpCore\\Court\\Court;\n\nclass DoesCustomerHavePendingOrders {\n    public function __invoke($customer, $objection) {\n        if ($customer-\u003eorders()-\u003ewhere('status', 'pending')-\u003eexists()) {\n            $objection('Customer has pending orders.');\n        }\n    }\n}\n\nclass DoesHaveChildren {\n    public function __invoke($customer, $objection) {\n        if ($customer-\u003echildren()-\u003eexists()) {\n            $objection('Customer has children.');\n        }\n    }\n}\n\n$customer = Customer::find(1);\n\n$verdict = Court::of($customer)\n    -\u003eguards([\n        new DoesCustomerHavePendingOrders(),\n        new DoesHaveChildren(),\n    ])\n    -\u003everdict();\n\nif ($verdict-\u003eisNotEmpty()) {\n    throw new DeleteFailedException($verdict);\n} else {\n    $customer-\u003edelete();\n}\n```\n\n### A Wrapper to Unify Deletions\n\n```php\nuse BumpCore\\Court\\Court;\n\nabstract class Destroyer {\n    public function guards() {\n        return [];\n    }\n\n    public function destroyOrFail($subject) {\n        $verdict = Court::of($subject)\n            -\u003eguards($this-\u003eguards())\n            -\u003everdict();\n\n        if ($verdict-\u003eisNotEmpty()) {\n            throw new DeleteFailedException($verdict);\n        }\n\n        $this-\u003edestroy($subject);\n    }\n\n    abstract protected function destroy($subject);\n}\n\nclass CustomerDestroyer extends Destroyer {\n    public function guards() {\n        return [\n            new DoesCustomerHavePendingOrders(),\n            RelationGuard::for('children', 'Customer has children.')\n                -\u003eand('payments', 'Customer has payments.'),\n        ];\n    }\n\n    protected function destroy($customer) {\n        $customer-\u003edelete();\n    }\n}\n```\n\n### Product \u0026 Product Stock Validation Before Placing an Order\n\n```php\nuse BumpCore\\Court\\Court;\n\nclass IsProductActive {\n    public function __invoke($product, $objection) {\n        if (!$product-\u003eis_active) {\n            $objection(\"Product {$product-\u003ename} is not active.\");\n        }\n    }\n}\n\nclass HasSufficientStock {\n    public function __invoke($product, $objection) {\n        if ($product-\u003estock \u003c= 0) {\n            $objection(\"Product {$product-\u003ename} is out of stock.\");\n        }\n    }\n}\n\n$verdict = Court::of($product)\n    -\u003eguards([\n        new IsProductActive(),\n        new HasSufficientStock(),\n    ])\n    -\u003everdict();\n\nif ($verdict-\u003eisNotEmpty()) {\n    throw new OutOfStockException($verdict);\n}\n\n// Proceed with order placement...\n```\n\n## Testing\n\nRun the test suite:\n\n```bash\ncomposer test\n```\n\nRun with coverage:\n\n```bash\ncomposer test:coverage\n```\n\n# Contributing\n\nContributions are welcome! If you find a bug or have a suggestion for improvement, please open an issue or create a pull request. Below are some guidelines to follow:\n\n* Fork the repository and clone it to your local machine.\n* Create a new branch for your contribution.\n* Make your changes and test them thoroughly.\n* Ensure that your code adheres to the existing coding style and conventions.\n* Commit your changes and push them to your forked repository.\n* Submit a pull request to the main repository.\n\nPlease provide a detailed description of your changes and the problem they solve. Your contribution will be reviewed, and feedback may be provided. Thank you for your help in making this project better!\n\n\n### Pull Requests\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Write tests for your changes\n4. Ensure all tests pass (`composer test`)\n5. Check code quality (`composer phpstan`)\n6. Commit your changes (`git commit -m 'Add amazing feature'`)\n7. Push to the branch (`git push origin feature/amazing-feature`)\n8. Open a Pull Request\n\n### Development Setup\n\n```bash\ngit clone https://github.com/bumpcore/court.git\ncd court\ncomposer install\ncomposer test\n```\n\n### Code Style\n\n- Follow PSR-12 coding standards\n- Add type hints where possible\n- Write comprehensive tests\n- Update documentation for new features\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for version history.\n\n# Credits\n\n- [Abdulkadir Cemiloğlu](https://github.com/megastive19)\n- [All Contributors](../../contributors)\n\n# License\n\nThe MIT License (MIT). Please see [License File](LICENSE) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbumpcore%2Fcourt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbumpcore%2Fcourt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbumpcore%2Fcourt/lists"}