This tool is currently proof-of-concept. Your feedback and evaluation is valuable in helping to improve it and ensure its reports are meaninful.
Please click here to complete a short survey to tell us what you think. It should take less than 5 minutes and help further this research project!
GuzzleHttp\Handler\StreamHandler
Detected issues
Issue | Method | Line number |
---|---|---|
Use of static methods | bool | 231 |
Global/Static variables | NA | 264 |
Use of static methods | createResource | 313 |
Use of static methods | addNotification | 518 |
Global/Static variables | NA | 535 |
Global/Static variables | NA | 547 |
Use of static methods | void | 553 |
Use of static methods | void | 563 |
Use of static methods | callArray | 576 |
Use of static methods | callArray | 578 |
Code
Click highlighted lines for details
<?php
namespace GuzzleHttp\Handler;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\TransferStats;
use GuzzleHttp\Utils;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
/**
* HTTP handler that uses PHP's HTTP stream wrapper.
*
* @final
*/
class StreamHandler
{
/**
* @var array
*/
private $lastHeaders = [];
/**
* Sends an HTTP request.
*
* @param RequestInterface $request Request to send.
* @param array $options Request transfer options.
*/
public function __invoke(RequestInterface $request, array $options): PromiseInterface
{
// Sleep if there is a delay specified.
if (isset($options['delay'])) {
\usleep($options['delay'] * 1000);
}
$startTime = isset($options['on_stats']) ? Utils::currentTime() : null;
try {
// Does not support the expect header.
$request = $request->withoutHeader('Expect');
// Append a content-length header if body size is zero to match
// cURL's behavior.
if (0 === $request->getBody()->getSize()) {
$request = $request->withHeader('Content-Length', '0');
}
return $this->createResponse(
$request,
$options,
$this->createStream($request, $options),
$startTime
);
} catch (\InvalidArgumentException $e) {
throw $e;
} catch (\Exception $e) {
// Determine if the error was a networking error.
$message = $e->getMessage();
// This list can probably get more comprehensive.
if (false !== \strpos($message, 'getaddrinfo') // DNS lookup failed
|| false !== \strpos($message, 'Connection refused')
|| false !== \strpos($message, "couldn't connect to host") // error on HHVM
|| false !== \strpos($message, "connection attempt failed")
) {
$e = new ConnectException($e->getMessage(), $request, $e);
} else {
$e = RequestException::wrapException($request, $e);
}
$this->invokeStats($options, $request, $startTime, null, $e);
return P\Create::rejectionFor($e);
}
}
private function invokeStats(
array $options,
RequestInterface $request,
?float $startTime,
ResponseInterface $response = null,
\Throwable $error = null
): void {
if (isset($options['on_stats'])) {
$stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []);
($options['on_stats'])($stats);
}
}
/**
* @param resource $stream
*/
private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface
{
$hdrs = $this->lastHeaders;
$this->lastHeaders = [];
try {
[$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs);
} catch (\Exception $e) {
return P\Create::rejectionFor(
new RequestException('An error was encountered while creating the response', $request, null, $e)
);
}
[$stream, $headers] = $this->checkDecode($options, $headers, $stream);
$stream = Psr7\Utils::streamFor($stream);
$sink = $stream;
if (\strcasecmp('HEAD', $request->getMethod())) {
$sink = $this->createSink($stream, $options);
}
try {
$response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
} catch (\Exception $e) {
return P\Create::rejectionFor(
new RequestException('An error was encountered while creating the response', $request, null, $e)
);
}
if (isset($options['on_headers'])) {
try {
$options['on_headers']($response);
} catch (\Exception $e) {
return P\Create::rejectionFor(
new RequestException('An error was encountered during the on_headers event', $request, $response, $e)
);
}
}
// Do not drain when the request is a HEAD request because they have
// no body.
if ($sink !== $stream) {
$this->drain($stream, $sink, $response->getHeaderLine('Content-Length'));
}
$this->invokeStats($options, $request, $startTime, $response, null);
return new FulfilledPromise($response);
}
private function createSink(StreamInterface $stream, array $options): StreamInterface
{
if (!empty($options['stream'])) {
return $stream;
}
$sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+');
return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink);
}
/**
* @param resource $stream
*/
private function checkDecode(array $options, array $headers, $stream): array
{
// Automatically decode responses when instructed.
if (!empty($options['decode_content'])) {
$normalizedKeys = Utils::normalizeHeaderKeys($headers);
if (isset($normalizedKeys['content-encoding'])) {
$encoding = $headers[$normalizedKeys['content-encoding']];
if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
$stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream));
$headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']];
// Remove content-encoding header
unset($headers[$normalizedKeys['content-encoding']]);
// Fix content-length header
if (isset($normalizedKeys['content-length'])) {
$headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']];
$length = (int) $stream->getSize();
if ($length === 0) {
unset($headers[$normalizedKeys['content-length']]);
} else {
$headers[$normalizedKeys['content-length']] = [$length];
}
}
}
}
}
return [$stream, $headers];
}
/**
* Drains the source stream into the "sink" client option.
*
* @param string $contentLength Header specifying the amount of
* data to read.
*
* @throws \RuntimeException when the sink option is invalid.
*/
private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength): StreamInterface
{
// If a content-length header is provided, then stop reading once
// that number of bytes has been read. This can prevent infinitely
// reading from a stream when dealing with servers that do not honor
// Connection: Close headers.
Psr7\Utils::copyToStream(
$source,
$sink,
(\strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
);
$sink->seek(0);
$source->close();
return $sink;
}
/**
* Create a resource and check to ensure it was created successfully
*
* @param callable $callback Callable that returns stream resource
*
* @return resource
*
* @throws \RuntimeException on error
*/
private function createResource(callable $callback)
{
$errors = [];
Static methods
Summary of issues
- Tight Coupling
- Hidden dependencies
- Global state (if also using static variables)
Tight Coupling
Use of static methods always reduces flexibility by introducing tight coupling[1]. A static method tightly couples the calling code to the specific class the method exists in.
function totalAbs(double value, double value2) {
return abs(value) + abs(value2);
}Here, the method
totalAbs
has a dependency on theMath
class and the.abs()
method will always be called. Although for testing purposes this may not be a problem, the coupling reduces flexibility because thetotal
method can only work with doubles/integers, as that's all theMath.abs()
function can use. Although type coercion will allow the use of any primitive numeric type, these types have limitations. It's impossible to use another class such asBigInteger
or a class for dealing with greater precision decimals or even alternative numbering systems such as Roman numerals.The totalAbs function takes two doubles and converts them to their absolute values before adding them. This is inflexible because it only works with doubles. It's tied to doubles because that's what the
Math.abs()
static method requires. If, instead, using OOP an interface was created to handle any number that had this method:interface Numeric {
public function abs(): Numeric;
}It would then be possible to rewrite the
totalAbs
method to work with any kind of number:function totalAbs(Numeric value, Numeric value): Numeric {
return value.abs() + value2.abs();
}
By removing the static method and using an instance method in its place the
totalAbs
method is now agnostic about the type of number it is dealing with. It could be called with any of the following (assuming they implement theNumeric
interface)
totalAbs(new Integer(4), new Integer(-53));
totalAbs(new Double(34.4), new Integer(-2));
totalAbs(new BigInteger('123445454564765739878989343225778'), new Integer(2343));
totalAbs(new RomanNumeral('VII'), new RomanNumeral('CXV'));
Making the method reusable in a way that it wasn't when static methods were being used. By changing the static methods to instance methods, flexibility has been enhanced as the method can be used with any numeric type, not just numeric types that are supported by the
Math.abs()
method.Broken encapsulation
Static methods also break encapsulation. Encapsulation is defined by Rogers[2] as:
the bundling of data with the methods that operate on that dataBy passing the numeric value into the
abs
method, the data being operated on is being separated from the methods that operate on it, breaking encapsulation. Instead usingnum.abs()
the data is encapsulated in thenum
instance and its type is not visible or relevant to the outside world.abs()
will work on the data and work regardless ofnum
's type, providing it implements theabs
method.This is a simple example, but applies to all static methods. Use of polymorphic instance methods that work on encapsulated data will always be more flexible than static method calls which can only ever deal with specific pre-defined types.
Further reading
- What is tight coupling?
- Why Static is Bad and How to Avoid It
- Static Methods Will Shock You
- Flaw: Brittle Global State & Singletons
- Static Methods are Death to Testability
Exceptions
The only exception to this rule is when a static method is used for object creation in place of the
new
keyword[3]. This is because thenew
keyword is already a static call. However, even here a non-static factory is often preferable for testing purposes[4][5].References
- Popov, N. (2014) Don't be STUPID: GRASP SOLID! [online]. Available from: https://nikic.github.io/2011/12/27/Dont-be-STUPID-GRASP-SOLID.html
- Rogers, P. (2001) Encapsulation is not information hiding [online]. Available from: http://www.javaworld.com/article/2075271/core-java/encapsulation-is-not-information-hiding.html
- Sonmez, J. (2010) Static Methods Will Shock You [online]. Available from: http://simpleprogrammer.com/2010/01/29/static-methods-will-shock-you/
- Hevery, M. (2008) Static Methods are Death to Testability [online]. Available from: http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/
- Butler, T. (2013) Are Static Methods/Variables bad practice? [online]. Available from: https://r.je/static-methods-bad-practice.html
$errors[] = [
'message' => $msg,
'file' => $file,
'line' => $line
];
return true;
});
try {
$resource = $callback();
} finally {
\restore_error_handler();
}
if (!$resource) {
$message = 'Error creating resource: ';
foreach ($errors as $err) {
foreach ($err as $key => $value) {
$message .= "[$key] $value" . \PHP_EOL;
}
}
throw new \RuntimeException(\trim($message));
}
return $resource;
}
/**
* @return resource
*/
private function createStream(RequestInterface $request, array $options)
{
Global variables
Note: A future update will differentiate between
private static
variables andpublic static
orglobal
variables asprivate static
variables do not cause as much of a problem.Summary
- Hidden dependencies
- Broken encapsulation
- One component can accidentally overwrite data required by another component (action at a distance)
- You can only every have one copy of the variable
- Adding code requires knowing exactly what variables are already in use
- When working in teams, name clashes can be easily introduced
- Global state makes it difficult to reuse the code. E.g. having two files open at the same time would require writing the code twice, three times for three files, etc.
Background
The identification of global variables as a bad practice dates as far back at least as far back as 1973[1] and are one of the most widespread and well known bad practices related to flexibility. This is likely due to being available in almost every programming language, ease of use and speed to learn. They also cause severe problems in code and it's very easy to get caught out by using them, even in a small application.
Global vairables are widely labelled "bad practice" and have been for some time, for example back in 1999 Kernighan wrote:
Avoid global variables; wherever possible it is better to pass references to all data through function argumentsKernighan[2]
And Hevery[3] states:
I hope that by now most developers agree that global state should be treated like GOTO.This attitude is widespread and Sayfan[4] sums up the problem:
Whenever shared mutable state is involved, it is easy for components to step on each other's toes.Although "global variables are bad" is a common thing to here, for novice developers it's not immediately obvious why this is. However, the reasons have been covered frequently by developers of varying prominence. While writing about desiging the Eiffel programming language, [5] stated several problems with global variables:
Since global variables are shared by different modules, they make each of these modules more difficult to understand separately, diminishing readability and hence hampering maintenance.As global variables constitute a form of undercover dependency between modules, they are a major obstacle to software evolution, since they make it harder to modify a module without impacting others.They are a major source of nasty errors. Through a global variable, an error in a module may propagate to many others. As a result, the manifestation of the error may be quite remote from its cause in the software architecture, making it very hard to trace down errors and correct them. This problem is particularly serious in environments where incorrect array references may pollute other data.Action at a distance
This problem is commonly referred to as action at a distance and described by Hevery[6] as:
Spooky Action at a Distance is when we run one thing that we believe is isolated (since we did not pass any references in) but unexpected interactions and state changes happen in distant locations of the system which we did not tell the object about. This can only happen via global state.Broken encapsulation
The biggest issue with global variables (even private static variables) is that they break encapsulation. A class no longer has its own state and one instance of a class can affect the state of another. Although private static variables are the by far the least worst type of global variables they should still be refactored out where possible.
> Encapsulation refers to the bundling of data with the methods that operate on that dataBy making the data globally accessible, encapsulation has been lost. Any part of the program has access to the data and can modify it. Even when using private static variables, each instance no longer has control of its own sate.
Tight coupling
Global variables introduce tight coupling. In Object Oriented Programming an object should be self-contained[7][8]. If a class depends on a global (or static) variable, then moving the class to a different project requires defining the required global variables in the new project. Private static variables do not not introduce additional coupling.
Name Clashes
Global variables can introduce name clashes:
everywhere in the program, you would have to keep track of the names of all the variables declared anywhere else in the program, so that you didn't accidentally re-use one.Summit[9]
The problem of name clashes is magnified by the size of a team. If two people are working on a piece of software and both use global variables, it's possible they'll write some code using the same variable names. During execution this might cause the two peices of code to interfere with each other.
Examples
As a very crude example, imagine the following:
function getUser($id) {
$connection = Database::$connection;
$connection->query('SELET * FROM user ...')
}
This code assumes that
Database::$connection
has been set correctly and not overwritten. If any part of the application accidently runs the codeDatabase::$connection = null;
(or sets it to anything other than a database connection) then the code will fail. This is due to broken encapsulation and action and a distance.Anything in the code is able to change the property and cause unexpected behaviour when further methods are called on the instance.
This can also happen with private static properties:
class FileReadWrite {
private static $fileName;
public function __construct(string $file) {
self::$fileName = $file;
}
public function read() {
return file_get_contents(self::$fileName);
}
public function write(string $data) {
file_put_contents(self::$fileName, $data);
}
}
//Works as expected:
$file = new FileReadWrite('./one.txt');
$file->write('data');
//cause a problem
$file1 = new FileReadWrite('./one.txt');
$file2 = new FileReadWrite('./two.txt');
$file1->write('data1');
$file2->write('data2');This causes a problem because there is a global variable storing the file name. Assuming a class requires only one value of a variable across the whole application always limits flexibility. There are occasionally practical reasons for this such as keeping track of and limiting the number of open files/connections but flexibility is always reduced. Even in these practical exceptions, it introduces a new issue of separation of concerns: Should the class be concerned with the number of open connections throughout the application or should that be managed at an application level rather than a class level?
Although this is a contrived example, the same kind of bugs can occur any time a static variable is used. If it's set by one instance and read by another then unexpected changes can cause bugs.
Further reading
- What is tight coupling?
- Why Static is Bad and How to Avoid It
- Flaw: Brittle Global State & Singletons
- What's Wrong With Global Variables
References
- Wulf, W., Shaw, M. (1973) Global varaibles considered harmful. ACM SIGPLAN Notices , pp.28-34.
- Kernighan, B. (1999) The Practice of Programming ISBN: 978-0201615869. Addison Wesley.
- Hevery, M. (2008) Top 10 things which make your code hard to test [online]. Available from: http://misko.hevery.com/2008/07/30/top-10-things-which-make-your-code-hard-to-test/
- Sayfan, M. (n.d.) Avoid Global Variables, Environment Variables, and Singletons [online]. Available from: https://sites.google.com/site/michaelsafyan/software-engineering/avoid-global-variables-environment-variables-and-singletons
- Meyer, B. (1988) Bidding farewell to globals. JOOP(Journal of Object-Oriented Programming) , pp.73-77.
- Hevery, M. (2008) Brittle Global State & Singletons [online]. Available from: http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/
- Yaiser, M. (2011) Object-oriented programming concepts: Objects and classes [online]. Available from: http://www.adobe.com/devnet/actionscript/learning/oop-concepts/objects-and-classes.html
- Caromel, D. (1993) Toward a method of object-oriented concurrent programming. Communications of the ACM , pp.90-102.
- Summit, S. (1997) Visibility and Lifetime (Global Variables, etc.) [online]. Available from: https://www.eskimo.com/~scs/cclass/notes/sx4b.html
if (!$methods) {
$methods = \array_flip(\get_class_methods(__CLASS__));
}
// HTTP/1.1 streams using the PHP stream wrapper require a
// Connection: close header
if ($request->getProtocolVersion() == '1.1'
&& !$request->hasHeader('Connection')
) {
$request = $request->withHeader('Connection', 'close');
}
// Ensure SSL is verified by default
if (!isset($options['verify'])) {
$options['verify'] = true;
}
$params = [];
$context = $this->getDefaultContext($request);
if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) {
throw new \InvalidArgumentException('on_headers must be callable');
}
if (!empty($options)) {
foreach ($options as $key => $value) {
$method = "add_{$key}";
if (isset($methods[$method])) {
$this->{$method}($request, $context, $value, $params);
}
}
}
if (isset($options['stream_context'])) {
if (!\is_array($options['stream_context'])) {
throw new \InvalidArgumentException('stream_context must be an array');
}
$context = \array_replace_recursive($context, $options['stream_context']);
}
// Microsoft NTLM authentication only supported with curl handler
if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) {
throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
}
$uri = $this->resolveHost($request, $options);
$contextResource = $this->createResource(
Static methods
Summary of issues
- Tight Coupling
- Hidden dependencies
- Global state (if also using static variables)
Tight Coupling
Use of static methods always reduces flexibility by introducing tight coupling[1]. A static method tightly couples the calling code to the specific class the method exists in.
function totalAbs(double value, double value2) {
return abs(value) + abs(value2);
}Here, the method
totalAbs
has a dependency on theMath
class and the.abs()
method will always be called. Although for testing purposes this may not be a problem, the coupling reduces flexibility because thetotal
method can only work with doubles/integers, as that's all theMath.abs()
function can use. Although type coercion will allow the use of any primitive numeric type, these types have limitations. It's impossible to use another class such asBigInteger
or a class for dealing with greater precision decimals or even alternative numbering systems such as Roman numerals.The totalAbs function takes two doubles and converts them to their absolute values before adding them. This is inflexible because it only works with doubles. It's tied to doubles because that's what the
Math.abs()
static method requires. If, instead, using OOP an interface was created to handle any number that had this method:interface Numeric {
public function abs(): Numeric;
}It would then be possible to rewrite the
totalAbs
method to work with any kind of number:function totalAbs(Numeric value, Numeric value): Numeric {
return value.abs() + value2.abs();
}
By removing the static method and using an instance method in its place the
totalAbs
method is now agnostic about the type of number it is dealing with. It could be called with any of the following (assuming they implement theNumeric
interface)
totalAbs(new Integer(4), new Integer(-53));
totalAbs(new Double(34.4), new Integer(-2));
totalAbs(new BigInteger('123445454564765739878989343225778'), new Integer(2343));
totalAbs(new RomanNumeral('VII'), new RomanNumeral('CXV'));
Making the method reusable in a way that it wasn't when static methods were being used. By changing the static methods to instance methods, flexibility has been enhanced as the method can be used with any numeric type, not just numeric types that are supported by the
Math.abs()
method.Broken encapsulation
Static methods also break encapsulation. Encapsulation is defined by Rogers[2] as:
the bundling of data with the methods that operate on that dataBy passing the numeric value into the
abs
method, the data being operated on is being separated from the methods that operate on it, breaking encapsulation. Instead usingnum.abs()
the data is encapsulated in thenum
instance and its type is not visible or relevant to the outside world.abs()
will work on the data and work regardless ofnum
's type, providing it implements theabs
method.This is a simple example, but applies to all static methods. Use of polymorphic instance methods that work on encapsulated data will always be more flexible than static method calls which can only ever deal with specific pre-defined types.
Further reading
- What is tight coupling?
- Why Static is Bad and How to Avoid It
- Static Methods Will Shock You
- Flaw: Brittle Global State & Singletons
- Static Methods are Death to Testability
Exceptions
The only exception to this rule is when a static method is used for object creation in place of the
new
keyword[3]. This is because thenew
keyword is already a static call. However, even here a non-static factory is often preferable for testing purposes[4][5].References
- Popov, N. (2014) Don't be STUPID: GRASP SOLID! [online]. Available from: https://nikic.github.io/2011/12/27/Dont-be-STUPID-GRASP-SOLID.html
- Rogers, P. (2001) Encapsulation is not information hiding [online]. Available from: http://www.javaworld.com/article/2075271/core-java/encapsulation-is-not-information-hiding.html
- Sonmez, J. (2010) Static Methods Will Shock You [online]. Available from: http://simpleprogrammer.com/2010/01/29/static-methods-will-shock-you/
- Hevery, M. (2008) Static Methods are Death to Testability [online]. Available from: http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/
- Butler, T. (2013) Are Static Methods/Variables bad practice? [online]. Available from: https://r.je/static-methods-bad-practice.html
return \stream_context_create($context, $params);
}
);
return $this->createResource(
function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) {
$resource = @\fopen((string) $uri, 'r', false, $contextResource);
$this->lastHeaders = $http_response_header;
if (false === $resource) {
throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context);
}
if (isset($options['read_timeout'])) {
$readTimeout = $options['read_timeout'];
$sec = (int) $readTimeout;
$usec = ($readTimeout - $sec) * 100000;
\stream_set_timeout($resource, $sec, $usec);
}
return $resource;
}
);
}
private function resolveHost(RequestInterface $request, array $options): UriInterface
{
$uri = $request->getUri();
if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) {
if ('v4' === $options['force_ip_resolve']) {
$records = \dns_get_record($uri->getHost(), \DNS_A);
if (false === $records || !isset($records[0]['ip'])) {
throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
}
return $uri->withHost($records[0]['ip']);
}
if ('v6' === $options['force_ip_resolve']) {
$records = \dns_get_record($uri->getHost(), \DNS_AAAA);
if (false === $records || !isset($records[0]['ipv6'])) {
throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
}
return $uri->withHost('[' . $records[0]['ipv6'] . ']');
}
}
return $uri;
}
private function getDefaultContext(RequestInterface $request): array
{
$headers = '';
foreach ($request->getHeaders() as $name => $value) {
foreach ($value as $val) {
$headers .= "$name: $val\r\n";
}
}
$context = [
'http' => [
'method' => $request->getMethod(),
'header' => $headers,
'protocol_version' => $request->getProtocolVersion(),
'ignore_errors' => true,
'follow_location' => 0,
],
];
$body = (string) $request->getBody();
if (!empty($body)) {
$context['http']['content'] = $body;
// Prevent the HTTP handler from adding a Content-Type header.
if (!$request->hasHeader('Content-Type')) {
$context['http']['header'] .= "Content-Type:\r\n";
}
}
$context['http']['header'] = \rtrim($context['http']['header']);
return $context;
}
/**
* @param mixed $value as passed via Request transfer options.
*/
private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): void
{
$uri = null;
if (!\is_array($value)) {
$uri = $value;
} else {
$scheme = $request->getUri()->getScheme();
if (isset($value[$scheme])) {
if (!isset($value['no']) || !Utils::isHostInNoProxy($request->getUri()->getHost(), $value['no'])) {
$uri = $value[$scheme];
}
}
}
if (!$uri) {
return;
}
$parsed = $this->parse_proxy($uri);
$options['http']['proxy'] = $parsed['proxy'];
if ($parsed['auth']) {
if (!isset($options['http']['header'])) {
$options['http']['header'] = [];
}
$options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}";
}
}
/**
* Parses the given proxy URL to make it compatible with the format PHP's stream context expects.
*/
private function parse_proxy(string $url): array
{
$parsed = \parse_url($url);
if ($parsed !== false && isset($parsed['scheme']) && $parsed['scheme'] === 'http') {
if (isset($parsed['host']) && isset($parsed['port'])) {
$auth = null;
if (isset($parsed['user']) && isset($parsed['pass'])) {
$auth = \base64_encode("{$parsed['user']}:{$parsed['pass']}");
}
return [
'proxy' => "tcp://{$parsed['host']}:{$parsed['port']}",
'auth' => $auth ? "Basic {$auth}" : null,
];
}
}
// Return proxy as-is.
return [
'proxy' => $url,
'auth' => null,
];
}
/**
* @param mixed $value as passed via Request transfer options.
*/
private function add_timeout(RequestInterface $request, array &$options, $value, array &$params): void
{
if ($value > 0) {
$options['http']['timeout'] = $value;
}
}
/**
* @param mixed $value as passed via Request transfer options.
*/
private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void
{
if ($value === false) {
$options['ssl']['verify_peer'] = false;
$options['ssl']['verify_peer_name'] = false;
return;
}
if (\is_string($value)) {
$options['ssl']['cafile'] = $value;
if (!\file_exists($value)) {
throw new \RuntimeException("SSL CA bundle not found: $value");
}
} elseif ($value !== true) {
throw new \InvalidArgumentException('Invalid verify request option');
}
$options['ssl']['verify_peer'] = true;
$options['ssl']['verify_peer_name'] = true;
$options['ssl']['allow_self_signed'] = false;
}
/**
* @param mixed $value as passed via Request transfer options.
*/
private function add_cert(RequestInterface $request, array &$options, $value, array &$params): void
{
if (\is_array($value)) {
$options['ssl']['passphrase'] = $value[1];
$value = $value[0];
}
if (!\file_exists($value)) {
throw new \RuntimeException("SSL certificate not found: {$value}");
}
$options['ssl']['local_cert'] = $value;
}
/**
* @param mixed $value as passed via Request transfer options.
*/
private function add_progress(RequestInterface $request, array &$options, $value, array &$params): void
{
self::addNotification(
$params,
Static methods
Summary of issues
- Tight Coupling
- Hidden dependencies
- Global state (if also using static variables)
Tight Coupling
Use of static methods always reduces flexibility by introducing tight coupling[1]. A static method tightly couples the calling code to the specific class the method exists in.
function totalAbs(double value, double value2) {
return abs(value) + abs(value2);
}Here, the method
totalAbs
has a dependency on theMath
class and the.abs()
method will always be called. Although for testing purposes this may not be a problem, the coupling reduces flexibility because thetotal
method can only work with doubles/integers, as that's all theMath.abs()
function can use. Although type coercion will allow the use of any primitive numeric type, these types have limitations. It's impossible to use another class such asBigInteger
or a class for dealing with greater precision decimals or even alternative numbering systems such as Roman numerals.The totalAbs function takes two doubles and converts them to their absolute values before adding them. This is inflexible because it only works with doubles. It's tied to doubles because that's what the
Math.abs()
static method requires. If, instead, using OOP an interface was created to handle any number that had this method:interface Numeric {
public function abs(): Numeric;
}It would then be possible to rewrite the
totalAbs
method to work with any kind of number:function totalAbs(Numeric value, Numeric value): Numeric {
return value.abs() + value2.abs();
}
By removing the static method and using an instance method in its place the
totalAbs
method is now agnostic about the type of number it is dealing with. It could be called with any of the following (assuming they implement theNumeric
interface)
totalAbs(new Integer(4), new Integer(-53));
totalAbs(new Double(34.4), new Integer(-2));
totalAbs(new BigInteger('123445454564765739878989343225778'), new Integer(2343));
totalAbs(new RomanNumeral('VII'), new RomanNumeral('CXV'));
Making the method reusable in a way that it wasn't when static methods were being used. By changing the static methods to instance methods, flexibility has been enhanced as the method can be used with any numeric type, not just numeric types that are supported by the
Math.abs()
method.Broken encapsulation
Static methods also break encapsulation. Encapsulation is defined by Rogers[2] as:
the bundling of data with the methods that operate on that dataBy passing the numeric value into the
abs
method, the data being operated on is being separated from the methods that operate on it, breaking encapsulation. Instead usingnum.abs()
the data is encapsulated in thenum
instance and its type is not visible or relevant to the outside world.abs()
will work on the data and work regardless ofnum
's type, providing it implements theabs
method.This is a simple example, but applies to all static methods. Use of polymorphic instance methods that work on encapsulated data will always be more flexible than static method calls which can only ever deal with specific pre-defined types.
Further reading
- What is tight coupling?
- Why Static is Bad and How to Avoid It
- Static Methods Will Shock You
- Flaw: Brittle Global State & Singletons
- Static Methods are Death to Testability
Exceptions
The only exception to this rule is when a static method is used for object creation in place of the
new
keyword[3]. This is because thenew
keyword is already a static call. However, even here a non-static factory is often preferable for testing purposes[4][5].References
- Popov, N. (2014) Don't be STUPID: GRASP SOLID! [online]. Available from: https://nikic.github.io/2011/12/27/Dont-be-STUPID-GRASP-SOLID.html
- Rogers, P. (2001) Encapsulation is not information hiding [online]. Available from: http://www.javaworld.com/article/2075271/core-java/encapsulation-is-not-information-hiding.html
- Sonmez, J. (2010) Static Methods Will Shock You [online]. Available from: http://simpleprogrammer.com/2010/01/29/static-methods-will-shock-you/
- Hevery, M. (2008) Static Methods are Death to Testability [online]. Available from: http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/
- Butler, T. (2013) Are Static Methods/Variables bad practice? [online]. Available from: https://r.je/static-methods-bad-practice.html
if ($code == \STREAM_NOTIFY_PROGRESS) {
$value($total, $transferred, null, null);
}
}
);
}
/**
* @param mixed $value as passed via Request transfer options.
*/
private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void
{
if ($value === false) {
return;
}
Global variables
Note: A future update will differentiate between
private static
variables andpublic static
orglobal
variables asprivate static
variables do not cause as much of a problem.Summary
- Hidden dependencies
- Broken encapsulation
- One component can accidentally overwrite data required by another component (action at a distance)
- You can only every have one copy of the variable
- Adding code requires knowing exactly what variables are already in use
- When working in teams, name clashes can be easily introduced
- Global state makes it difficult to reuse the code. E.g. having two files open at the same time would require writing the code twice, three times for three files, etc.
Background
The identification of global variables as a bad practice dates as far back at least as far back as 1973[1] and are one of the most widespread and well known bad practices related to flexibility. This is likely due to being available in almost every programming language, ease of use and speed to learn. They also cause severe problems in code and it's very easy to get caught out by using them, even in a small application.
Global vairables are widely labelled "bad practice" and have been for some time, for example back in 1999 Kernighan wrote:
Avoid global variables; wherever possible it is better to pass references to all data through function argumentsKernighan[2]
And Hevery[3] states:
I hope that by now most developers agree that global state should be treated like GOTO.This attitude is widespread and Sayfan[4] sums up the problem:
Whenever shared mutable state is involved, it is easy for components to step on each other's toes.Although "global variables are bad" is a common thing to here, for novice developers it's not immediately obvious why this is. However, the reasons have been covered frequently by developers of varying prominence. While writing about desiging the Eiffel programming language, [5] stated several problems with global variables:
Since global variables are shared by different modules, they make each of these modules more difficult to understand separately, diminishing readability and hence hampering maintenance.As global variables constitute a form of undercover dependency between modules, they are a major obstacle to software evolution, since they make it harder to modify a module without impacting others.They are a major source of nasty errors. Through a global variable, an error in a module may propagate to many others. As a result, the manifestation of the error may be quite remote from its cause in the software architecture, making it very hard to trace down errors and correct them. This problem is particularly serious in environments where incorrect array references may pollute other data.Action at a distance
This problem is commonly referred to as action at a distance and described by Hevery[6] as:
Spooky Action at a Distance is when we run one thing that we believe is isolated (since we did not pass any references in) but unexpected interactions and state changes happen in distant locations of the system which we did not tell the object about. This can only happen via global state.Broken encapsulation
The biggest issue with global variables (even private static variables) is that they break encapsulation. A class no longer has its own state and one instance of a class can affect the state of another. Although private static variables are the by far the least worst type of global variables they should still be refactored out where possible.
> Encapsulation refers to the bundling of data with the methods that operate on that dataBy making the data globally accessible, encapsulation has been lost. Any part of the program has access to the data and can modify it. Even when using private static variables, each instance no longer has control of its own sate.
Tight coupling
Global variables introduce tight coupling. In Object Oriented Programming an object should be self-contained[7][8]. If a class depends on a global (or static) variable, then moving the class to a different project requires defining the required global variables in the new project. Private static variables do not not introduce additional coupling.
Name Clashes
Global variables can introduce name clashes:
everywhere in the program, you would have to keep track of the names of all the variables declared anywhere else in the program, so that you didn't accidentally re-use one.Summit[9]
The problem of name clashes is magnified by the size of a team. If two people are working on a piece of software and both use global variables, it's possible they'll write some code using the same variable names. During execution this might cause the two peices of code to interfere with each other.
Examples
As a very crude example, imagine the following:
function getUser($id) {
$connection = Database::$connection;
$connection->query('SELET * FROM user ...')
}
This code assumes that
Database::$connection
has been set correctly and not overwritten. If any part of the application accidently runs the codeDatabase::$connection = null;
(or sets it to anything other than a database connection) then the code will fail. This is due to broken encapsulation and action and a distance.Anything in the code is able to change the property and cause unexpected behaviour when further methods are called on the instance.
This can also happen with private static properties:
class FileReadWrite {
private static $fileName;
public function __construct(string $file) {
self::$fileName = $file;
}
public function read() {
return file_get_contents(self::$fileName);
}
public function write(string $data) {
file_put_contents(self::$fileName, $data);
}
}
//Works as expected:
$file = new FileReadWrite('./one.txt');
$file->write('data');
//cause a problem
$file1 = new FileReadWrite('./one.txt');
$file2 = new FileReadWrite('./two.txt');
$file1->write('data1');
$file2->write('data2');This causes a problem because there is a global variable storing the file name. Assuming a class requires only one value of a variable across the whole application always limits flexibility. There are occasionally practical reasons for this such as keeping track of and limiting the number of open files/connections but flexibility is always reduced. Even in these practical exceptions, it introduces a new issue of separation of concerns: Should the class be concerned with the number of open connections throughout the application or should that be managed at an application level rather than a class level?
Although this is a contrived example, the same kind of bugs can occur any time a static variable is used. If it's set by one instance and read by another then unexpected changes can cause bugs.
Further reading
- What is tight coupling?
- Why Static is Bad and How to Avoid It
- Flaw: Brittle Global State & Singletons
- What's Wrong With Global Variables
References
- Wulf, W., Shaw, M. (1973) Global varaibles considered harmful. ACM SIGPLAN Notices , pp.28-34.
- Kernighan, B. (1999) The Practice of Programming ISBN: 978-0201615869. Addison Wesley.
- Hevery, M. (2008) Top 10 things which make your code hard to test [online]. Available from: http://misko.hevery.com/2008/07/30/top-10-things-which-make-your-code-hard-to-test/
- Sayfan, M. (n.d.) Avoid Global Variables, Environment Variables, and Singletons [online]. Available from: https://sites.google.com/site/michaelsafyan/software-engineering/avoid-global-variables-environment-variables-and-singletons
- Meyer, B. (1988) Bidding farewell to globals. JOOP(Journal of Object-Oriented Programming) , pp.73-77.
- Hevery, M. (2008) Brittle Global State & Singletons [online]. Available from: http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/
- Yaiser, M. (2011) Object-oriented programming concepts: Objects and classes [online]. Available from: http://www.adobe.com/devnet/actionscript/learning/oop-concepts/objects-and-classes.html
- Caromel, D. (1993) Toward a method of object-oriented concurrent programming. Communications of the ACM , pp.90-102.
- Summit, S. (1997) Visibility and Lifetime (Global Variables, etc.) [online]. Available from: https://www.eskimo.com/~scs/cclass/notes/sx4b.html
\STREAM_NOTIFY_CONNECT => 'CONNECT',
\STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
\STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
\STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
\STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
\STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
\STREAM_NOTIFY_PROGRESS => 'PROGRESS',
\STREAM_NOTIFY_FAILURE => 'FAILURE',
\STREAM_NOTIFY_COMPLETED => 'COMPLETED',
\STREAM_NOTIFY_RESOLVE => 'RESOLVE',
];
Global variables
Note: A future update will differentiate between
private static
variables andpublic static
orglobal
variables asprivate static
variables do not cause as much of a problem.Summary
- Hidden dependencies
- Broken encapsulation
- One component can accidentally overwrite data required by another component (action at a distance)
- You can only every have one copy of the variable
- Adding code requires knowing exactly what variables are already in use
- When working in teams, name clashes can be easily introduced
- Global state makes it difficult to reuse the code. E.g. having two files open at the same time would require writing the code twice, three times for three files, etc.
Background
The identification of global variables as a bad practice dates as far back at least as far back as 1973[1] and are one of the most widespread and well known bad practices related to flexibility. This is likely due to being available in almost every programming language, ease of use and speed to learn. They also cause severe problems in code and it's very easy to get caught out by using them, even in a small application.
Global vairables are widely labelled "bad practice" and have been for some time, for example back in 1999 Kernighan wrote:
Avoid global variables; wherever possible it is better to pass references to all data through function argumentsKernighan[2]
And Hevery[3] states:
I hope that by now most developers agree that global state should be treated like GOTO.This attitude is widespread and Sayfan[4] sums up the problem:
Whenever shared mutable state is involved, it is easy for components to step on each other's toes.Although "global variables are bad" is a common thing to here, for novice developers it's not immediately obvious why this is. However, the reasons have been covered frequently by developers of varying prominence. While writing about desiging the Eiffel programming language, [5] stated several problems with global variables:
Since global variables are shared by different modules, they make each of these modules more difficult to understand separately, diminishing readability and hence hampering maintenance.As global variables constitute a form of undercover dependency between modules, they are a major obstacle to software evolution, since they make it harder to modify a module without impacting others.They are a major source of nasty errors. Through a global variable, an error in a module may propagate to many others. As a result, the manifestation of the error may be quite remote from its cause in the software architecture, making it very hard to trace down errors and correct them. This problem is particularly serious in environments where incorrect array references may pollute other data.Action at a distance
This problem is commonly referred to as action at a distance and described by Hevery[6] as:
Spooky Action at a Distance is when we run one thing that we believe is isolated (since we did not pass any references in) but unexpected interactions and state changes happen in distant locations of the system which we did not tell the object about. This can only happen via global state.Broken encapsulation
The biggest issue with global variables (even private static variables) is that they break encapsulation. A class no longer has its own state and one instance of a class can affect the state of another. Although private static variables are the by far the least worst type of global variables they should still be refactored out where possible.
> Encapsulation refers to the bundling of data with the methods that operate on that dataBy making the data globally accessible, encapsulation has been lost. Any part of the program has access to the data and can modify it. Even when using private static variables, each instance no longer has control of its own sate.
Tight coupling
Global variables introduce tight coupling. In Object Oriented Programming an object should be self-contained[7][8]. If a class depends on a global (or static) variable, then moving the class to a different project requires defining the required global variables in the new project. Private static variables do not not introduce additional coupling.
Name Clashes
Global variables can introduce name clashes:
everywhere in the program, you would have to keep track of the names of all the variables declared anywhere else in the program, so that you didn't accidentally re-use one.Summit[9]
The problem of name clashes is magnified by the size of a team. If two people are working on a piece of software and both use global variables, it's possible they'll write some code using the same variable names. During execution this might cause the two peices of code to interfere with each other.
Examples
As a very crude example, imagine the following:
function getUser($id) {
$connection = Database::$connection;
$connection->query('SELET * FROM user ...')
}
This code assumes that
Database::$connection
has been set correctly and not overwritten. If any part of the application accidently runs the codeDatabase::$connection = null;
(or sets it to anything other than a database connection) then the code will fail. This is due to broken encapsulation and action and a distance.Anything in the code is able to change the property and cause unexpected behaviour when further methods are called on the instance.
This can also happen with private static properties:
class FileReadWrite {
private static $fileName;
public function __construct(string $file) {
self::$fileName = $file;
}
public function read() {
return file_get_contents(self::$fileName);
}
public function write(string $data) {
file_put_contents(self::$fileName, $data);
}
}
//Works as expected:
$file = new FileReadWrite('./one.txt');
$file->write('data');
//cause a problem
$file1 = new FileReadWrite('./one.txt');
$file2 = new FileReadWrite('./two.txt');
$file1->write('data1');
$file2->write('data2');This causes a problem because there is a global variable storing the file name. Assuming a class requires only one value of a variable across the whole application always limits flexibility. There are occasionally practical reasons for this such as keeping track of and limiting the number of open files/connections but flexibility is always reduced. Even in these practical exceptions, it introduces a new issue of separation of concerns: Should the class be concerned with the number of open connections throughout the application or should that be managed at an application level rather than a class level?
Although this is a contrived example, the same kind of bugs can occur any time a static variable is used. If it's set by one instance and read by another then unexpected changes can cause bugs.
Further reading
- What is tight coupling?
- Why Static is Bad and How to Avoid It
- Flaw: Brittle Global State & Singletons
- What's Wrong With Global Variables
References
- Wulf, W., Shaw, M. (1973) Global varaibles considered harmful. ACM SIGPLAN Notices , pp.28-34.
- Kernighan, B. (1999) The Practice of Programming ISBN: 978-0201615869. Addison Wesley.
- Hevery, M. (2008) Top 10 things which make your code hard to test [online]. Available from: http://misko.hevery.com/2008/07/30/top-10-things-which-make-your-code-hard-to-test/
- Sayfan, M. (n.d.) Avoid Global Variables, Environment Variables, and Singletons [online]. Available from: https://sites.google.com/site/michaelsafyan/software-engineering/avoid-global-variables-environment-variables-and-singletons
- Meyer, B. (1988) Bidding farewell to globals. JOOP(Journal of Object-Oriented Programming) , pp.73-77.
- Hevery, M. (2008) Brittle Global State & Singletons [online]. Available from: http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/
- Yaiser, M. (2011) Object-oriented programming concepts: Objects and classes [online]. Available from: http://www.adobe.com/devnet/actionscript/learning/oop-concepts/objects-and-classes.html
- Caromel, D. (1993) Toward a method of object-oriented concurrent programming. Communications of the ACM , pp.90-102.
- Summit, S. (1997) Visibility and Lifetime (Global Variables, etc.) [online]. Available from: https://www.eskimo.com/~scs/cclass/notes/sx4b.html
$value = Utils::debugResource($value);
$ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
self::addNotification(
$params,
Static methods
Summary of issues
- Tight Coupling
- Hidden dependencies
- Global state (if also using static variables)
Tight Coupling
Use of static methods always reduces flexibility by introducing tight coupling[1]. A static method tightly couples the calling code to the specific class the method exists in.
function totalAbs(double value, double value2) {
return abs(value) + abs(value2);
}Here, the method
totalAbs
has a dependency on theMath
class and the.abs()
method will always be called. Although for testing purposes this may not be a problem, the coupling reduces flexibility because thetotal
method can only work with doubles/integers, as that's all theMath.abs()
function can use. Although type coercion will allow the use of any primitive numeric type, these types have limitations. It's impossible to use another class such asBigInteger
or a class for dealing with greater precision decimals or even alternative numbering systems such as Roman numerals.The totalAbs function takes two doubles and converts them to their absolute values before adding them. This is inflexible because it only works with doubles. It's tied to doubles because that's what the
Math.abs()
static method requires. If, instead, using OOP an interface was created to handle any number that had this method:interface Numeric {
public function abs(): Numeric;
}It would then be possible to rewrite the
totalAbs
method to work with any kind of number:function totalAbs(Numeric value, Numeric value): Numeric {
return value.abs() + value2.abs();
}
By removing the static method and using an instance method in its place the
totalAbs
method is now agnostic about the type of number it is dealing with. It could be called with any of the following (assuming they implement theNumeric
interface)
totalAbs(new Integer(4), new Integer(-53));
totalAbs(new Double(34.4), new Integer(-2));
totalAbs(new BigInteger('123445454564765739878989343225778'), new Integer(2343));
totalAbs(new RomanNumeral('VII'), new RomanNumeral('CXV'));
Making the method reusable in a way that it wasn't when static methods were being used. By changing the static methods to instance methods, flexibility has been enhanced as the method can be used with any numeric type, not just numeric types that are supported by the
Math.abs()
method.Broken encapsulation
Static methods also break encapsulation. Encapsulation is defined by Rogers[2] as:
the bundling of data with the methods that operate on that dataBy passing the numeric value into the
abs
method, the data being operated on is being separated from the methods that operate on it, breaking encapsulation. Instead usingnum.abs()
the data is encapsulated in thenum
instance and its type is not visible or relevant to the outside world.abs()
will work on the data and work regardless ofnum
's type, providing it implements theabs
method.This is a simple example, but applies to all static methods. Use of polymorphic instance methods that work on encapsulated data will always be more flexible than static method calls which can only ever deal with specific pre-defined types.
Further reading
- What is tight coupling?
- Why Static is Bad and How to Avoid It
- Static Methods Will Shock You
- Flaw: Brittle Global State & Singletons
- Static Methods are Death to Testability
Exceptions
The only exception to this rule is when a static method is used for object creation in place of the
new
keyword[3]. This is because thenew
keyword is already a static call. However, even here a non-static factory is often preferable for testing purposes[4][5].References
- Popov, N. (2014) Don't be STUPID: GRASP SOLID! [online]. Available from: https://nikic.github.io/2011/12/27/Dont-be-STUPID-GRASP-SOLID.html
- Rogers, P. (2001) Encapsulation is not information hiding [online]. Available from: http://www.javaworld.com/article/2075271/core-java/encapsulation-is-not-information-hiding.html
- Sonmez, J. (2010) Static Methods Will Shock You [online]. Available from: http://simpleprogrammer.com/2010/01/29/static-methods-will-shock-you/
- Hevery, M. (2008) Static Methods are Death to Testability [online]. Available from: http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/
- Butler, T. (2013) Are Static Methods/Variables bad practice? [online]. Available from: https://r.je/static-methods-bad-practice.html
\fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
foreach (\array_filter($passed) as $i => $v) {
\fwrite($value, $args[$i] . ': "' . $v . '" ');
}
\fwrite($value, "\n");
}
);
}
Static methods
Summary of issues
- Tight Coupling
- Hidden dependencies
- Global state (if also using static variables)
Tight Coupling
Use of static methods always reduces flexibility by introducing tight coupling[1]. A static method tightly couples the calling code to the specific class the method exists in.
function totalAbs(double value, double value2) {
return abs(value) + abs(value2);
}Here, the method
totalAbs
has a dependency on theMath
class and the.abs()
method will always be called. Although for testing purposes this may not be a problem, the coupling reduces flexibility because thetotal
method can only work with doubles/integers, as that's all theMath.abs()
function can use. Although type coercion will allow the use of any primitive numeric type, these types have limitations. It's impossible to use another class such asBigInteger
or a class for dealing with greater precision decimals or even alternative numbering systems such as Roman numerals.The totalAbs function takes two doubles and converts them to their absolute values before adding them. This is inflexible because it only works with doubles. It's tied to doubles because that's what the
Math.abs()
static method requires. If, instead, using OOP an interface was created to handle any number that had this method:interface Numeric {
public function abs(): Numeric;
}It would then be possible to rewrite the
totalAbs
method to work with any kind of number:function totalAbs(Numeric value, Numeric value): Numeric {
return value.abs() + value2.abs();
}
By removing the static method and using an instance method in its place the
totalAbs
method is now agnostic about the type of number it is dealing with. It could be called with any of the following (assuming they implement theNumeric
interface)
totalAbs(new Integer(4), new Integer(-53));
totalAbs(new Double(34.4), new Integer(-2));
totalAbs(new BigInteger('123445454564765739878989343225778'), new Integer(2343));
totalAbs(new RomanNumeral('VII'), new RomanNumeral('CXV'));
Making the method reusable in a way that it wasn't when static methods were being used. By changing the static methods to instance methods, flexibility has been enhanced as the method can be used with any numeric type, not just numeric types that are supported by the
Math.abs()
method.Broken encapsulation
Static methods also break encapsulation. Encapsulation is defined by Rogers[2] as:
the bundling of data with the methods that operate on that dataBy passing the numeric value into the
abs
method, the data being operated on is being separated from the methods that operate on it, breaking encapsulation. Instead usingnum.abs()
the data is encapsulated in thenum
instance and its type is not visible or relevant to the outside world.abs()
will work on the data and work regardless ofnum
's type, providing it implements theabs
method.This is a simple example, but applies to all static methods. Use of polymorphic instance methods that work on encapsulated data will always be more flexible than static method calls which can only ever deal with specific pre-defined types.
Further reading
- What is tight coupling?
- Why Static is Bad and How to Avoid It
- Static Methods Will Shock You
- Flaw: Brittle Global State & Singletons
- Static Methods are Death to Testability
Exceptions
The only exception to this rule is when a static method is used for object creation in place of the
new
keyword[3]. This is because thenew
keyword is already a static call. However, even here a non-static factory is often preferable for testing purposes[4][5].References
- Popov, N. (2014) Don't be STUPID: GRASP SOLID! [online]. Available from: https://nikic.github.io/2011/12/27/Dont-be-STUPID-GRASP-SOLID.html
- Rogers, P. (2001) Encapsulation is not information hiding [online]. Available from: http://www.javaworld.com/article/2075271/core-java/encapsulation-is-not-information-hiding.html
- Sonmez, J. (2010) Static Methods Will Shock You [online]. Available from: http://simpleprogrammer.com/2010/01/29/static-methods-will-shock-you/
- Hevery, M. (2008) Static Methods are Death to Testability [online]. Available from: http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/
- Butler, T. (2013) Are Static Methods/Variables bad practice? [online]. Available from: https://r.je/static-methods-bad-practice.html
{
// Wrap the existing function if needed.
if (!isset($params['notification'])) {
$params['notification'] = $notify;
} else {
$params['notification'] = self::callArray([
$params['notification'],
$notify
]);
}
}
Static methods
Summary of issues
- Tight Coupling
- Hidden dependencies
- Global state (if also using static variables)
Tight Coupling
Use of static methods always reduces flexibility by introducing tight coupling[1]. A static method tightly couples the calling code to the specific class the method exists in.
function totalAbs(double value, double value2) {
return abs(value) + abs(value2);
}Here, the method
totalAbs
has a dependency on theMath
class and the.abs()
method will always be called. Although for testing purposes this may not be a problem, the coupling reduces flexibility because thetotal
method can only work with doubles/integers, as that's all theMath.abs()
function can use. Although type coercion will allow the use of any primitive numeric type, these types have limitations. It's impossible to use another class such asBigInteger
or a class for dealing with greater precision decimals or even alternative numbering systems such as Roman numerals.The totalAbs function takes two doubles and converts them to their absolute values before adding them. This is inflexible because it only works with doubles. It's tied to doubles because that's what the
Math.abs()
static method requires. If, instead, using OOP an interface was created to handle any number that had this method:interface Numeric {
public function abs(): Numeric;
}It would then be possible to rewrite the
totalAbs
method to work with any kind of number:function totalAbs(Numeric value, Numeric value): Numeric {
return value.abs() + value2.abs();
}
By removing the static method and using an instance method in its place the
totalAbs
method is now agnostic about the type of number it is dealing with. It could be called with any of the following (assuming they implement theNumeric
interface)
totalAbs(new Integer(4), new Integer(-53));
totalAbs(new Double(34.4), new Integer(-2));
totalAbs(new BigInteger('123445454564765739878989343225778'), new Integer(2343));
totalAbs(new RomanNumeral('VII'), new RomanNumeral('CXV'));
Making the method reusable in a way that it wasn't when static methods were being used. By changing the static methods to instance methods, flexibility has been enhanced as the method can be used with any numeric type, not just numeric types that are supported by the
Math.abs()
method.Broken encapsulation
Static methods also break encapsulation. Encapsulation is defined by Rogers[2] as:
the bundling of data with the methods that operate on that dataBy passing the numeric value into the
abs
method, the data being operated on is being separated from the methods that operate on it, breaking encapsulation. Instead usingnum.abs()
the data is encapsulated in thenum
instance and its type is not visible or relevant to the outside world.abs()
will work on the data and work regardless ofnum
's type, providing it implements theabs
method.This is a simple example, but applies to all static methods. Use of polymorphic instance methods that work on encapsulated data will always be more flexible than static method calls which can only ever deal with specific pre-defined types.
Further reading
- What is tight coupling?
- Why Static is Bad and How to Avoid It
- Static Methods Will Shock You
- Flaw: Brittle Global State & Singletons
- Static Methods are Death to Testability
Exceptions
The only exception to this rule is when a static method is used for object creation in place of the
new
keyword[3]. This is because thenew
keyword is already a static call. However, even here a non-static factory is often preferable for testing purposes[4][5].References
- Popov, N. (2014) Don't be STUPID: GRASP SOLID! [online]. Available from: https://nikic.github.io/2011/12/27/Dont-be-STUPID-GRASP-SOLID.html
- Rogers, P. (2001) Encapsulation is not information hiding [online]. Available from: http://www.javaworld.com/article/2075271/core-java/encapsulation-is-not-information-hiding.html
- Sonmez, J. (2010) Static Methods Will Shock You [online]. Available from: http://simpleprogrammer.com/2010/01/29/static-methods-will-shock-you/
- Hevery, M. (2008) Static Methods are Death to Testability [online]. Available from: http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/
- Butler, T. (2013) Are Static Methods/Variables bad practice? [online]. Available from: https://r.je/static-methods-bad-practice.html
{
Static methods
Summary of issues
- Tight Coupling
- Hidden dependencies
- Global state (if also using static variables)
Tight Coupling
Use of static methods always reduces flexibility by introducing tight coupling[1]. A static method tightly couples the calling code to the specific class the method exists in.
function totalAbs(double value, double value2) {
return abs(value) + abs(value2);
}Here, the method
totalAbs
has a dependency on theMath
class and the.abs()
method will always be called. Although for testing purposes this may not be a problem, the coupling reduces flexibility because thetotal
method can only work with doubles/integers, as that's all theMath.abs()
function can use. Although type coercion will allow the use of any primitive numeric type, these types have limitations. It's impossible to use another class such asBigInteger
or a class for dealing with greater precision decimals or even alternative numbering systems such as Roman numerals.The totalAbs function takes two doubles and converts them to their absolute values before adding them. This is inflexible because it only works with doubles. It's tied to doubles because that's what the
Math.abs()
static method requires. If, instead, using OOP an interface was created to handle any number that had this method:interface Numeric {
public function abs(): Numeric;
}It would then be possible to rewrite the
totalAbs
method to work with any kind of number:function totalAbs(Numeric value, Numeric value): Numeric {
return value.abs() + value2.abs();
}
By removing the static method and using an instance method in its place the
totalAbs
method is now agnostic about the type of number it is dealing with. It could be called with any of the following (assuming they implement theNumeric
interface)
totalAbs(new Integer(4), new Integer(-53));
totalAbs(new Double(34.4), new Integer(-2));
totalAbs(new BigInteger('123445454564765739878989343225778'), new Integer(2343));
totalAbs(new RomanNumeral('VII'), new RomanNumeral('CXV'));
Making the method reusable in a way that it wasn't when static methods were being used. By changing the static methods to instance methods, flexibility has been enhanced as the method can be used with any numeric type, not just numeric types that are supported by the
Math.abs()
method.Broken encapsulation
Static methods also break encapsulation. Encapsulation is defined by Rogers[2] as:
the bundling of data with the methods that operate on that dataBy passing the numeric value into the
abs
method, the data being operated on is being separated from the methods that operate on it, breaking encapsulation. Instead usingnum.abs()
the data is encapsulated in thenum
instance and its type is not visible or relevant to the outside world.abs()
will work on the data and work regardless ofnum
's type, providing it implements theabs
method.This is a simple example, but applies to all static methods. Use of polymorphic instance methods that work on encapsulated data will always be more flexible than static method calls which can only ever deal with specific pre-defined types.
Further reading
- What is tight coupling?
- Why Static is Bad and How to Avoid It
- Static Methods Will Shock You
- Flaw: Brittle Global State & Singletons
- Static Methods are Death to Testability
Exceptions
The only exception to this rule is when a static method is used for object creation in place of the
new
keyword[3]. This is because thenew
keyword is already a static call. However, even here a non-static factory is often preferable for testing purposes[4][5].References
- Popov, N. (2014) Don't be STUPID: GRASP SOLID! [online]. Available from: https://nikic.github.io/2011/12/27/Dont-be-STUPID-GRASP-SOLID.html
- Rogers, P. (2001) Encapsulation is not information hiding [online]. Available from: http://www.javaworld.com/article/2075271/core-java/encapsulation-is-not-information-hiding.html
- Sonmez, J. (2010) Static Methods Will Shock You [online]. Available from: http://simpleprogrammer.com/2010/01/29/static-methods-will-shock-you/
- Hevery, M. (2008) Static Methods are Death to Testability [online]. Available from: http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/
- Butler, T. (2013) Are Static Methods/Variables bad practice? [online]. Available from: https://r.je/static-methods-bad-practice.html
foreach ($functions as $fn) {
$fn(...$args);
}
};
}
}