The problem of object initialization in OOP applications in PHP. Finding a solution using Registry, Factory Method, Service Locator and Dependency Injection patterns. OOP patterns with examples and descriptions Authoritative registro php

The problem of object initialization in OOP applications in PHP. Finding a solution using Registry, Factory Method, Service Locator and Dependency Injection patterns

It just so happens that programmers consolidate successful solutions in the form of design patterns. There is a lot of literature on patterns. The Gang of Four's book "Design Patterns" by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides" and, perhaps, "Patterns of Enterprise Application Architecture" by Martin Fowler are certainly considered classics. The best thing I've read with examples in PHP - this. It just so happens that all this literature is quite complex for people who have just begun to master OOP. So I had the idea to present some of the patterns that I find most useful in a greatly simplified form. In other words, this article is my first an attempt to interpret design patterns in the KISS style.
Today we'll talk about what problems can arise with object initialization in an OOP application and how you can use some popular design patterns to solve these problems.

Example

A modern OOP application works with tens, hundreds, and sometimes thousands of objects. Well, let's take a closer look at how these objects are initialized in our applications. Object initialization is the only aspect that interests us in this article, so I decided to omit all the “extra” implementation.
Let's say we created a super-duper useful class that can send a GET request to a specific URI and return HTML from the server response. So that our class does not seem too simple, let it also check the result and throw an exception if the server responds “incorrectly”.

Class Grabber ( public function get($url) (/** returns HTML code or throws an exception */) )

Let's create another class whose objects will be responsible for filtering the received HTML. The filter method takes HTML code and a CSS selector as arguments, and it returns an array of elements found for the given selector.

Class HtmlExtractor ( public function filter($html, $selector) (/** returns array of filtered elements */) )

Now, imagine that we need to get search results on Google for given keywords. To do this, we will introduce another class that will use the Grabber class to send a request, and the HtmlExtractor class to extract the necessary content. It will also contain the logic for constructing the URI, a selector for filtering the received HTML, and processing the results obtained.

Class GoogleFinder ( private $grabber; private $filter; public function __construct() ( $this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) public function find($searchString) ( /* * returns array of founded results */) )

Did you notice that the initialization of the Grabber and HtmlExtractor objects is in the constructor of the GoogleFinder class? Let's think about how good this decision is.
Of course, hardcoding the creation of objects in a constructor is not a good idea. And that's why. First, we won't be able to easily override the Grabber class in the test environment to avoid sending a real request. To be fair, it is worth saying that this can be done using the Reflection API. Those. the technical possibility exists, but this is far from the most convenient and obvious way.
Secondly, the same problem will arise if we want to reuse GoogleFinder logic with other Grabber and HtmlExtractor implementations. Creation of dependencies is hardcoded in the class constructor. And in the best case, we will be able to inherit GoogleFinder and override its constructor. And even then, only if the scope of the grabber and filter properties is protected or public.
One last point, each time we create a new GoogleFinder object, a new pair of dependency objects will be created in memory, although we can quite easily use one Grabber object and one HtmlExtractor object in several GoogleFinder objects.
I think you already understand that dependency initialization needs to be moved outside the class. We can require that already prepared dependencies be passed to the constructor of the GoogleFinder class.

Class GoogleFinder ( private $grabber; private $filter; public function __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filter = $filter; ) public function find($searchString) ( /** returns array of founded results */) )

If we want to give other developers the ability to add and use their own Grabber and HtmlExtractor implementations, then we should consider introducing interfaces for them. In this case, this is not only useful, but also necessary. I believe that if we use only one implementation in a project and do not expect to create new ones in the future, then we should refuse to create an interface. It is better to act according to the situation and do simple refactoring when there is a real need for it.
Now we have all the necessary classes and we can use the GoogleFinder class in the controller.

Class Controller ( public function action() ( /* Some stuff */ $finder = new GoogleFinder(new Grabber(), new HtmlExtractor()); $results = $finder->

Let's sum up the intermediate results. We wrote very little code, and at first glance, we didn’t do anything wrong. But... what if we need to use an object like GoogleFinder in another place? We will have to duplicate its creation. In our example, this is just one line and the problem is not so noticeable. In practice, initializing objects can be quite complex and can take up to 10 lines, or even more. Other problems typical of code duplication also arise. If during the refactoring process you need to change the name of the class used or the object initialization logic, you will have to manually change all the places. I think you know how it happens :)
Usually, hardcode is dealt with simply. Duplicate values ​​are usually included in the configuration. This allows you to change values ​​centrally in all places where they are used.

Registry template.

So, we decided to move the creation of objects into the configuration. Let's do that.

$registry = new ArrayObject(); $registry["grabber"] = new Grabber(); $registry["filter"] = new HtmlExtractor(); $registry["google_finder"] = new GoogleFinder($registry["grabber"], $registry["filter"]);
All we have to do is pass our ArrayObject to the controller and the problem is solved.

Class Controller ( private $registry; public function __construct(ArrayObject $registry) ( $this->registry = $registry; ) public function action() ( /* Some stuff */ $results = $this->registry["google_finder" ]->find("search string"); /* Do something with results */ ) )

We can further develop the idea of ​​Registry. Inherit ArrayObject, encapsulate the creation of objects inside a new class, prohibit adding new objects after initialization, etc. But in my opinion, the given code fully makes it clear what the Registry template is. This pattern is not generative, but it goes some way to solving our problems. Registry is just a container in which we can store objects and pass them around within the application. In order for objects to become available, we need to first create them and register them in this container. Let's look at the advantages and disadvantages of this approach.
At first glance, we have achieved our goal. We stopped hardcoding class names and create objects in one place. We create objects in a single copy, which guarantees their reuse. If the logic for creating objects changes, then only one place in the application will need to be edited. As a bonus, we received the ability to centrally manage objects in the Registry. We can easily get a list of all available objects and perform some manipulations with them. Let's now look at what we might not like about this template.
First, we must create the object before registering it with the Registry. Accordingly, there is a high probability of creating “unnecessary objects”, i.e. those that will be created in memory, but will not be used in the application. Yes, we can add objects to the Registry dynamically, i.e. create only those objects that are needed to process a specific request. One way or another, we will have to control this manually. Accordingly, over time it will become very difficult to maintain.
Secondly, we have a new controller dependency. Yes, we can receive objects through a static method in Registry so that we don't have to pass Registry to the constructor. But in my opinion, you shouldn't do this. Static methods are an even tighter connection than creating dependencies inside an object, and difficulties in testing (on this topic).
Third, the controller interface doesn't tell us anything about what objects it uses. We can get any object available in the Registry in the controller. It will be difficult for us to say which objects the controller uses until we check all its source code.

Factory Method

Our biggest gripe with Registry is that an object must be initialized before it can be accessed. Instead of initializing an object in the configuration, we can separate the logic for creating objects into another class, which we can “ask” to build the object we need. Classes that are responsible for creating objects are called factories. And the design pattern is called Factory Method. Let's look at an example factory.

Class Factory ( public function getGoogleFinder() ( return new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) private function getGrabber() ( return new Grabber(); ) private function getHtmlExtractor() ( return new HtmlFiletr(); ) )

As a rule, factories are made that are responsible for creating one type of object. Sometimes a factory may create a group of related objects. We can use caching into a property to avoid re-creating objects.

Class Factory ( private $finder; public function getGoogleFinder() ( if (null === $this->finder) ( $this->finder = new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) return $this->finder; ) )

We can parameterize a factory method and delegate initialization to other factories depending on the incoming parameter. This will already be an Abstract Factory template.
If we need to modularize the application, we can require each module to provide its own factories. We can further develop the theme of factories, but I think that the essence of this template is clear. Let's see how we will use the factory in the controller.

Class Controller ( private $factory; public function __construct(Factory $factory) ( $this->factory = $factory; ) public function action() ( /* Some stuff */ $results = $this->factory->getGoogleFinder( )->find("search string"); /* Do something with results */ ) )

The advantages of this approach include its simplicity. Our objects are created explicitly, and your IDE will easily lead you to the place where this happens. We've also solved the Registry problem so that objects in memory will only be created when we "ask" the factory to do so. But we have not yet decided how to supply the necessary factories to the controllers. There are several options here. You can use static methods. We can let controllers create the necessary factories themselves and nullify all our attempts to get rid of copy-paste. You can create a factory of factories and pass only that to the controller. But getting objects in the controller will become a little more complicated, and you will need to manage dependencies between factories. In addition, it is not entirely clear what to do if we want to use modules in our application, how to register module factories, how to manage connections between factories from different modules. In general, we have lost the main advantage of the factory - the explicit creation of objects. And we still haven’t solved the problem of the “implicit” controller interface.

Service Locator

The Service Locator template allows you to solve the lack of fragmentation of factories and manage the creation of objects automatically and centrally. If we think about it, we can introduce an additional abstraction layer that will be responsible for creating objects in our application and managing the relationships between these objects. In order for this layer to be able to create objects for us, we will need to give it the knowledge of how to do this.
Service Locator pattern terms:
  • Service is a ready-made object that can be obtained from a container.
  • Service Definition – service initialization logic.
  • A container (Service Container) is a central object that stores all descriptions and can create services based on them.
Any module can register its service descriptions. To get some service from the container, we will have to request it by key. There are many options for implementing Service Locator; in the simplest version, we can use ArrayObject as a container and a closure as a description of services.

Class ServiceContainer extends ArrayObject ( public function get($key) ( if (is_callable($this[$key])) ( return call_user_func($this[$key]); ) throw new \RuntimeException("Can not find service definition under the key [ $key ]"); ) )

Then the Definitions registration will look like this:

$container = new ServiceContainer(); $container["grabber"] = function () ( return new Grabber(); ); $container["html_filter"] = function () ( return new HtmlExtractor(); ); $container["google_finder"] = function() use ($container) ( return new GoogleFinder($container->get("grabber"), $container->get("html_filter")); );

And the usage in the controller is like this:

Class Controller ( private $container; public function __construct(ServiceContainer $container) ( $this->container = $container; ) public function action() ( /* Some stuff */ $results = $this->container->get( "google_finder")->find("search string"); /* Do something with results */ ) )

A Service Container can be very simple, or it can be very complex. For example, Symfony Service Container provides a lot of features: parameters, scopes of services, search for services by tags, aliases, private services, the ability to make changes to the container after adding all services (compiller passes) and much more. DIExtraBundle further expands the capabilities of the standard implementation.
But let's return to our example. As you can see, Service Locator not only solves all the same problems as the previous templates, but also makes it easy to use modules with their own service definitions.
In addition, at the framework level we received an additional level of abstraction. Namely, by changing the ServiceContainer::get method we can, for example, replace the object with a proxy. And the scope of application of proxy objects is limited only by the developer’s imagination. Here you can implement the AOP paradigm, LazyLoading, etc.
But most developers still consider Service Locator an anti-pattern. Because, in theory, we can have as many so-called Container Aware classes (i.e. classes that contain a reference to the container). For example, our Controller, inside of which we can get any service.
Let's see why this is bad.
First, testing again. Instead of creating mocks only for the classes used in tests, you will have to mock the entire container or use a real container. The first option does not suit you, because... you have to write a lot of unnecessary code in tests, second, because it contradicts the principles of unit testing, and can lead to additional costs for maintaining tests.
Secondly, it will be difficult for us to refactor. By changing any service (or ServiceDefinition) in the container, we will be forced to check all dependent services as well. And this problem cannot be solved with the help of an IDE. Finding such places throughout the application will not be so easy. In addition to dependent services, you will also need to check all the places where the refactored service is obtained from the container.
Well, the third reason is that uncontrolled pulling of services from the container will sooner or later lead to a mess in the code and unnecessary confusion. This is difficult to explain, you will just need to spend more and more time to understand how this or that service works, in other words, you can fully understand what it does or how a class works only by reading its entire source code.

Dependency Injection

What else can you do to limit the use of a container in an application? You can transfer control of the creation of all user objects, including controllers, to the framework. In other words, user code should not call the container's get method. In our example, we can add a Definition for the controller to the container:

$container["google_finder"] = function() use ($container) ( return new Controller(Grabber $grabber); );

And get rid of the container in the controller:

Class Controller ( private $finder; public function __construct(GoogleFinder $finder) ( $this->finder = $finder; ) public function action() ( /* Some stuff */ $results = $this->finder->find( "search string"); /* Do something with results */ ) )

This approach (when access to the Service Container is not provided to client classes) is called Dependency Injection. But this template also has both advantages and disadvantages. As long as we adhere to the principle of single responsibility, the code looks very beautiful. First of all, we got rid of the container in the client classes, making their code much clearer and simpler. We can easily test the controller by replacing the necessary dependencies. We can create and test each class independently of others (including controller classes) using a TDD or BDD approach. When creating tests, we can abstract away from the container, and later add a Definition when we need to use specific instances. All this will make our code simpler and clearer, and testing more transparent.
But it is necessary to mention the other side of the coin. The fact is that controllers are very specific classes. Let's start with the fact that the controller, as a rule, contains a set of actions, which means it violates the principle of single responsibility. As a result, the controller class may have many more dependencies than are necessary to execute a specific action. Using lazy initialization (the object is instantiated at the time of its first use, and before that a lightweight proxy is used) solves the performance issue to some extent. But from an architectural point of view, creating many dependencies on a controller is also not entirely correct. In addition, testing controllers is usually an unnecessary operation. Everything, of course, depends on how testing is organized in your application and on how you yourself feel about it.
From the previous paragraph, you realized that using Dependency Injection does not completely eliminate architectural problems. Therefore, think about how it will be more convenient for you, whether to store a link to the container in controllers or not. There is no single correct solution here. I think both approaches are good as long as the controller code remains simple. But, definitely, you should not create Conatiner Aware services in addition to controllers.

conclusions

Well, the time has come to sum up everything that has been said. And a lot has been said... :)
So, to structure the work of creating objects, we can use the following patterns:
  • Registry: The template has obvious disadvantages, the most basic of which is the need to create objects before putting them into a common container. Obviously, we will get more problems than benefits from using it. This is clearly not the best use of the template.
  • Factory Method: The main advantage of the pattern: objects are created explicitly. The main disadvantage: controllers either have to worry about creating factories themselves, which does not completely solve the problem of class names being hardcoded, or the framework must be responsible for providing controllers with all the necessary factories, which will not be so obvious. There is no possibility to centrally manage the process of creating objects.
  • Service Locator: A more advanced way to control the creation of objects. An additional level of abstraction can be used to automate common tasks encountered when creating objects. For example:
    class ServiceContainer extends ArrayObject ( public function get($key) ( if (is_callable($this[$key])) ( $obj = call_user_func($this[$key]); if ($obj instanceof RequestAwareInterface) ( $obj- >setRequest($this->get("request")); ) return $obj; ) throw new \RuntimeException("Can not find service definition under the key [ $key ]"); ) )
    The disadvantage of Service Locator is that the public API of classes ceases to be informative. It is necessary to read the entire code of the class to understand what services are used in it. A class that contains a reference to a container is more difficult to test.
  • Dependency Injection: Essentially we can use the same Service Container as for the previous pattern. The difference is how this container is used. If we avoid making classes dependent on the container, we will get a clear and explicit class API.
This is not all I would like to tell you about the problem of creating objects in PHP applications. There is also the Prototype pattern, we did not consider the use of the Reflection API, we left aside the problem of lazy loading of services and many other nuances. The article turned out to be quite long, so I’ll wrap it up :)
I wanted to show that Dependency Injection and other patterns are not as complicated as is commonly believed.
If we talk about Dependency Injection, then there are KISS implementations of this pattern, for example

Touching upon the structure of the future database. A start has been made, and we cannot retreat, and I don’t even think about it.

We will return to the database a little later, but for now we will start writing the code for our engine. But first, a little bit of hardware. Begin.

The beginning of time

At the moment, we only have some ideas and understanding of the operation of the system that we want to implement, but there is no implementation itself yet. We have nothing to work with: we do not have any functionality - and, as you remember, we divided it into 2 parts: internal and external. The alphabet requires letters, but external functionality requires internal functionality—that’s where we’ll start.

But not so fast. For it to work you need to go a little deeper. Our system represents a hierarchy, and any hierarchical system has a beginning: a mount point in Linux, a local disk in Windows, a system of a state, a company, an educational institution, etc. Each element of such a system is subordinate to someone and can have several subordinates, and to address its neighbors and their subordinates it uses superiors or the beginning itself. A good example of a hierarchical system is a family tree: a starting point is chosen - some ancestor - and away we go. In our system, we also need a starting point from which we will grow branches - modules, plugins, etc. We need some kind of interface through which all our modules will “communicate”. For further work, we need to get acquainted with the concept “ design pattern" and a couple of their implementations.

Design Patterns

There are a great many articles about what it is and what varieties there are; the topic is quite hackneyed and I won’t tell you anything new. On my favorite Wiki there is information on this topic: a carriage with a slide and a little more.

Design patterns are also often called design patterns or simply patterns (from the English word pattern, translated meaning “pattern”). Further in the articles, when I talk about patterns, I will mean design patterns.

From the huge list of all kinds of scary (and not so scary) pattern names, we are only interested in two so far: registry and singleton.

Registry (or register) is a pattern that operates on a certain array into which you can add and remove a certain set of objects and gain access to any of them and its capabilities.

Loner (or singleton) is a pattern that ensures that only one instance of a class can exist. It cannot be copied, put to sleep or woken up (talking about PHP magic: __clone(), __sleep(), __wakeup()). Singleton has a global access point.

The definitions are not complete or generalized, but this is enough for understanding. We don't need them separately anyway. We are interested in the capabilities of each of these patterns, but in one class: such a pattern is called singleton registry or Singleton Registry.

What will this give us?
  • We will be guaranteed to have a single instance of the registry, into which we can add objects at any time and use them from anywhere in the code;
  • it will be impossible to copy it and use other unwanted (in this case) magic of the PHP language.

At this stage, it is enough to understand that a single registry will allow us to implement a modular structure of the system, which is what we wanted when discussing the goals in , and you will understand the rest as development progresses.

Well, enough words, let's create!

First lines

Since this class will relate to the functionality of the kernel, we will start by creating a folder in the root of our project called core in which we will place all classes of kernel modules. We start with the registry, so let's call the file registry.php

We are not interested in the possibility that a curious user will enter a direct address to our file into the browser line, so we need to protect ourselves from this. To achieve this goal, we just need to define a certain constant in the main executable file, which we will check. The idea is not new; as far as I remember, it was used in Joomla. This is a simple and working method, so we can do without bicycles here.

Since we are protecting something that is connected, we will call the constant _PLUGSECURE_ :

If (!defined("_PLUGSECURE_")) ( die("Direct module call is prohibited!"); )

Now, if you try to access this file directly, nothing useful will come out, which means the goal has been achieved.

Next, I propose to stipulate a certain standard for all our modules. I want to provide each module with a function that will return some information about it, such as the name of the module, and this function must be required in the class. To achieve this goal we write the following:

Interface StorableObject ( public static function getClassName(); )

Like this. Now, if we connect any class without a function getClassName() we will see an error message. I won’t focus on this for now, it will be useful to us later, for testing and debugging at least.

It's time for the class of our singles registry itself. We'll start by declaring the class and some of its variables:

Class Registry implements StorableObject ( //module name, readable private static $className = "Registry"; //registry instance private static $instance; //array of objects private static $objects = array();

So far everything is logical and understandable. Now, as you remember, we have a registry with singleton properties, so let’s immediately write a function that will allow us to work with the registry in this way:

Public static function singleton() ( if(!isset(self::$instance)) ( $obj = __CLASS__; self::$instance = new $obj; ) return self::$instance; )

Literally: the function checks whether an instance of our registry exists: if not, it creates it and returns it; if it already exists, it simply returns it. In this case, we don’t need some magic, so for protection we’ll declare it private:

Private function __construct()() private function __clone()() private function __wakeup()() private function __sleep() ()

Now we need a function to add an object to our registry - this function is called a setter and I decided to implement it in two ways to show how we can use magic and provide an alternative way to add an object. The first method is a standard function, the second one executes the first one through the magic of __set().

//$object - path to the connected object //$key - access key to the object in the register public function addObject($key, $object) ( require_once($object); //create an object in an array of objects self::$objects[ $key] = new $key(self::$instance); ) //an alternative method through magic public function __set($key, $object) ( $this->addObject($key, $object); )

Now, to add an object to our registry, we can use two types of entries (let’s say we have already created a registry instance $registry and we want to add the config.php file):

$registry->addObject("config", "/core/config.php"); //regular method $registry->config = "/core/config.php"; //via the PHP magic function __set()

Both entries will perform the same function - they will connect the file, create an instance of the class and place it in the register with the key. There is one important point here, we must not forget about it in the future: the object key in the register must match the class name in the connected object. If you look at the code again, you will understand why.

Which recording to use is up to you. I prefer recording through the magic method - it is “prettier” and shorter.

So, we’ve sorted out adding an object, now we need a function for accessing a connected object by key - a getter. I also implemented it with two functions, similar to the setter:

//get the object from the register //$key - the key in the array public function getObject($key) ( //check whether the variable is an object if (is_object(self::$objects[$key])) ( //if so, then we return this object return self::$objects[$key]; ) ) //a similar method through magic public function __get($key) ( if (is_object(self::$objects[$key])) ( return self: :$objects[$key]; ) )

As with the setter, to gain access to the object we will have 2 equivalent entries:

$registry->getObject("config"); //regular method $registry->config; //via the PHP magic function __get()

The attentive reader will immediately ask the question: why in the __set() magic function I just call a regular (non-magic) object adding function, but in the __get() getter I copy the getObject() function code instead of the same call? Honestly, I can’t answer this question accurately enough, I’ll just say that I had problems when working with the __get() magic in other modules, but when rewriting the code “head-on” there are no such problems.

Maybe that’s why I often saw in articles reproaches towards PHP magic methods and advice to avoid using them.

"All magic comes with a price." © Rumplestiltskin

At this stage, the main functionality of our registry is already ready: we can create a single instance of the registry, add objects and access them both using conventional methods and through the magic methods of the PHP language. “What about deletion?”— we won’t need this function for now, and I’m not sure that anything will change in the future. In the end, we can always add the necessary functionality. But if we now try to create an instance of our registry,

$registry = Registry::singleton();

we will get an error:

Fatal error: Class Registry contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (StorableObject::getClassName) in ...

All because we forgot to write a required function. Remember at the very beginning I talked about a function that returns the name of the module? This is what remains to be added for full functionality. It's simple:

Public static function getClassName() ( return self::$className; )

Now there should be no errors. I propose adding one more function, it is not required, but sooner or later it may come in handy; we will use it in the future for checking and debugging. The function will return the names of all objects (modules) added to our registry:

Public function getObjectsList() ( //the array that we will return $names = array(); //get the name of each object from the array of objects foreach(self::$objects as $obj) ( $names = $obj->getClassName() ; ) //add the name of the register module to the array array_push($names, self::getClassName()); //and return return $names; )

That's all. This completes the register. Let's check his work? When checking, we will need to connect something - let there be a configuration file. Create a new core/config.php file and add the minimum content that our registry requires:

//don't forget to check the constant if (!defined("_PLUGSECURE_")) ( die("Direct module call is prohibited!"); ) class Config ( //module name, readable private static $className = "Config"; public static function getClassName() ( return self::$className; ) )

Something like that. Now let's proceed to the verification itself. In the root of our project, create a file index.php and write the following code into it:

Define("_PLUGSECURE_", true); //defined a constant to protect against direct access to objects require_once "/core/registry.php"; //connected the register $registry = Registry::singleton(); //created a register singleton instance $registry->config = "/core/config.php"; //connect our, so far useless, config //display the names of the connected modules echo " Connected"; foreach ($registry->

  • " . $names . "
  • "; }

    Or, if you still avoid magic, then the 5th line can be replaced with an alternative method:

    Define("_PLUGSECURE_", true); //defined a constant to protect against direct access to objects require_once "/core/registry.php"; //connected the register $registry = Registry::singleton(); //created a register singleton instance $registry->addObject("config", "/core/config.php"); //connect our, so far useless, config //display the names of the connected modules echo " Connected"; foreach ($registry->getObjectsList() as $names) ( echo "

  • " . $names . "
  • "; }

    Now open the browser and write http://localhost/index.php or simply http://localhost/ in the address bar (relevant if you are using standard Open Server or similar web server settings)

    As a result, we should see something like this:

    As you can see, there are no errors, which means everything works, for which I congratulate you :)

    Today we will stop at this. In the next article, we will return to the database and write a class for working with MySQL SUDB, connect it to the registry and test the work in practice. See you!

    This pattern, like Singleton, rarely causes a positive reaction from developers, as it gives rise to the same problems when testing applications. Nevertheless, they scold, but actively use. Like Singleton, the Registry pattern is found in many applications and, one way or another, greatly simplifies solving certain problems.

    Let's consider both options in order.

    What is called a "pure registry" or simply Registry is an implementation of a class with a static interface. The main difference from the Singleton pattern is that it blocks the ability to create at least one instance of a class. In view of this, there is no point in hiding the magic methods __clone() and __wakeup() behind the private or protected modifier.

    Registry class must have two static methods - a getter and a setter. The setter places the passed object into storage with a binding to the given key. The getter, accordingly, returns an object from the store. A store is nothing more than an associative key-value array.

    For complete control over the registry, another interface element is introduced - a method that allows you to delete an object from the storage.

    In addition to problems identical to the Singleton pattern, there are two more:

    • introducing another type of dependency - on registry keys;
    • two different registry keys can have a reference to the same object

    In the first case, it is impossible to avoid additional dependence. To some extent, we do become attached to key names.

    The second problem is solved by introducing a check into the Registry::set() method:

    Public static function set($key, $item) ( if (!array_key_exists($key, self::$_registry)) ( foreach (self::$_registry as $val) ( if ($val === $item) ( throw new Exception("Item already exists"); ) ) self::$_registry[$key] = $item; ) )

    « Clean Registry pattern"gives rise to another problem - increasing the dependence due to the need to access the setter and getter through the class name. You cannot create a reference to an object and work with it, as was the case with the Singleton pattern, when this approach was available:

    $instance = Singleton::getInstance(); $instance->Foo();

    Here we have the opportunity to save a reference to a Singleton instance, for example, in a property of the current class, and work with it as required by the OOP ideology: pass it as a parameter to aggregated objects or use it in descendants.

    To resolve this issue there is Singleton Registry implementation, which many people don’t like because it seems like redundant code. I think the reason for this attitude is some misunderstanding of the principles of OOP or a deliberate disregard for them.

    _registry[$key] = $object; ) static public function get($key) ( return self::getInstance()->_registry[$key]; ) private function __wakeup() ( ) private function __construct() ( ) private function __clone() ( ) ) ?>

    To save money, I deliberately omitted comment blocks for methods and properties. I don't think they are necessary.

    As I already said, the fundamental difference is that now it is possible to save a reference to the registry volume and not use cumbersome calls to static methods every time. This option seems to me somewhat more correct. Agreeing or disagreeing with my opinion does not matter much, just like my opinion itself. No implementation subtleties can eliminate the pattern from a number of the mentioned disadvantages.

    I decided to write briefly about patterns often used in our lives, more examples, less water, let's go.

    Singleton

    The main point of the “single” is that when you say “I need a telephone exchange,” they would say to you “It has already been built there,” and not “Let’s build it again.” A “loner” is always alone.

    Class Singleton ( private static $instance = null; private function __construct())( /* ... @return Singleton */ ) // Protect against creation via new Singleton private function __clone() ( /* ... @return Singleton * / ) // Protect against creation via cloning private function __wakeup() ( /* ... @return Singleton */ ) // Protect against creation via unserialize public static function getInstance() ( if (is_null(self::$instance) ) ( self::$instance = new self; ) return self::$instance; ) )

    Registry (registry, journal of entries)

    As the name suggests, this pattern is designed to store records that are placed in it and, accordingly, return these records (by name) if they are required. In the example of a telephone exchange, it is a register in relation to the telephone numbers of residents.

    Class Registry ( private $registry = array(); public function set($key, $object) ( $this->registry[$key] = $object; ) public function get($key) ( return $this->registry [$key]; ) )

    Singleton Registry- do not confuse with)

    The “registry” is often a “loner,” but it doesn’t always have to be that way. For example, we can create several journals in the accounting department, in one there are employees from “A” to “M”, in the other from “N” to “Z”. Each such journal will be a “registry”, but not a “single”, because there are already 2 journals.

    Class SingletonRegistry ( private static $instance = null; private $registry = array(); private function __construct() ( /* ... @return Singleton */ ) // Protect from creation via new Singleton private function __clone() ( / * ... @return Singleton */ ) // Protect against creation via cloning private function __wakeup() ( /* ... @return Singleton */ ) // Protect against creation via unserialize public static function getInstance() ( if ( is_null(self::$instance)) ( self::$instance = new self; ) return self::$instance; ) public function set($key, $object) ( $this->registry[$key] = $ object; ) public function get($key) ( return $this->registry[$key]; ) )

    Multiton (pool of “singles”) or in other wordsRegistry Singleton ) - do not confuse with Singleton Registry

    Often the “register” is used specifically to store “singles”. But, because the “registry” pattern is not a “generative pattern”, but I would like to consider the “register” in connection with the “singleton”.That's why we came up with a pattern Multiton, which according toAt its core, it is a “registry” containing several “singles”, each of which has its own “name” by which it can be accessed.

    Short: allows you to create objects of this class, but only if you name the object. There is no real-life example, but I found the following example on the Internet:

    Class Database ( private static $instances = array(); private function __construct() ( ) private function __clone() ( ) public static function getInstance($key) ( if(!array_key_exists($key, self::$instances)) ( self::$instances[$key] = new self(); ) return self::$instances[$key]; ) ) $master = Database::getInstance("master"); var_dump($master); // object(Database)#1 (0) ( ) $logger = Database::getInstance("logger"); var_dump($logger); // object(Database)#2 (0) ( ) $masterDupe = Database::getInstance("master"); var_dump($masterDupe); // object(Database)#1 (0) ( ) // Fatal error: Call to private Database::__construct() from invalid context $dbFatalError = new Database(); // PHP Fatal error: Call to private Database::__clone() $dbCloneError = clone $masterDupe;

    Object pool

    Essentially this pattern is a “registry” that stores only objects, no strings, arrays, etc. data types.

    Factory

    The essence of the pattern is almost completely described by its name. When you need to get some objects, such as juice boxes, you don't need to know how they are made in a factory. You simply say, “give me a carton of orange juice,” and the “factory” returns the required package to you. How? All this is decided by the factory itself, for example, it “copies” an already existing standard. The main purpose of the “factory” is to make it possible, if necessary, to change the process of “appearance” of a juice package, and the consumer himself does not need to be told anything about this, so that he can request it as before. As a rule, one factory is engaged in the “production” of only one type of “product”. It is not recommended to create a “juice factory” taking into account the production of car tires. As in life, the factory pattern is often created by a single person.

    Abstract class AnimalAbstract ( protected $species; public function getSpecies() ( return $this->species; ) ) class Cat extends AnimalAbstract ( protected $species = "cat"; ) class Dog extends AnimalAbstract ( protected $species = "dog"; ) class AnimalFactory ( public static function factory($animal) ( switch ($animal) ( case "cat": $obj = new Cat(); break; case "dog": $obj = new Dog(); break; default : throw new Exception("Animal factory could not create animal of species "" . $animal . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("cat"); // object(Cat)#1 echo $cat->getSpecies(); // cat $dog = AnimalFactory::factory("dog"); // object(Dog)#1 echo $dog->getSpecies(); // dog $hippo = AnimalFactory::factory("hippopotamus"); // This will throw an Exception

    I would like to draw your attention to the fact that the factory method is also a pattern; it is called the Factory method.

    Builder (builder)

    So, we have already understood that “Factory” is a drinks vending machine, it already has everything ready, and you just say what you need. “Builder” is a plant that produces these drinks and contains all the complex operations and can assemble complex objects from simpler ones (packaging, label, water, flavors, etc.) depending on the request.

    Class Bottle ( public $name; public $liters; ) /** * all builders must */ interface BottleBuilderInterface ( public function setName(); public function setLiters(); public function getResult(); ) class CocaColaBuilder implements BottleBuilderInterface ( private $ bottle; public function __construct() ( $this->bottle = new Bottle(); ) public function setName($value) ( ​​$this->bottle->name = $value; ) public function setLiters($value) ( ​​$ this->bottle->liters = $value; ) public function getResult() ( return $this->bottle; ) ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $juice->getResult();

    Prototype

    Resembling a factory, it also serves to create objects, but with a slightly different approach. Imagine yourself in a bar, you were drinking beer and you are running out of it, you tell the bartender - make me another one of the same kind. The bartender in turn looks at the beer you are drinking and makes a copy as you asked. PHP already has an implementation of this pattern, it’s called .

    $newJuice = clone $juice;

    Lazy initialization

    For example, a boss sees a list of reports for different types of activities and thinks that these reports already exist, but in fact only the names of the reports are displayed, and the reports themselves have not yet been generated, and will only be generated upon order (for example, by clicking the View report button). A special case of lazy initialization is the creation of an object at the time it is accessed. You can find an interesting one on Wikipedia, but... according to theory, the correct example in php would be, for example, a function

    Adapter or Wrapper (adapter, wrapper)

    This pattern fully corresponds to its name. To make a “Soviet” plug work through a Euro socket, an adapter is required. This is exactly what an "adapter" does - it serves as an intermediate object between two others that cannot work directly with each other. Despite the definition, in practice I still see the difference between Adapter and Wrapper.

    Class MyClass ( public function methodA() () ) class MyClassWrapper ( public function __construct())( $this->myClass = new MyClass(); ) public function __call($name, $arguments)( Log::info("You are about to call $name method."); return call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->methodA();

    Dependency injection

    Dependency injection allows you to shift part of the responsibility for some functionality to other objects. For example, if we need to hire new personnel, then we can not create our own HR department, but introduce dependence on a recruitment company, which, in turn, at our first request “we need a person,” will either work as a HR department itself, or will find another company (using a “service locator”) that will provide these services.
    “Dependency injection” allows you to shift and interchange individual parts of the company without losing overall functionality.

    Class AppleJuice () // this method is a primitive implementation of the Dependency injection pattern and further you will see this function getBottleJuice())( $obj = new AppleJuice AppleJuice)( return $obj; ) ) $bottleJuice = getBottleJuice();

    Now imagine that we no longer want apple juice, we want orange juice.

    Class AppleJuice() Class OrangeJuice() // this method implements Dependency injection function getBottleJuice())( $obj = new OrangeJuice; // check the object, in case they slipped us beer (beer is not juice) if($obj instanceof OrangeJuice)( return $obj; ) )

    As you can see, we had to change not only the type of juice, but also the check for the type of juice, which is not very convenient. It is much more correct to use the Dependency inversion principle:

    Interface Juice () Class AppleJuice implements Juice () Class OrangeJuice implements Juice () function getBottleJuice())( $obj = new OrangeJuice; // check the object, in case they slipped us beer (beer is not juice) if($obj instanceof Juice)( return $obj; ) )

    Dependency inversion is sometimes confused with Dependency injection, but there is no need to confuse them, because Dependency inversion is a principle, not a pattern.

    Service Locator

    "Service Locator" is a method of implementing "Dependency Injection". It returns different types of objects depending on the initialization code. Let the task be to deliver our juice package, created by a builder, factory or something else, wherever the buyer wants. We tell the locator “give us a delivery service,” and ask the service to deliver the juice to the desired address. Today there is one service, and tomorrow there may be another. It doesn’t matter to us what specific service it is, it’s important for us to know that this service will deliver what we tell it and where we tell it. In turn, the services implement the “Deliver<предмет>on<адрес>».

    If we talk about real life, then probably a good example of a Service Locator would be the PDO PHP extension, because Today we work with a MySQL database, and tomorrow we can work with PostgreSQL. As you already understood, it doesn’t matter to our class which database it sends its data to, it’s important that it can do it.

    $db = new PDO(" mysql:dbname=test;host=localhost", $user, $pass); $db = new PDO(" pgsql:dbname=test host=localhost", $user, $pass);

    The difference between Dependency injection and Service Locator

    If you haven’t noticed yet, I’d like to explain. Dependency injection As a result, it returns not a service (which can deliver something somewhere) but an object whose data it uses.

    I will try to tell you about my implementation of the Registry pattern in PHP. Registry is an OOP replacement for global variables, designed to store data and transfer it between system modules. Accordingly, it is endowed with standard properties - write, read, delete. Here is a typical implementation.

    Well, this way we get a stupid replacement of methods $key = $value - Registry::set($key, $value) $key - Registry::get($key) unset($key) - remove Registry::remove($key ) It just becomes unclear - why this extra code. So, let's teach our class to do what global variables cannot do. Let's add pepper to it.

    getMessage()); ) Amdy_Registry::unlock("test"); var_dump(Amdy_Registry::get("test")); ?>

    To the typical tasks of the pattern, I added the ability to block a variable from changes, this is very convenient on large projects, you won’t accidentally insert anything. For example, convenient for working with databases
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    define('DB_USER', ' ’);
    define('DB_PASSWORD', ' ’);
    define('DB_HANDLE');

    Amdy_Regisrtry::set(DB_HANDLE, new PDO(DB_DNS, DB_USER, DB_PASSWORD));
    Amdy_Registry::lock(DB_HANDLE);

    Now for an explanation of the code, to store data, we use the static variable $data, the $lock variable stores data about keys that are locked for change. In the network, we check whether the variable is locked and change or add it to the register. When deleting, we also check the lock; the getter remains unchanged, with the exception of the default optional parameter. Well, it’s worth paying attention to exception handling, which for some reason is rarely used. By the way, I already have a draft on exceptions, wait for the article. Below is a draft code for testing, here is an article about testing, it wouldn’t hurt to write it either, although I’m not a fan of TDD.

    In the next article we will further expand the functionality by adding data initialization and implementing “laziness”.