{"id":30738251,"url":"https://github.com/teofanis/hook-press","last_synced_at":"2026-05-09T05:34:40.768Z","repository":{"id":312488953,"uuid":"1047371413","full_name":"teofanis/hook-press","owner":"teofanis","description":"HookPress is a Laravel package that uses Composer hooks to automatically discover, filter, and cache classes, traits, and interfaces in your application","archived":false,"fork":false,"pushed_at":"2026-04-25T23:58:00.000Z","size":1472,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-26T01:23:09.220Z","etag":null,"topics":["auto-discovery","composer","laravel","laravel-package","php"],"latest_commit_sha":null,"homepage":"","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/teofanis.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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},"funding":{"github":"Teofanis Papadopulos","custom":"https://www.paypal.com/paypalme/teofanis","buy_me_a_coffee":"teofanis","thanks_dev":"u/gh/teofanis"}},"created_at":"2025-08-30T09:12:46.000Z","updated_at":"2026-04-25T23:58:02.000Z","dependencies_parsed_at":null,"dependency_job_id":"8c0a18dc-6aa9-4cc5-8122-be07253d23c4","html_url":"https://github.com/teofanis/hook-press","commit_stats":null,"previous_names":["teofanis/hook-press"],"tags_count":8,"template":false,"template_full_name":"spatie/package-skeleton-laravel","purl":"pkg:github/teofanis/hook-press","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teofanis%2Fhook-press","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teofanis%2Fhook-press/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teofanis%2Fhook-press/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teofanis%2Fhook-press/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teofanis","download_url":"https://codeload.github.com/teofanis/hook-press/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teofanis%2Fhook-press/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32808540,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"online","status_checked_at":"2026-05-09T02:00:06.633Z","response_time":123,"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":["auto-discovery","composer","laravel","laravel-package","php"],"created_at":"2025-09-03T22:01:25.280Z","updated_at":"2026-05-09T05:34:40.753Z","avatar_url":"https://github.com/teofanis.png","language":"PHP","funding_links":["https://github.com/sponsors/Teofanis Papadopulos","https://www.paypal.com/paypalme/teofanis","https://buymeacoffee.com/teofanis","https://thanks.dev/u/gh/teofanis"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/logo.png\" alt=\"HookPress logo\" width=\"500\" /\u003e\n\u003c/p\u003e\n\n# HookPress is a Laravel package that uses Composer hooks to automatically discover, filter, and cache classes, traits, and interfaces in your application\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://packagist.org/packages/teofanis/hook-press\"\u003e\n    \u003cimg alt=\"Latest Version on Packagist\" src=\"https://img.shields.io/packagist/v/teofanis/hook-press.svg\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/teofanis/hook-press/actions/workflows/run-tests.yml\"\u003e\n    \u003cimg alt=\"GitHub Tests Action Status\" src=\"https://img.shields.io/github/actions/workflow/status/teofanis/hook-press/run-tests.yml?branch=main\u0026amp;label=tests\u0026amp\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/teofanis/hook-press/actions/workflows/fix-php-code-style-issues.yml\"\u003e\n    \u003cimg alt=\"GitHub Code Style Action Status\" src=\"https://img.shields.io/github/actions/workflow/status/teofanis/hook-press/fix-php-code-style-issues.yml?branch=main\u0026amp;label=code%20style\u0026amp\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://packagist.org/packages/teofanis/hook-press\"\u003e\n    \u003cimg alt=\"Total Downloads\" src=\"https://img.shields.io/packagist/dt/teofanis/hook-press.svg\"\u003e\n  \u003c/a\u003e\n\u003ca href=\"https://github.com/teofanis/hook-press/blob/main/LICENSE.md\"\u003e\n    \u003cimg alt=\"Licence\" src=\"https://img.shields.io/packagist/l/teofanis/hook-press.svg\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nHookPress builds a static class map during Composer install/update so your app can look up “discoverable” classes in O(1) time at runtime. No boot-time reflection, no recursive directory scans, no config arrays to maintain.\n\n## Why?\n\nI’ve used this in production for a while to auto-discover things like payment methods and populate lists/registries without hand-wiring every class. It’s been handy for pluggable components (drivers, actions, jobs) where “put the class in the right namespace” should be enough.\n\n## Typical uses\n\n- Collect all implementations of an interface (e.g. PayoutMethod) to build a registry or a UI dropdown.\n\n- Group classes by trait (e.g. everything using Searchable) for indexing or batch operations.\n\n- Discover classes marked by attributes (e.g. #[Discoverable]).\n\n- Find invokables or classes with particular methods/properties for handler pipelines.\n\n## Requirements\n\n- PHP 8.0+\n\n- Laravel 9+\n\n## Installation\n\n```bash\ncomposer require teofanis/hook-press\n```\n\nPublish the config file with:\n\n```bash\nphp artisan vendor:publish --tag=\"hook-press-config\"\n```\n\nWire HookPress to run when composer installs/updates\n```json\n{\n  \"scripts\": {\n    \"post-install-cmd\": [\n      \"HookPress\\\\Hooks\\\\HookRunner::postInstall\"\n    ],\n    \"post-update-cmd\": [\n      \"HookPress\\\\Hooks\\\\HookRunner::postUpdate\"\n    ]\n  }\n}\n```\n\u003e **_NOTE:_** You can use artisan directly if you prefer\n```json\n\"post-install-cmd\": [\"@php artisan hook-press:build --no-ansi --no-interaction\"],\n\"post-update-cmd\":  [\"@php artisan hook-press:build --no-ansi --no-interaction\"]\n\n```\n \nThis is the contents of the published config file:\n\n```php\nreturn [\n     // Namespaces to consider as your application roots\n    'roots' =\u003e [\n        'App\\\\',\n    ],\n\n    // Optional: trait grouping\n    'traits' =\u003e [\n        'enabled'    =\u003e true,\n        'namespaces' =\u003e ['App\\\\Traits\\\\'],\n        'group_key'  =\u003e 'traits', // where the trait map will be stored\n    ],\n\n    // Define your discovery “maps”\n    'maps' =\u003e [\n        'payout_methods' =\u003e [\n            'namespaces' =\u003e ['App\\\\Classes\\\\PayoutMethods\\\\'],\n            'conditions' =\u003e [\n                'isInstantiable',\n                'implementsInterface' =\u003e 'App\\\\Interfaces\\\\PayoutMethod',\n            ],\n        ],\n    ],\n\n    // Exclusions (exact class, namespace prefix, or regex)\n    'exclusions' =\u003e [\n        'classes'    =\u003e [],\n        'namespaces' =\u003e [],\n        'regex'      =\u003e [],\n    ],\n\n    // Where the computed map is stored\n    'store' =\u003e [\n        'driver' =\u003e 'file', // 'file' or 'cache'\n        'file'   =\u003e ['path'  =\u003e 'bootstrap/cache/hook-press.php'],\n        'cache'  =\u003e ['store' =\u003e null, 'key' =\u003e 'hookpress:map', 'ttl' =\u003e null],\n    ],\n\n    // Where HookPress reads the Composer classmap from (leave as default in apps)\n    'composer' =\u003e [\n        'classmap_path'   =\u003e 'vendor/composer/autoload_classmap.php',\n        'artisan_command' =\u003e 'hook-press:build',\n    ],\n];\n```\n### How it works\n\nOn composer install/update (or php artisan hook-press:build), HookPress scans your Composer classmap, applies your conditions, and writes a single PHP file (or cache entry) with the results.\n\nAt runtime you read from that file/cache—no reflection or directory walking.\n\n## Usage\n\n```php\nuse HookPress\\Facades\\HookPress;\n// Entire map (all keys)\n$all = HookPress::map();\n// Specific map\n$methods = HookPress::map('payout_methods');\n\n// Classes that use a trait\n$searchables = HookPress::classesUsing(\\App\\Traits\\Searchable::class);\n\n// Rebuild on demand (usually done via Composer hook)\nHookPress::refresh();\n\n// Clear the stored map\nHookPress::clear();\n```\n\n## Artisan Commands \n```bash\nphp artisan hook-press:build        # compute and store the map\nphp artisan hook-press:show         # print the map\nphp artisan hook-press:show payout_methods\nphp artisan hook-press:clear\nphp artisan hook-press:build --no-traits  # skip trait grouping\n\n```\n\n## Conditions \u0026 Examples\n\n### Available built-in conditions\n\n| Condition            | Purpose                                                                 | Arg Type                                      | Example Arg(s)                                                                                   | Notes / Behavior                                                                                                                                                 |\n|----------------------|-------------------------------------------------------------------------|-----------------------------------------------|---------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `isInstantiable`     | Include only classes that can be instantiated (not abstract/interfaces). | _none_                                         | _n/a_                                                                                            | Default when `conditions` is omitted. Effectively filters out abstract classes and interfaces.                                                                   |\n| `implementsInterface`| Require the class to implement a specific interface.                    | `string` (FQCN)                                | `App\\\\Interfaces\\\\PayoutMethod::class`                                                           | Uses `ReflectionClass::implementsInterface()`. Pass a fully-qualified interface name.                                                                            |\n| `usesTrait`          | Require the class to use a given trait.                                 | `string` (FQCN)                                | `App\\\\Traits\\\\Searchable::class`                                                                  | Checks `ReflectionClass::getTraitNames()`. Matches exact trait FQCN.                                                                                             |\n| `hasAttribute`       | Require the class to be annotated with a PHP 8 attribute.               | `string` (FQCN)                                | `App\\\\Attributes\\\\Discoverable::class`                                                            | Uses `ReflectionClass::getAttributes()`. Attribute FQCN must match.                                                                                              |\n| `extends`            | Require the class to extend a given parent/base class.                   | `string` (FQCN)                                | `Illuminate\\\\Database\\\\Eloquent\\\\Model::class`                                                    | Uses `ReflectionClass::isSubclassOf()`.                                                                                                                           |\n| `isAbstract`         | Include only abstract classes.                                          | _none_                                         | _n/a_                                                                                            | Opposite of `isInstantiable` for abstracts. Useful if you deliberately want base classes.                                                                         |\n| `isFinal`            | Include only classes declared `final`.                                   | _none_                                         | _n/a_                                                                                            | Uses `ReflectionClass::isFinal()`.                                                                                                                                |\n| `hasMethod`          | Require a method to exist (optionally with constraints).                 | `string` **or** `array`                        | `'process'` **or** `['name' =\u003e 'process', 'public' =\u003e true, 'static' =\u003e false, 'returns' =\u003e 'bool']` | Array keys supported: `name` (required), `public`/`protected`/`private` (bool), `static` (bool), `returns` (`'void'` or FQCN/string of return type).             |\n| `hasProperty`        | Require a property to exist (optionally with constraints).               | `string` **or** `array`                        | `'driver'` **or** `['name' =\u003e 'driver', 'public' =\u003e true, 'static' =\u003e false, 'type' =\u003e 'string']`   | Array keys supported: `name` (required), `public`/`protected`/`private` (bool), `static` (bool), `type` (`'string'`, `'int'`, FQCN, etc.).                        |\n| `nameMatches`        | Match FQCN or short class name using a regex.                           | `string` **or** `array`                        | `'/Controller$/'` **or** `['pattern' =\u003e '/^Bank/', 'short' =\u003e true]`                               | If you pass an array: `pattern` is the regex; `short: true` applies it to the short class name; otherwise it applies to the full-qualified class name (FQCN).    |\n\n**Notes**\n- If `conditions` is omitted for a map, HookPress defaults to `['isInstantiable']`.\n- Combine multiple conditions in the array to apply **AND** logic.\n- All FQCNs must be fully-qualified (escape backslashes in JSON/Markdown as needed).\n\n\n### Examples\n\n#### Models (non-abstract Eloquent):\n```php\n\n'models' =\u003e [\n    'namespaces' =\u003e ['App\\\\Models\\\\'],\n    'conditions' =\u003e [\n        'extends' =\u003e \\Illuminate\\Database\\Eloquent\\Model::class,\n        'isInstantiable'// removes abstracts if-any\n    ],\n],\n```\n#### Invokables (public `__invoke`):\n```php\n'invokables' =\u003e [\n    'namespaces' =\u003e ['App\\\\Actions\\\\', 'App\\\\Jobs\\\\'],\n    'conditions' =\u003e [\n        'isInstantiable',\n        'hasMethod' =\u003e ['name' =\u003e '__invoke', 'public' =\u003e true],\n    ],\n],\n\n```\n#### Controllers by name:\n```php\n'controllers' =\u003e [\n    'namespaces' =\u003e ['App\\\\Http\\\\Controllers\\\\'],\n    'conditions' =\u003e [\n        'isInstantiable',\n        'nameMatches' =\u003e '/Controller$/',\n    ],\n],\n```\n\n### Extending with your own custom checks\n\n1. Implement the contract \n\n```php\nnamespace App\\HookPress;\n\nuse HookPress\\Contracts\\Condition;\nuse ReflectionClass;\n\nclass ConstructorTakesLogger implements Condition\n{\n    public function passes(ReflectionClass $ref, mixed $arg = null): bool\n    {\n        $ctor = $ref-\u003egetConstructor();\n        if (! $ctor) return false;\n\n        foreach ($ctor-\u003egetParameters() as $p) {\n            $t = $p-\u003egetType();\n            if ($t \u0026\u0026 ltrim((string) $t, '\\\\') === 'Psr\\\\Log\\\\LoggerInterface') {\n                return true;\n            }\n        }\n        return false;\n    }\n}\n\n```\n\n2. Reference it by the FQCN (Fully Qualified Class Name) in your config\n\n```php\n'services_requiring_logger' =\u003e [\n    'namespaces' =\u003e ['App\\\\Services\\\\'],\n    'conditions' =\u003e [\n        \\App\\HookPress\\ConstructorTakesLogger::class =\u003e null,\n    ],\n],\n```\nHookPress resolves unknown condition keys as classes via the container, so no extra registration is needed.\n\n\n### Notes\nBuild time scales with your classmap size, but it’s a one-off step during Composer or CI.\n\nRuntime lookups are reading from a single array in a PHP file or a cache item.\n\nYou can switch to the cache driver if you prefer not to ship a file under bootstrap/cache.\n\n## Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n## Security Vulnerabilities\n\nPlease review [our security policy](../../security/policy) on how to report security vulnerabilities.\n\n## Credits\n\n- [Teofanis Papadopulos](https://github.com/teofanis)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteofanis%2Fhook-press","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteofanis%2Fhook-press","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteofanis%2Fhook-press/lists"}