$foo = substr($bar, 42);
$bar
variable and the substr
function. $bar
is just a local variable, defined a little higher in the same file and in the same scope. substr
is a PHP core function. Everything is simple here. $foo = normalizer_normalize($bar);
class Foo { public static function bar() { return Database::fetchAll("SELECT * FROM `foo` WHERE `bar` = 'baz'"); } }
Foo
is tied to the Database
. And we assume that the Database
class has already been initialized and the connection to the database (DB) has already been established. Presumably, the use of this code will be as follows: Database::connect('localhost', 'user', 'password'); $bar = Foo::bar();
Foo::bar
implicitly depends on the availability of the Database
and its internal state. You cannot use Foo
without Database
, and Database
supposedly requires connecting to a database. How can you be sure that the connection to the database is already established when the call to Database::fetchAll
? One way looks like this: class Database { protected static $connection; public static function connect() { if (!self::$connection) { $credentials = include 'config/database.php'; self::$connection = some_database_adapter($credentials['host'], $credentials['user'], $credentials['password']); } } public static function fetchAll($query) { self::connect(); // self::$connection... // here be dragons... return $data; } }
Database::fetchAll
, we check the existence of the connection by calling the connect
method, which, if necessary, retrieves the connection parameters from the config. This means that Database
depends on the file config/database.php
. If this file does not exist, it cannot function. We go further. The Database
class is bound to a single database. If you need to transfer other connection parameters, it will be at least not easy. Kom is growing. Foo
not only dependent on the availability of the Database
, but also depends on its state. Database
depends on a specific file in a specific folder. Those. implicitly, the Foo
class depends on the file in the folder, although it is not visible from its code. Moreover, there are a lot of dependencies on the global state. Each piece depends on another piece, which must be in the desired state and nowhere is this clearly indicated. function database_connect() { global $database_connection; if (!$database_connection) { $credentials = include 'config/database.php'; $database_connection = some_database_adapter($credentials['host'], $credentials['user'], $credentials['password']); } } function database_fetch_all($query) { global $database_connection; database_connect(); // $database_connection... // ... return $data; } function foo_bar() { return database_fetch_all("SELECT * FROM `foo` WHERE `bar` = 'baz'"); }
Database::$connection
and $database_connection
.Database
class itself, and in the procedural code this variable is global. The code has the same dependencies, connections, problems and works the same. There is almost no difference between $database_connection
and Database::$connection
- it's just a different syntax for the same, both variables have a global state. The easy touch of the namespace, thanks to the use of classes, is certainly better than nothing, but does not seriously change anything.Foo
: class Foo { protected $database; public function __construct(Database $database) { $this->database = $database; } public function bar() { return $this->database->fetchAll("SELECT * FROM `foo` WHERE `bar` = 'baz'"); } }
Foo
does not depend on a specific Database
. When creating an instance of Foo
, you need to pass some object that has the characteristics of a Database
. This can be either a Database
instance or its descendant. So we can use another implementation of the Database
, which can get data from somewhere else. Or has a caching layer. Or it is a stub for tests, and not a real connection to the database. Now we need to create a Database
instance, this means that we can use several different connections to different databases, with different parameters. Let's implement the Database
: class Database { protected $connection; public function __construct($host, $user, $password) { $this->connection = some_database_adapter($host, $user, $password); if (!$this->connection) { throw new Exception("Couldn't connect to database"); } } public function fetchAll($query) { // $this->connection ... // ... return $data; } }
Database::fetchAll
do not need to check the status of the connection. To call Database::fetchAll
, you need to create an instance of the class. To create an instance of a class, you need to pass the connection parameters to the constructor. If the connection parameters are not valid or the connection cannot be established for other reasons, an exception will be thrown and the object will not be created. This all means that when you call Database::fetchAll
, you are guaranteed to have a database connection. This means that Foo
only needs to specify in the constructor that it needs a Database $database
and it will have a connection to the database.Foo
, you cannot call Foo::bar
. Without a Database
instance, you cannot create an instance of Foo
. Without valid connection parameters, you will not create a Database
instance.Foo::bar
at any time, but an error will occur if the Database
class is not ready. Database::fetchAll
can call Database::fetchAll
at any time, but an error will occur if there are problems with the config/database.php
file. Database::connect
sets a global state on which all other operations depend, but this dependency is not guaranteed.Foo
. Procedural example: $bar = foo_bar();
$bar = foo_bar(); if (!$bar) { // - $bar, ! } else { // , }
foo_bar
, in case of an error, it will be hard to understand exactly what has broken. $bar = Foo::bar(); if (!$bar) { // - $bar, ! } else { // , }
$foo = new Foo; $bar = $foo->bar();
new Foo
. We indicated that Foo
needs a Database
instance, but did not pass it. $db = new Database; $foo = new Foo($db); $bar = $foo->bar();
Database::__construct
. $db = new Database('localhost', 'user', 'password'); $foo = new Foo($db); $bar = $foo->bar();
new Database(...)
executed. The following lines simply will not be executed. So, we don’t need to check the error after calling $foo->bar()
(of course, you can check what you returned). If something goes wrong with any of the dependencies, the code will not be executed. And the thrown exception will contain information useful for debugging.foo_bar
or Foo::bar
, while the object-oriented approach takes three lines. It is important to capture the essence. We did not initialize the database in the procedural code, although we need to do this anyway. The procedural approach requires error handling after the fact and at every point in the process. Error handling is very confusing, because It is difficult to track which of the implicit dependencies caused the error. Hardcode hides dependencies. The sources of error are not obvious. It is not obvious what your code depends on for its normal functioning.Foo
needs a Database
instance, and Database
instance needs connection parameters.Foo::bar
- now it must return the result to us. This method, in turn, delegates the Database::fetchAll
. Now all the responsibility is on him and he is trying to connect to the database and return some data. And if something goes wrong at any point ... who knows what will be returned to you and from where.Foo::bar
? OK, then give it a DB connection. What is the connection? It doesn't matter if it was a Database
instance. This is the power of dependency injection. It makes the necessary dependencies explicit.Foo
with the Database
immediately while writing the code. In the object-oriented code, you indicate that Foo
needs some Database
, but leave room for maneuver, as it may be. When you want to use Foo
, you will need to associate a specific instance of Foo
with a specific Database
instance: class Database { protected static $types = array( 'int' => array('internalType' => 'Integer', 'precision' => 0, ...), 'string' => array('internalType' => 'String', 'encoding' => 'utf-8', ...), ... ) }
Database
instances and is used in several Database
methods. Why not make the map a static property? Data never changes, but only reads. And it will save some memory, because data common to all Database
instances. Since data access occurs only inside the class, it will not create any external dependencies. Static properties should never be accessible from the outside, because these are just global variables. And we have already seen what this leads to ...Foo::bar()
, this line of code becomes associated with a specific class Foo
. This can lead to problems. class Database { ... public function __construct($host, $user, $password) { $this->connection = new PDO(...); } ... }
Database
depends on a specific class - PDO
. But PDO
is part of the platform, this is the database class provided by PHP. In any case, to work with the database will have to use some kind of API. class BloomFilter { ... public function __construct($m, $k) { ... } public static function getK($m, $n) { return ceil(($m / $n) * log(2)); } ... }
$k
argument used in the constructor. Since it must be called before creating an instance of the class, it must be static. This algorithm has no external dependencies and is unlikely to be replaced. It is used like this: $m = 10000; $n = 2000; $b = new BloomFilter($m, BloomFilter::getK($m, $n));
DateTime
class built into PHP. An instance of it can be created in two different ways: $date = new DateTime('2012-11-04'); $date = DateTime::createFromFormat('dm-Y', '04-11-2012');
DateTime
instance and in both cases the code is tied to the DateTime
class anyway. The static DateTime::createFromFormat
is an alternative object constructor that returns the same as new DateTime
, but using additional functionality. Where you can write new Class
, you can write Class::method()
. No new dependencies arise.Application
class that represents your application. It communicates with the User
class, which is the representation of the user. Which receives data from Database
. The Database
class needs a DatabaseDriver
. DatabaseDriver
needs connection options. And so on. If you simply call Application::start()
statically, which causes User::getData()
statically, which causes a database statically, and so on, in the hope that each layer will deal with its dependencies, you can get a terrible mess if something goes not this way. It is impossible to guess whether the call to Application::start()
will work, because it is not at all obvious how the internal dependencies will behave. Even worse, the only way to influence the behavior of Application::start()
is to change the source code of this class and the code of the classes it calls and the code of the classes that call those classes ... in the house that Jack built.Database::fetchAll(...)
, there are no guarantees that the connection to the database is already established or will be established. function (Database $database) { ... }
Database
instance was successfully transferred, which means that the Database
object instance was successfully created. If the Database
class is designed correctly, then you can be sure that the presence of an instance of this class means the ability to perform queries to the database. If there is no class instance, the function body will not be executed. This means that the function should not care about the state of the database; the Database
class will do it itself. This approach allows you to forget about dependencies and concentrate on solving problems.Database
can be a small wrapper class or a giant multi-layered monster with a bunch of dependencies, it can start as a small wrapper and mutate into a giant monster with time, you can inherit the Database
class and pass a descendant to the function, it's all not important for your function (Database $database)
, until the public Database
interface is changed. If your classes are properly separated from the rest of the application using dependency injection, you can test each of them using stubs instead of their dependencies. When you have tested a class enough to make sure that it works as it should, you can get rid of your head out of it, just knowing that you need to use a Database
instance to work with the database.Source: https://habr.com/ru/post/169301/
All Articles