{"id":23359790,"url":"https://github.com/firehed/container","last_synced_at":"2026-03-07T02:13:20.814Z","repository":{"id":46069967,"uuid":"172765616","full_name":"Firehed/container","owner":"Firehed","description":null,"archived":false,"fork":false,"pushed_at":"2024-08-22T20:43:56.000Z","size":138,"stargazers_count":2,"open_issues_count":12,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-24T04:17:53.809Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/Firehed.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,"publiccode":null,"codemeta":null}},"created_at":"2019-02-26T18:21:27.000Z","updated_at":"2024-08-22T20:43:40.000Z","dependencies_parsed_at":"2024-03-16T19:22:10.269Z","dependency_job_id":"99a2c65b-43c5-4966-9d17-8b70510b8422","html_url":"https://github.com/Firehed/container","commit_stats":{"total_commits":88,"total_committers":2,"mean_commits":44.0,"dds":"0.23863636363636365","last_synced_commit":"3ba0ec1a23fd74f4417a4d7db396a92dbed939a6"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Firehed%2Fcontainer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Firehed%2Fcontainer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Firehed%2Fcontainer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Firehed%2Fcontainer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Firehed","download_url":"https://codeload.github.com/Firehed/container/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248199136,"owners_count":21063641,"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","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":[],"created_at":"2024-12-21T11:11:58.307Z","updated_at":"2026-03-07T02:13:20.753Z","avatar_url":"https://github.com/Firehed.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Container\nA PSR-11 compliant Dependency Inversion Container\n\n[![Test](https://github.com/Firehed/container/actions/workflows/test.yml/badge.svg)](https://github.com/Firehed/container/actions/workflows/test.yml)\n[![Static analysis](https://github.com/Firehed/container/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/Firehed/container/actions/workflows/static-analysis.yml)\n[![Lint](https://github.com/Firehed/container/actions/workflows/lint.yml/badge.svg)](https://github.com/Firehed/container/actions/workflows/lint.yml)\n[![Packagist](https://img.shields.io/packagist/v/firehed/container.svg)](https://packagist.org/packages/firehed/container)\n\n## Why another container implementation?\n\nThe primary motivation for creating this was to have a container implementation that's optimized for containerized deployment in a long-running process (like ReactPHP and PHP-PM).\n\nThe usage and API is is highly inspired by PHP-DI, but adds functionality to support factories at definition-time (rather than exclusively at access-time with `make`).\nThis is intended to reduce unpredictable behavior of services in concurrent environments while strictly adhering to the PSR container specification.\n\n### Differences from PHP-DI\n\n- Only `get()` and `has()` methods exist on the container\n\n- The `factory` function has _completely_ different behavior:\n  the closure it wraps will be called every time it is requested through `get` (PHP-DI exposes this as `$container-\u003emake()`).\n  In PHP-DI, `factory` is just alternate syntax for defining a service through a closure.\n\n- When an interface is mapped to an implementation, the default behavior is to return the configured implementation.\n  In PHP-DI, `SomeInterface::class =\u003e autowire(SomeImplementation::class)` does NOT point to an explcitly-configured `SomeImplementation`\n\n- A shorthand syntax for interface-to-implementation has been added\n\n- When recursively resolving dependencies, all required parameters must also be explicitly configured (see above)\n\n- Annotations are not supported.\n  In future versions, `@param` annotations _may_ be supported; `@Inject` will never be.\n\n### Design Opinions\n\nLike many autowiring DI containers, this has some opinionated design decisions.\nYou may or may not agree, but it's important to document them to help you make an informed choice about whether this library is right for you.\n\n- This is based around having a distinct build/compile stage for your application's deployment process.\n  Implicit autowiring will NOT occur in the production-ready compiled container, which yields performance improvements.\n  Add the autowired class name to any definition file to explicitly wire it (`Foo::class,` is sufficient, see \"Automatic autowiring\" below).\n\n- Any `$id` that's a valid class string should return an instance of that class (or interface, enum).\n  As of 0.6, this is reflected in the provided type information: `-\u003eget($id)` has Generic information for PHPStan where if a `class-string` is detected, get returns that class.\n  This is not (currently) enforced at runtime, but be warned that e.g. `$container-\u003eget(LoggerInterface::class);` will be indicated as being a `LoggerInteface` to static analysis tools, and if the definition doesn't do that, you may get conflicts.\n\n- All files should always be included.\n  Do NOT skip files based on the environment.\n  Instead, definitions should be conditional.\n  Example:\n  ```php\n  \u003c?php\n\n  return [\n      MyInterface::class =\u003e function ($c) {\n          return $c-\u003eget('use_mocks')\n              ? $c-\u003eget(MyImplementationMock::class)\n              : $c-\u003eget(MyImplementationReal::class);\n      },\n  ];\n  ```\n\n## Installation\n\n```\ncomposer require firehed/container\n```\n\n## Usage\n\nThe primary interface to the container is through the `BuilderInterface`.\n\nThere are two implementations:\n\n### `Builder`\nThe `Builder` class will create a dev container, which will determine dependencies on the fly.\nThis is intended for use during development - reflection for autowired classes is performed on every request, which is convenient while changes are being made but adds overhead.\n\n### `Compiler`\nThe `Compiler` class will generate optimized code and write it to a file once, load that file, and return a container that uses the optimized code.\nReflection for autowiring is only performed at compile-time, so this will run significantly faster than the dev container.\nHowever, whenever any definition changes (including constructor signatures of autowired classes), the file must be recompiled.\n\n\u003e [!TIP]\n\u003e It is **highly recommended** to a) use the `Compiler` implementation in non-dev environments, and b) compile the container during your build process.\n\n#### Running the compiler\n\nThe compilation process runs automatically the first time `build()` is called.\n\n### Example\n```php\n\u003c?php\ndeclare(strict_types=1);\n\n// Include Composer's autoloader if not already done\nrequire 'vendor/autoload.php';\n\n// If using a tool like dotenv, apply it here\n/*\nif (file_exists(__DIR__.'/.env')) {\n    Dotenv\\Dotenv::create(__DIR__)-\u003eload();\n}\n */\n\n$isDevMode = getenv('ENVIRONMENT') === 'development';\n\nif ($isDevMode) {\n    $builder = new Firehed\\Container\\Builder();\n} else {\n    $builder = new Firehed\\Container\\Compiler();\n}\n\n// Each definition file must return a definition array (see below)\nforeach (glob('config/*.php') as $definitionFile) {\n    $builder-\u003eaddFile($definitionFile);\n}\n\nreturn $builder-\u003ebuild();\n```\n\n\u003e [!TIP]\n\u003e If you're following the pattern above (config files in one directory), the `AutoDetect` class can do this for you.\n\u003e See [Auto-detection](#auto-detection), below.\n\n## Definition API\n\nAll files added to the `BuilderInterface` must `return` an `array`.\nThe keys of the array will map to `$id`s that can be checked for existence with `has($id)`, and the values of the array will be returned when those keys are provided to `get($id)`.\n\nIt is **highly recommended** that class instances use their fully-qualified class name as an array key, and to additionally create a separate interface-to-implementation mapping.\nThe latter will happen automatically when a key is the fully-qualified name of an `interface` and the value is a string that maps to a class name.\n\n\u003e [!NOTE]\n\u003e The library output implements a `TypedContainerInterface`, which adds docblock generics readable by tools like PHPStan and Psalm to PSR-11.\n\u003e It assumes you are following the above convention; not doing so could result in misleading output.\n\u003e This has no effect at runtime, and only helps during the development and CI.\n\n### Examples\n\nThe most concise examples are all part of the unit tests: [`tests/ValidDefinitions`](tests/ValidDefinitions).\n\n### Simple values\n\nIf a scalar, array, or Enum is provided as a value, that value will be returned unmodified.\n\n**Exception**: if the value is a `string` AND the key is the name of a declared `interface`, it will automatically be treated as an interface-to-implementation mapping and processed as `InterfaceName::class =\u003e autowire(ImplementationName::class)`\nWhen doing so, you **SHOULD** write the mapping with a `::class` literal; e.g. `\\Psr\\Log\\LoggerInterface::class =\u003e SomeLoggerImplementation::class`.\nThis approach (as compared to strings) not only provides additional clarity when reading the file, but allows static analysis tools to detect some errors.\n\nObjects **may not** be directly provided as a value and **must** be provided as a closure; see below.\nThis is because the compiler cannot create an actual object instance, and thus would only work in development mode.\n\n### Closures\n\nIf a closure is provided as a value, that closure will be executed when `get()` is called and the value it returns will be returned.\nThe container will be provided as the first and only parameter to the closure, so definitions may depend on other services.\nFor services that do not have dependencies, the closure may be defined as a function that takes no parameters (a \"thunk\"), though at execution time the container will still be passed in (and subsequently ignored).\nDo not use the `use()` syntax to access other container definitions.\n\nSince computed values are cached (except when wrapped with `factory`, see below), the closure will only be executed once regardless of how many times `get()` is called.\n\nAny definition that should return an instance of an object **must** be defined by a closure, or using one of the helpers described below.\nDirectly instantiating the class in the definition file is invalid.\n\n```php\n\u003c?php\nuse Psr\\Container\\ContainerInterface;\nreturn [\n    // This will provide a single connection to your database, deferring the\n    // connection until either directly accessed or a service with PDO as a\n    // dependency is accessed.\n    // Note: you may opt to elide the `ContainerInterface` typehint for brevity\n    PDO::class =\u003e function (ContainerInterface $c) {\n        // This example assumes pdo_dsn, database_user, and database_pass are\n        // defined elsewhere (probably using the `env` helper)\n        return new PDO(\n            $c-\u003eget('pdo_dsn'),\n            $c-\u003eget('database_user'),\n            $c-\u003eget('database_pass')\n        );\n    },\n];\n```\n\n### `autowire(?string $classToAutowire = null)`\nUsing `autowire` will use reflection to attempt to determine the specified class's dependencies, recursively resolve them, and return a shared instance of that object.\n\nRequired parameters **must** have a typehint in order to be resolved.\nThat typehint may be to either a class or an interface; in both cases, that dependency must also be defined (but can also be autowired).\nRequired parameters with value types (scalars, arrays, etc) are not supported and must be manually wired.\n\nOptional parameters will always have their default value provided.\n\n\u003e [!IMPORTANT]\n\u003e Classes with any untyped constructor parameters, or those typed with `int/float/bool/array`, **cannot** be autowired.\n\n#### Automatic autowiring\nIn the returned definition array, having a bare string value with no key will treat the value as a key to be autowired.\n\nThe following are all equivalent definitions:\n\n```php\n\u003c?php\n/**\nclass MySpecialClass\n{\n}\nclass MyOtherClass\n{\n    public function __construct(MySpecialClass $required)\n    {\n        // ...\n    }\n}\n*/\nreturn [\n    MySpecialClass::class,\n    MyOtherClass::class,\n];\n```\n```php\n\u003c?php\nuse function Firehed\\Container\\autowire;\nreturn [\n    MySpecialClass::class =\u003e autowire(),\n    MyOtherClass::class =\u003e autowire(),\n];\n```\n```php\n\u003c?php\nuse function Firehed\\Container\\autowire;\nreturn [\n    MySpecialClass::class =\u003e autowire(MySpecialClass::class),\n    MyOtherClass::class =\u003e autowire(MyOtherClass::class),\n];\n```\n```php\n\u003c?php\nreturn [\n    MySpecialClass::class =\u003e function () {\n        return new MySpecialClass();\n    },\n    MyOtherClass::class =\u003e function (ContainerInterface $c) {\n        return new MyOtherClass($c-\u003eget(MySpecialClass::class));\n    },\n];\n```\n\nThe topmost example is recommended for configuring any class that can be autowired.\n\n### `factory(?closure $body = null)`\nUse `factory` to return a new copy of the class or value every time it is accessed through `get()`\n\nIf a paramater is not provided to the definition, the key will be used to autowire a definition.\nIf a closure is provided, that closure will be executed instead.\n\n### `env(string $variableName, ?string $default = null)`\nUse `env` to embed environment variables in your container.\nLike other non-factory values, these will be cached for the lifetime of the script.\n\n`env` embeds a tiny DSL, allowing you to get the values set in the environment as an int, float, or bool rather than the native string read from the environment.\nTo use this, the following methods exist:\n\n- `asBool`\n- `asInt`\n- `asFloat`\n- `asEnum`\n\nThese are roughly equivalent to e.g. `(int) getenv('SOME_ENV_VAR')`, with the exception that `asBool` will only allow values `0`, `1`, `\"true\"`, and `\"false\"` (case-insensitively).\n\n`asEnum` takes a class-string to a **string-backed** enum that you have defined, and will use `::from($envValue)` to hydrate from the environment value.\nThis does not attempt to locally normalize values, so the envvar value MUST match the backing value exactly.\n\n\u003e [!WARNING]\n\u003e Do not use `getenv` or `$_ENV` to access environment variables!\n\u003e If you do so, compiled containers will get the *compile-time* value set, which is almost certainly not the behavior you want.\n\u003e Instead, use the `env` wrapper, which will defer the access of the environment variable until the first time it is used.\n\u003e\n\u003e If *and only if* you want a value compiled in, you must use `getenv` directly.\n\nSource definitions like this:\n```php\n\u003c?php\nuse function Firehed\\Container\\env;\nreturn [\n    'some_key' =\u003e env('SOME_ENV_VAR'),\n    'some_key_with_default' =\u003e env('SOME_ENV_VAR', 'default_value'),\n    'some_key_with_null_default' =\u003e env('SOME_ENV_VAR', null),\n\n    'some_bool' =\u003e env('SOME_BOOL')-\u003easBool(),\n    'some_int' =\u003e env('SOME_INT')-\u003easInt(),\n    'some_float' =\u003e env('SOME_FLOAT')-\u003easFloat(),\n    'some_enum' =\u003e env('SOME_ENUM')-\u003easEnum(MyEnum::class),\n\n    // Counterexample!\n    'getenv' =\u003e getenv('VALUE_AT_COMPILE_TIME'),\n];\n```\n\nwill compile to code similar to this:\n```php\n\u003c?php\nreturn [\n    'some_key' =\u003e function () {\n        $value = getenv('SOME_ENV_VAR');\n        if ($value === false) {\n            throw new Firehed\\Container\\Exceptions\\EnvironmentVariableNotSet('SOME_ENV_VAR');\n        }\n        return $value;\n    },\n    'some_key_with_default' =\u003e function () {\n        $value = getenv('SOME_ENV_VAR');\n        if ($value === false) {\n            return 'default_value';\n        }\n        return $value;\n    },\n    'some_key_with_null_default' =\u003e function () {\n        $value = getenv('SOME_ENV_VAR');\n        if ($value === false) {\n            return null;\n        }\n        return $value;\n    },\n\n    'some_bool' =\u003e function () {\n        $value = getenv('SOME_ENV_VAR');\n        if ($value === false) {\n            throw new Firehed\\Container\\Exceptions\\EnvironmentVariableNotSet('SOME_ENV_VAR');\n        }\n        $value = strtolower($value);\n        if ($value === '1' || $value === 'true') {\n            return true;\n        } elseif ($value === '0' || $value === 'false') {\n            return false;\n        } else {\n            throw new OutOfBoundsException('Invalid boolean value');\n        }\n    },\n    'some_int' =\u003e function () {\n        $value = getenv('SOME_INT');\n        if ($value === false) {\n            throw new Firehed\\Container\\Exceptions\\EnvironmentVariableNotSet('SOME_INT');\n        }\n        return (int)$value;\n    },\n    'some_float' =\u003e function () {\n        $value = getenv('SOME_FLOAT');\n        if ($value === false) {\n            throw new Firehed\\Container\\Exceptions\\EnvironmentVariableNotSet('SOME_FLOAT');\n        }\n        return (float)$value;\n    },\n    'some_enum' =\u003e function () {\n        $value = getenv('SOME_ENUM');\n        if ($value === false) {\n            throw new Firehed\\Container\\Exceptions\\EnvironmentVariableNotSet('SOME_ENUM');\n        }\n        return MyEnum::from($value);\n    },\n\n    // Counterexample!\n    'getenv' =\u003e 'whatever_value_is_in_your_current_environment',\n];\n```\n\n## Auto-detection\n\nIf your software is following common conventions, the container bootstrapping can be greatly simplified:\n\n```php\n$container = \\Firehed\\Container\\AutoDetect::from('config');\n```\n\nIt will auto-detect your environment, looking at `ENVIRONMENT` or `ENV` environment variables, in that order.\nIf it's any of `local`, `dev`, or `development` (case-insensitive), then it will use the dev container, which is not cached or compiled.\nAny other value will run the compilation process, writing the output to `AutoDetect::$compiledOutputPath = 'vendor/compiledConfig.php'`.\nYou may change the output directory by changing that variable (be mindful of `getcwd()`!); the default writes into Composer's `vendor` directory since it's commonly `gitignore`d.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffirehed%2Fcontainer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffirehed%2Fcontainer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffirehed%2Fcontainer/lists"}