Download the PHP package polymorphine/container without Composer

On this page you can find all versions of the php package polymorphine/container. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package container

Polymorphine/Container

Latest stable release Build status Coverage status PHP version

PSR-11 Container for libraries & configuration

Concept features:

Installation with Composer

Container setup

This example will show how to set up simple container. It starts with instantiating Setup type object, and using its methods to set container's entries:

Instead configuring each entry using builder methods you can pass arrays of Record instances to one of Setup constructors:

Of course if all entries will be added with constructor Setup instantiation is not even necessary, and Instantiating Container directly might be better idea.

Setup::container() may be called again after more entries were added, but the call will return new, independent container instance. It is also recommended to encapsulate Setup within controlled scope ad described in section on read and Write separation.

Records decide how it works internally

Values returned from Container are initially wrapped into Record abstraction that allows for different strategies to produce them - it may be either returned directly or internally created by calling its (lazy) initialization procedure. Here's short explanation of package's Record implementations:

Custom Record implementations might be mutable, return different values on subsequent calls or introduce various side effects, but it is not recommended.

Composed entries

Record composition using Wrapper

Entry may be built with multiple instance descriptors (same parameters as InstanceRecord uses) created in chained Wrapper calls:

Notice that wrapper definition contains reference to wrapped entry as one of its dependencies. Without it exception will be thrown because it wouldn't constitute composition but definition of different instance that should've been defined with Entry::instance() method. This self-reference will not cause circular calls because it isn't used as standalone container entry (as identifiers for other dependencies), but a placeholder pointing wrapped instance in composition process.

Composite Container

Entry::container() method can be used to add another ContainerInterface instances and create composite container by wrapping multiple sub-containers which values (or containers themselves) may be accessed with container's id prefix (dot notation):

Passing array of ContainerInterface instances together with records Setup will also build composite container:

Secure setup & circular reference detection

Secure setup is designed as a development tool that helps with setup debugging. It is instantiated either with development static constructor:

or with ValidatedBuild instance passed to default constructor:

Naming rules and inaccessible entries

Because the way enclosed containers are accessed and because they're stored separately from Record instances some naming constraints are required:

Sub-container identifier MUST be a string, MUST NOT contain separator (. by default) and MUST NOT be used as id prefix for stored Record.

Having container stored with foo identifier would make foo.bar record inaccessible, because this value would be assumed to come from foo container. The rules might be hard to follow with multiple entries and sub-containers, so runtime checks were implemented.

Basic (production) Setup instantiated directly or with Setup::production() method won't check whether given identifiers are already defined or whether they will cause name collision that would make some entries inaccessible (sub-containers with identifier used record entry prefix).

Instantiating validated Setup::development() or directly with ValidatedBuild instance will enable runtime integrity checks for container configuration, and make sure that all defined identifiers can be accessed with ContainerInterface::get() method.

Circular references

Because Records may refer to other container entries to be built (instantiated) a hard to spot bug might be introduced where entry A in order to be resolved will need to retrieve itself during build process starting endless loop and eventually blowing up the stack. For example:

Both entries A and B refer each other, so instantiating B would need A that will attempt to instantiate B in nested context. Neither class can be instantiated, because its dependencies cannot be fully resolved (are currently being resolved on higher context level) - without detection the instantiation process would continue until call stack is overflown.

Container able to detect those circular references and append call stack information to exceptions being thrown (for both circular references and missing entries) is another feature that development setup comes with. ContainerInterface::get() would throw CircularReferenceException immediately after recursive container call on deeper context level would try to retrieve currently resolved record, which will allow to exit the endless loop.

These checks are not included in Setup::production(), because they should not be required in production environment. Although it is recommended to use them during development.

Integration tests are necessary in development, because misconfigured container will most likely crash the application, and it cannot be controlled by code in reliable way. Development setup will not prevent all the bugs that might happen, so it becomes needless performance overhead in production environment. It's worth noticing however, that visible drop in performance by using those checks in development stage will most likely mean that container is used too extensively - see recommended use section.

Direct instantiation & container composition

Setup provides helper methods to create Record instances and collect them together, optionally with sub-container entries and additional validation checks creating immutable container composition. Creating container directly is also possible - for example simple container containing only Record entries would be instantiated with as flat Record[] array (here stored in $records variable) this way:

When container needs circular reference checking and encapsulate some sub-containers stored in $containers variable as flat ContainerInterface[] array its instantiation would change into this composition:

Configuration Container

ConfigContainer that comes with this package is a convenient way to store and retrieve values from multidimensional associative arrays using path notation. This container is instantiated directly with array passed to constructor, which values can be accessed by dot-separated keys on consecutive nesting levels. Example:

As it was described in composite container section you can use both record-based and config container as a single Container using Entry::container() method. Having above $container defined, you can recreate main example which uses its value and domain entries:

Note additional path prefixes for value and domain within deferred.object and factory.product definitions compared to records used in original example. These values are still fetched from ConfigContainer, but accessed through composite container using env prefix. This way values from both config and record containers encapsulated inside composite container can be retrieved:

Object created with $container->get('factory.product') will be the same as instantiated objects directly using new operator shown in Containers vs direct instantiation section with extended take on the subject.

Recommended use

Read and Write separation

Setup builder-like API allows for setting up container and creating its instance, but it would result in cleaner design to have container encapsulated and still be able to configure it from outside scope. This could be achieved by proxy object exposing only setup methods.

Calling Setup::set() returns write-only Entry helper object. Beside providing methods to define various implementations of Record or sub-containers for configured container it allows to implement proxy with single method instead polluting its interface with multiple setup methods. For example, if you have front controller bootstrap class similar to...

...you can still use all helper methods provided by Entry object. Now You can push values into container from the scope of App class object, but cannot access container afterwards. App controls the Setup and will call Setup::container() to use on its own terms.

Nothing in outer scope will be able to use instance of container created within App. It is possible to achieve with some configuration efforts, but this is not recommended, so details won't be explained here.

Real advantage of container

Containers vs direct instantiation

Instantiating container with setup commands used in main example and getting factory.product object will be equivalent to factory instantiated directly with new operator and calling create() method on it with http://api.example.com parameter:

As you can see the container does not give any visible advantage here over creating object directly, and assuming this object is used only in single use-case scenario there won't be any.

For libraries used in various request contexts or reused in the structures where the same instance should be passed (like database connection) having it configured in one place saves lots of trouble and repetition. Suppose that class is some api library that requires configuration and composition used by a few endpoints of your application - you would have to repeat this instantiation for each endpoint. You can still solve this problem encapsulating instantiation within hardcoded factory class and replace $container->get() with single (static) call (and type-hinted result!)

Containers vs decomposed factories and Singleton pattern

Mentioned factories introduce another problem though. You might not be able to tell up front which individual component of created object might be needed elsewhere and it would be necessary to extract it from existing factory into another factory and call it in both places - the factory it was extracted from and the other part that needs this component. When the same instance is needed such factory in some cases would need to cache created object - most probably using Singleton pattern.

Singleton pattern is needed when same object needs to be provided in different scopes of the code. When only single factory uses it there's no need for singleton. The number of injection points doesn't matter since it can be passed in all of them as previously created local variable. Singleton pattern objects are available in global scope for any part of the code and this makes them hard to maintain since you don't really know where it is or will be used.

For example authentication service might use session, so it's not enough to write factory for auth service, but one for session is also needed, because session might be used not only in many different contexts, but also in different application scopes (building both middleware and use case compositions). Having a number of singleton factories called in multiple places results in hard to comprehend code even when they're used in disciplined manner - that is only in composition layer ("main" partition).

Container solves both decomposition and scope control problem, because all components can also be container entries and it's usage scope is strictly limited to the places it was injected. This flexibility is the only advantage of (standard) containers that cannot be easily replaced in other way. However some discipline regarding containers is also required.

Containers and Service locator anti-pattern

Containers shouldn't be injected as a wrapper providing direct (objects that will be called in the same scope) dependencies of the object, because that will expose dependency on container while hiding types of objects we really depend on. It may seem appealing that we can freely inject lazily invoked objects with possibility of not using them, but these unused objects, in vast majority of cases, should denote that our object's scope is too broad. Branching that leads to skipping method call (OOP message sending) on one of dependencies should be handled up front, which would make our class easy to test and read. Making exceptions for sake of easier implementation will quickly turn into standard practice (especially within larger or remote working teams), because consistency seems plausible even when it concerns bad practices. Healthy constraints are more reliable than expected reasoning.

Container in factory is harmless

Dependency injection container should help with dependency injection, but not replace it. It's fine to inject container into main factory objects in framework controlled scope, because factory itself does not make calls on objects container provides and it doesn't matter what objects factory is coupled to. Treat application objects composition as a form of configuration.

Why no auto-wiring (yet)?

Explicitly hardcoded class compositions whether instantiated directly or indirectly through container might be traded for convenient auto-wiring, but in my opinion its cost includes important part of polymorphism, which is resolving preconditions. This is not the price you pay up front, and while debt itself is not inherently bad, forgetting you have one until you can't pay it back definitely is.


All versions of container with dependencies

PHP Build Version
Package Version
Requires php Version ^7.4 || ^8.0
psr/container Version ^1.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package polymorphine/container contains the following files

Loading the files please wait ....