Download the PHP package miniature/component without Composer
On this page you can find all versions of the php package miniature/component. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download miniature/component
More information about miniature/component
Files in miniature/component
Package component
Short Description A template for a component of PHP-classeswith the purpose of a component architecture. A black-box approach on dependency injection.
License MIT
Informations about the package component
Component
Warning!
This is still on an experimental level. We don't know if we've got everything together currently. Extensive testing needs to be done as well.
Purpose
This is a template for a component of PHP-classes provided by a dependency injection container (DI-Container). The container itself is hidden by the component instance (black box approach) which from the outside serves as a facade.
As a facade, the component is provided with a mechanism of access restriction. This access restriction in return ist the wiring of the component coupling and the much-needed bottleneck regarding the communication of the classes residing in the container to the outer world. There is also a violation detection routine ensuring the black box is kept closed.
The behaviour can be changed easily by the help of certain injections and overrides. The DI-Container is an independent package of its own.
What does "Component" mean in this context?
We are seeking for a toolset to build a composite component architecture. We are trying to build a tiny framework, that enables us to set up something like this relatively easily.
If this does not look familiar to you and if the Cohesion & Coupling Problem is not on your agenda it is likely that you are using a different terminology and that this package is absolutely not for you.
You might want to visit an extended diagram first in order to get a better idea.
The basic steps of setting up a component:
- Inheritance from the
Miniature\Component\Component
Singleton and giving that a distinctive self speaking name - Setting up a configuration folder and injecting the directory path information to the Component
- Providing configuration-files (PHP-array or YAML) ...
- that contain the dependency injection wiring and
- the component coupling wiring
- And you are ready to go
One note before you continue:
This package provides certain dependency wiring and black-/grey-/white-boxing mechanisms that might be useful for releasing component architectures. This is mainly about structure, access controll and dependency management. This doesn't mean that these are most sufficient for the task of component architecture as such. Other means might be way more sustainable. Consider bundling, separate deployment and versioning first.
Installation
Using Composer
Downloading Package
You'll also need the DI-Container package.
Unzip both to a directory named Miniature
.
Add to your autoload something like the following:
Can be that you must adjust the file path concatenation for filePath
by setting the relative path in the filepath()
statement.
The instance of the Component
The basic instantiating
The basic class is an abstract Singleton. The purpose of this is:
- that your component is considered something unique in your PHP environment
- there will be multiple components
- you components shall be globally addressable at any time
Having said this and having made up your own name, you might almost be done with a few lines like this:
Most important here is the protected static $instance
property.
It ensures that always the same instance of the inheritor you create is used.
Otherwise, you will be confronted with unexpected behaviour as soon as you work with multiple component instances
while everything seemed fine with a unique instance.
This will work without errors, but it will not do anything.
Parameter injecting: Path to the configuration directory
The Component instance needs to know where to read the configurations.
And pretty likely there is more information and data the component is to be fed with.
Therefore there is a parameter-class Miniature\Component\InitParameters
to inject the component constructor with.
Here in this first example the path to the configuration directory might do.
This may not look good for you, since because you don't know where the component is called first in the lifetime of a request handling. Therefore, there is a overridable static auto-injection method.
This move enables you to access the component instance "from any place at any time".
The call will always look the same.
Even when you create it. You will never know that you create it.
Learn about the content of the configuration directory and the dependeny wiring here.
Your almost finished component
Given, you want to provide a type-save respectively contract-save access to chosen instances that live in the DI-Container, your finished component might look somewhat like this:
There are two public methods which provide certain access by the means of objects they retrieved from the DI-Container. There is one method retrieving another access object from another component, most likely returning it to a class residing inside the component, thus consuming the contract.
But you're not really done yet. Though you provided a contract by returning an interface implementation in your public methods you don't have the control about "who" is consuming it. Without any control it very easily will result in multiple criss-cross connection spun among the components and thus a very undesired structure. A failure of the architectural task.
Also, some restrictions about which classes from inside the DI-Container will make sense.
Threfore, Miniature\Component
offers a simple wiring mechanism.
Coupling detection and protection
Miniature\Component\Component
offers a protection method that ensures that it is only accessed
by classes and methods having been granted to do so by
wiring the coupling configuration.
No return value needed. insureCoupling()
very simply throws Exceptions.
Find the providing and consuming methods in the example below provided with the call. Without the wiring they will do nothing but throw Exceptions.
Wiring the Coupling
You might want to observe an overview diagram first for general better understanding.
These wiring examples are roughly based on the component-class example
shown above.
We just assume there is a The2ndComponent
and some classes from the inside of the container.
The examples are simplified.
Of course the class names would appear in their fully qualified form here.
Be aware that YAML-support is not available by default.
PHP | YAML |
The first wiring in the example explained
Start of the component coupling section:
The method SelfSpeakingComponent->providePerson()
...
... allows to be called by The2ndComponent->consumePerson()
.
This can be switched off immediately by changing the value to false
.
Gerneral wiring features
Generally, there are assignments of multiple classes and multiple methods possible. It should become clear by watching the YAML structure.
Multiple assignments among components should be considered undesirable unless it is proven, that these assignments altogether realize somewhat "one interface". It might be different for classes that access the component from inside the DI-Container.
Wildcard notation
There is also the option, to grant a class in general. Maybe for testing or development or it is a class residing inside the container dedicated to the communication with the component.
The DI-Mapping
Hard wiring only
Sorry, so far there will be no auto-wiring. Beside lack of development time and possible resource issues caused by the use of reflection classes we also think that, as far as interface-to-implementation mapping is concerned, you have all the means at hand already.
Deadlock Protection
Miniature\DiContainer
provides a deadlock protection via the Miniature\DiContainer\DiNode
-tree
that is built in the instantiation process.
A testing mode that bubble the whole wiring might be desired for the future.
An example
Let's start with simple examples in PHP and YAML. Be aware that YAML-support is not available by default. PHP on the other hand might have the advantage (or uncertainty) that structures can be produced dynamically.
If you used to service-container configurations in one of the popular MVC frameworks,
this might look familiar to you.
Indeed, we had considered adapting the syntax from Symfony but came to the conclusion
that the use of the key service
causes much confusion in the terminology.
So mapping-branch it is named as what it represents: di_mapping
.
If you want to change the syntax you can do it by overriding.
PHP | YAML |
The keys explained
Keys for classes
Find the 2nd-level keys such as person
, address
, person_manager
, mysql_wrapper
,
and person_access
repeated in the args
sub-branches with an @
-prefix added.
This is how classes are mapped as arguments (args
) for the constructor injection.
The naming is completely your choice. Consider name-spacing.
As mentioned above, equal keys will lead to overrides.
This is for the realisation of environment-based overrides.
Compulsory for each class-mapping is the key class
that most likely refers to a fully qualified class name.
Keys for parameter data
Find the mysql_wrapper
attributed with an argument tagged %mysql_person_connection
.
You will find this key (mysql_person_connection
) down in the params
-branch.
This will be passed to the constructor without any changes.
The same is true about the key access_token
(%access_token
respectively).
Key of the DI-Container
The mapping person_access
bears an example of how to inject the DI-Container itself via the arguments list:
@miniature.di_container
.
Be aware constructor injecting is easier to handle for the purpose of unit tests. But there are cases likely where you will even need it since this is the only way to directly access the container.
Key: 'static'
Another example from the entry person_access
: the key static
.
This should bear the string with the name of a static generation-/access-method to be used instead of the average constructor call (e.g. new ClasName($param)
).
In the case of our example it is the standard call for a singleton pattern, which will result in:
Key: 'singleton'
The use of the key singleton
is completely independent of the design pattern of the same name.
By assigning a true
you force the class only to be instantiated once.
This is not only useful for the cases where you would use the design pattern.
This might always be useful in cases when you can be ensured that no results will be stored in member variables.
This is the fastest and resource saving way to access an instance.
An internal key instance
will always be checked.
If there is content it will be returned and all other proceedings will be skipped.
To be true, this is not a substitute for a real singleton pattern.
(You might prefer to call it a cache.)
You could instantiate the same class under two different keys, maybe using different parameters.
Maybe this is your intention?
Key 'public'
Setting the key public
to true
you make the instance available from the outside via the component.
By default, this functionality is available for environments 'dev' and 'test' only.
You can control the behaviour by setting the environments that allow public
.
In our case something like this (assuming we are using auto-injection):
In case you dislike magic access you might prefer get($string)
as shown here:
If you feel tempted to declare everything public
or question what this cumbersome stuff is for,
I'd suggest to gain info about the Cohesion & Coupling problem and what component architectures are about.
Key 'skipViolationScan'
This is not includet to the upper example.
This key set to true
will disable the
violation detection
in the violation scan routine.
This for instance is useful in cases of vendor classes having been included to the mapping.
Overriding arguments on the fly
Please note: This feature is not compatible with the singleton feature. An attempt to combine both features will result in an Exception.
This kind of overriding the arguments is only available to classes
which had the container injected.
Retrieving instances from the container works via the get
:
On the fly overriding happens via a second parameter, an array whose fields are indexed the same as the original arguments array. You might provide all parameters once again or you might just want to provide certain parameters. In the second case string-indexes in the arguments are useful.
Considering the excample from above, let's assume the person_access
-instance
wants to retrieve a person_manager
instance using replacement-classes
for @person
and @adress
, it might look somehow like this:
Since the person_manager
hold an associative array as arguments, there is even no need to care about the sequence.
This approach would also work with numeric keys basically, but would one fell comfortable with that?
Reading the configuration directory
Reading behaviour
All files in the config-directory and it's sub-directories recursively will be read, given that the format is supported. Currently, that is:
- PHP: The PHP-files always should return an array
- YAML: This is depending on the PECL-extension for YAML is loaded. Alternatively the component class can be injected with a decorator that holds the PHP-based YAML-interpreter of your choice. More about this here
1st recursion level: selecting the interpreter
There are three main keys for the three main purposes: di_mapping
, params
, and coupling
.
di_mapping
contains the actual mapping of classes, injected parameters and behavoiour.params
contains array data which are adressable as parameters but will not be interpreted at all.coupling
contains the wiring that provides the component coupling
(By the way: If you don't like the naming you can change it by incection of the DI-Conatiner, ecept for the naming of the coupling
key.)
2nd recursion level: access keys to classes and paarameters
The keys on the next recursion level are all your choice. These are the names your classes and Parameters will be addressed with. Content with equal keys will be overwritten. This is for the basic mechanism that enables us to override for the needs of a certain environment.
YAML Support
YAML is not part of the standard PHP installation. On the other hand we try hard to keep Miniature free of foreign dependency. Nevertheless, for all the configuration work YAML is very much desirable.
Therefore, currently there are two choices:
- Installing the PECL-extension for YAML
- Injecting a decorator for an external PHP-based YAML-parser implementing the
Miniature\Component\Reader\YamlParserDecoratorInterface
The config-reader will detect if the YAML-PECL extension is installed and will give it the preference. If not, it will look if the decorator is injected and use this. Otherwise, it will throw a warning level error in case it meets a YAML file without any parser available.
We assume, the YamlParserDecoratorInterface speaks for itself. The InitParameters-class offers a dedicated setter for it:
Environment based overrides
As described above the configuration reader is merciless in overriding 2nd-recursion-level keys. And it will also continue so, by reading every sub-folder of the configuration directory recursively. You can change that by listing the sub-directories in the available environments. The inclusion in this list will prevent the eqully named directories from being read unless the current environment happens to be of the same name as the directory.
Thus is the implementation of the environment based overrides. Nothing more.
Configuring environments
Parameter injection in general
The InitParameters class
The basic configuration is done via a parameter object named InitParameters
that is passed to the Container instance.
(We might add some more config options in future in order to make this a bit more convenient.)
In longer terms the auto-injection is the better choice ...
... since it enables you to access "from any place at any time":
InitParameters supports method-chaining:
Path configuration
All path-related setters accept relative paths based on the entry point script.
Usually we assume this is the index.php
located in a public
directory.
The setter methods will change them to relative paths and check the validity.
This will also work with absolute paths.
setAppRootPath()
This is not rellay necessary, but it might make things more convenient. Once it is set, the other path related methods will concatenate to the root-path string.
So insstead of setting the config-path relatively:
It may be done directly like this:
setConfigDirectory()
Self speaking this is the directory where all the mappings reside. Be aware of the reading behaviour and the environment-based orverrides.
setDotEnvPath()
Self speaking this is the path to the directory where the .env file resides. Learn more about the resulting behaviour here.
Environment settings
setEnv()
Set the environment name directly with a string parameter.
Useful in cases there is no .env
file.
Be aware that in doubt this will lead to an override of the value read in the
.env file.
This might be useful for development situations in which you want to simulate production behaviour.
setAvailableEnv()
This should be a list of environment names that are known in your system. This list particularly is relevant in the directory reading behaviour Any directory having been named like one in the list will not be automatically scanned recursively.
It accepts an array of environment names ...
... and a parameter list of strings as well.
setEnvAllowingPublicAccess()
As in setAvailableEnv() this is a list of environment names. It can be passed as an array, or a varying number of string parameters.
Learn here about the what they are for.
Behaviour injection
setYamlParserDecorator()
You can provide a PHP-base YAML parser by the means of
implementing the Miniature\Component\Reader\YamlParserDecoratorInterface
.
Learn more in the section about YAML-support.
setDiSyntaxMapper()
Setting available environments
By default, the Component class knows three environments dev,
prod,
test`.
You can change that completely to your needs.
You can have as many as you want, and you can name them as you want.
Reading the dot-env file
The .env had become a standard for configuring valued that will be written the superglobals.
Miniature will not write to the globals and will not read from the globals.
But it will read the .env if you want.
Currently, the only purpose for this is the value APP_ENV
which will be transmitted to
the $env
flag of your component class, representing the
current environment.
Be aware the call of setEnv() will override this value.
Current environment
The current environment is named by a string naming the environment the local machine is providing.
Usually it is something like developement
, production
, or test
.
The value might com from the .env
-file
or it might be set directly.
Anyway, it has certain effects, most notably in the reading behaviour
during the scanning of the configuration directory.