4. Dependency Injection
From Wikipedia:
Dependency injection is a software design pattern that allows the removal of hard-coded dependencies and makes it possible to change them, whether at run-time or compile-time.
This quote makes the concept sound much more complicated than it actually is. Dependency Injection is providing a component with its dependencies either through constructor injection, method calls or the setting of properties. It is that simple.
4.1 Containers
The first thing you should understand about Dependency Injection Containers is that they are not the same thing as Dependency Injection. A container is a convenience utility that helps us implement Dependency Injection, however, they can be and often are misused to implement an anti-pattern, Service Location. Injecting a DI container as a Service Locator in to your classes arguably creates a harder dependency on the container than the dependency you are replacing. It also makes your code much less transparent and ultimately harder to test.
Most modern frameworks have their own Dependency Injection Container that allows you to wire your dependencies together through configuration. What this means in practice is that you can write application code that is as clean and de-coupled as the framework it is built on.
4.2 Basic Concept
We can demonstrate the concept with a simple, yet naive example.
Here we have a Database class that requires an adapter to speak to the database. We instantiate the adapter in the
constructor and create a hard dependency. This makes testing difficult and means the Database class is very tightly
coupled to the adapter.
1 <?php
2 namespace Database;
3
4 class Database
5 {
6 protected $adapter;
7
8 public function __construct()
9 {
10 $this->adapter = new MySqlAdapter;
11 }
12 }
13
14 class MysqlAdapter {}
This code can be refactored to use Dependency Injection and therefore loosen the dependency. Here, we inject the dependency in a constructor and use the constructor property promotion so it is available as a property across the class:
1 <?php
2 namespace Database;
3
4 class Database
5 {
6 public function __construct(protected MySqlAdapter $adapter)
7 {
8 }
9 }
10
11 class MysqlAdapter {}
Now we are giving the Database class its dependency rather than creating it itself. We could even create a method
that would accept an argument of the dependency and set it that way, or if the $adapter property was public we
could set it directly.
4.3 Further Reading
- What is Dependency Injection?
- Dependency Injection: An analogy
- Dependency Injection: Huh?
- Dependency Injection as a tool for testing
4.4 Data Filtering
Never ever (ever) trust foreign input introduced to your PHP code. Always sanitize and validate foreign input before
using it in code. The filter_var() and filter_input() functions can sanitize text and validate text formats (e.g.
email addresses).
Foreign input can be anything: $_GET and $_POST form input data, some values in the $_SERVER superglobal, and the
HTTP request body via fopen('php://input', 'r'). Remember, foreign input is not limited to form data submitted by the
user. Uploaded and downloaded files, session values, cookie data, and data from third-party web services are foreign
input, too.
While foreign data can be stored, combined, and accessed later, it is still foreign input. Every time you process, output, concatenate, or include data in your code, ask yourself if the data is filtered properly and can it be trusted.
Data may be filtered differently based on its purpose. For example, when unfiltered foreign input is passed into HTML
page output, it can execute HTML and JavaScript on your site! This is known as Cross-Site Scripting (XSS) and can be a
very dangerous attack. One way to avoid XSS is to sanitize all user-generated data before outputting it to your page by
removing HTML tags with the strip_tags() function or escaping characters with special meaning into their respective
HTML entities with the htmlentities() or htmlspecialchars() functions.
Another example is passing options to be executed on the command line. This can be extremely dangerous (and is usually
a bad idea), but you can use the built-in escapeshellarg() function to sanitize the executed command’s arguments.
One last example is accepting foreign input to determine a file to load from the filesystem. This can be exploited by
changing the filename to a file path. You need to remove "/", "../", null bytes, or other characters from the
file path so it can’t load hidden, non-public, or sensitive files.
- Learn about data filtering
- Learn about
filter_var - Learn about
filter_input - Learn about handling null bytes
Sanitization
Sanitization removes (or escapes) illegal or unsafe characters from foreign input.
For example, you should sanitize foreign input before including the input in HTML or inserting it into a raw SQL query. When you use bound parameters with PDO, it will sanitize the input for you.
Sometimes it is required to allow some safe HTML tags in the input when including it in the HTML page. This is very hard to do and many avoid it by using other more restricted formatting like Markdown or BBCode, although whitelisting libraries like HTML Purifier exist for this reason.
Unserialization
It is dangerous to unserialize() data from users or other untrusted sources. Doing so can allow malicious users to instantiate objects (with user-defined properties) whose destructors will be executed, even if the objects themselves aren’t used. You should therefore avoid unserializing untrusted data.
Use a safe, standard data interchange format such as JSON (via json_decode and json_encode) if you need to pass serialized data to the user.
Validation
Validation ensures that foreign input is what you expect. For example, you may want to validate an email address, a phone number, or age when processing a registration submission.
4.5 Web Application Security
It is very important for every PHP developer to learn the basics of web application security, which can be broken down into a handful of broad topics:
- Code-data separation.
- When data is executed as code, you get SQL Injection, Cross-Site Scripting, Local/Remote File Inclusion, etc.
- When code is printed as data, you get information leaks (source code disclosure or, in the case of C programs, enough information to bypass ASLR).
- Application logic.
- Missing authentication or authorization controls.
- Input validation.
- Operating environment.
- PHP versions.
- Third party libraries.
- The operating system.
- Cryptography weaknesses.
There are bad people ready and willing to exploit your web application. It is important that you take necessary precautions to harden your web application’s security. Luckily, the fine folks at The Open Web Application Security Project (OWASP) have compiled a comprehensive list of known security issues and methods to protect yourself against them. This is a must read for the security-conscious developer. Survive The Deep End: PHP Security by Padraic Brady is also another good web application security guide for PHP.
4.6 Error Reporting
Error logging can be useful in finding the problem spots in your application, but it can also expose information about the structure of your application to the outside world. To effectively protect your application from issues that could be caused by the output of these messages, you need to configure your server differently in development versus production (live).
Development
To show every possible error during development, configure the following settings in your php.ini:
1 display_errors = On
2 display_startup_errors = On
3 error_reporting = -1
4 log_errors = On
Passing in the value
-1will show every possible error, even when new levels and constants are added in future PHP versions. TheE_ALLconstant also behaves this way as of PHP 5.4. - php.net
The E_STRICT error level constant was introduced in 5.3.0 and is not part of E_ALL, however it became part of
E_ALL in 5.4.0. What does this mean? In terms of reporting every possible error in version 5.3 it means you must
use either -1 or E_ALL | E_STRICT.
Reporting every possible error by PHP version
- < 5.3
-1orE_ALL - 5.3
-1orE_ALL | E_STRICT - > 5.3
-1orE_ALL
Production
To hide errors on your production environment, configure your php.ini as:
1 display_errors = Off
2 display_startup_errors = Off
3 error_reporting = E_ALL
4 log_errors = On
With these settings in production, errors will still be logged to the error logs for the web server, but will not be shown to the user. For more information on these settings, see the PHP manual:
4.7 Configuration Files
When creating configuration files for your applications, best practices recommend that one of the following methods be followed:
- It is recommended that you store your configuration information where it cannot be accessed directly and pulled in via the file system.
- If you must store your configuration files in the document root, name the files with a
.phpextension. This ensures that, even if the script is accessed directly, it will not be output as plain text. - Information in configuration files should be protected accordingly, either through encryption or group/user file system permissions.
- It is a good idea to ensure that you do not commit configuration files containing sensitive information e.g. passwords or API tokens to source control.
4.8 Register Globals
NOTE: As of PHP 5.4.0 the register_globals setting has been removed and can no longer be used. This is only
included as a warning for anyone in the process of upgrading a legacy application.
When enabled, the register_globals configuration setting makes several types of variables (including ones from
$_POST, $_GET and $_REQUEST) available in the global scope of your application. This can easily lead to security
issues as your application cannot effectively tell where the data is coming from.
For example: $_GET['foo'] would be available via $foo, which can override variables that have been declared.
If you are using PHP < 5.4.0 make sure that register_globals is off.