It's a bit like when you spot a poster for a concert by a band you remember
from your youth. Are they still playing? Or did they get back together after
years because they need the money? Perhaps to cash in on the strings of
nostalgia? [perex]
Texy is my first open-source project. I started writing it fifteen years ago. Texy
has survived several version control systems. Numerous web services hosting
repositories. Several string encodings. Various markup languages for creating
websites. Several of my life relationships. A number of cities I've
lived in.
Texy is still here because there is nothing
better.
So, I have kept it up-to-date for fifteen years. We started in PHP 4, which
was the worst programming language in the world and thus a challenge, then moved
on to PHP 5 with relief, a few years later we transitioned to namespaces
(Texy::Parser
instead of TexyParser
, wow), watched PHP
stop being the worst language in the world, which frustrated many programmers
who then turned to JavaScript, then God created PHP 7 and with it type hints
(Texy::process(string $text): string
megawow), and strictness came
into fashion with declare(strict_types=1)
and we honor that.
And so here is Texy 3.0.. It's the
same as the previous versions, but with all the bells and whistles of PHP
7.1. It's the same because you don't mess with perfection.
Texy was here when you were born, in programming terms. Someday, Texy might
even format your epitaph. And it will insert a non-breaking space between
a
and room
.
How to mock classes that are defined as final or some of their
methods are final?
Mocking means replacing the original object with its testing imitation that
does not perform any functionality and just looks like the original object. And
pretending the behavior we need to test.
For example, instead of a PDO with methods like query() etc., we create a
mock that pretends working with the database, and instead verifies that the
correct SQL statements are called, etc. More e.g. in the Mockery
documentation.
And in order to be able to pass mock to methods that use PDO
type hint, it is necessary for the mock class to inherit from the PDO. And that
can be a stumbling block. If the PDO or method query() were final, it would not
be possible.
Is there any solution? The first option is not to use the final keyword at
all. This, of course, does not help with the third-party code that it uses, but
mainly detracts from the important element of the object design. For example,
there is dogma that every class should be either final or abstract.
The second and very handy option is to use BypassFinals, which removes
finals from source code on-the-fly and allows mocking of final methods and
classes.
Install it using Composer:
composer require dg/bypass-finals --dev
And just call at the beginning of the test:
require __DIR__ . '/vendor/autoload.php';
DG\BypassFinals::enable();
Thats all. Incredibly black magic 🙂
BypassFinals requires PHP version 5.6 and supports PHP up to 7.2. It can be
used together with any test tool such as PHPUnit or Mockery.
This functionality is directly implemented in the “Nette Tester”: https://tester.nette.org version 2.0 and
can be enabled this way:
require __DIR__ . '/vendor/autoload.php';
Tester\Environment::bypassFinals();
A naming conundrum: how to collectively refer to classes and interfaces? For
instance, what should you call a variable that could contain either a class or
an interface name? What should be used instead of $class
?
One might consider the term type ($type
), but this is
quite generic because a type can also be a string or an array. From the
perspective of the language, a type could be something more complex, such as
?array
. Moreover, it's debatable what constitutes the type of an
object: is it the class name, or is it object
?
However, there indeed exists a collective term for classes and interfaces: it
is the word class.
How so?
- From a declaration standpoint, an interface is essentially a stripped-down
class. It can only contain public abstract methods, which also implies that
objects cannot be created. Therefore, interfaces are a subset of classes. If
something is a subset, we can refer to it by the name of the superset. Just as a
human is a mammal, an interface is a class.
- Nevertheless, there's also the usage perspective. A class can inherit from
only one class but can implement multiple interfaces. However, this limitation
pertains to classes, not to the interfaces themselves. Similarly, a class cannot
inherit from a final class, but we still perceive the final class as a class.
Also, if a class can implement multiple interfaces (i.e., classes, see 1.), we
still regard them as classes.
And what about traits? They simply do not belong here, as they do not exist
from an OOP standpoint.
Thus, the issue of naming classes and interfaces together is resolved.
Let’s simply call them classes.
classes + interfaces = classes
Well, but a new problem has arisen. How to refer to classes that are not
interfaces? That is, their complement. What was referred to at the beginning of
the article as classes. Non-interface? Or “implementations”#Class_vs._type)? 🙂
That's an even bigger nut to crack. It’s a tough nut indeed. You know
what, let's forget that interfaces are also classes and again pretend that
every OOP identifier is either a class or an interface. It will be easier.
And what you won't read in the documentation, including a
security patch and advice on speeding up server response without slowing
it down.
Output buffering allows the output of a PHP script (primarily from the
echo
function) to be stored in memory (i.e., a buffer) instead of
being sent immediately to the browser or terminal. This is useful for various
purposes.
Preventing Output to the Screen:
ob_start(); // enables output buffering
$foo->bar(); // all output goes only to the buffer
ob_end_clean(); // clears the buffer and ends buffering
Capturing Output into a Variable:
ob_start(); // enables output buffering
$foo->render(); // output goes only to the buffer
$output = ob_get_contents(); // saves the buffer content into a variable
ob_end_clean(); // clears the buffer and ends buffering
The pair ob_get_contents()
and ob_end_clean()
can be replaced by a single function ob_get_clean()
,
which removes end
from the name but indeed turns off output
buffering:
$output = ob_get_clean(); // saves the buffer content into variable and disables buffering
In the given examples, the buffer content did not reach the output at all. If
you want to send it to the output instead, you should use ob_end_flush()
instead of ob_end_clean()
. To simultaneously get the buffer
content, send it to the output, and end buffering, there is also a shortcut: ob_get_flush()
.
You can empty the buffer at any time without ending it using ob_clean()
(clears it) or ob_flush()
(sends it to the output):
ob_start(); // enables output buffering
$foo->bar(); // all output goes only to the buffer
ob_clean(); // clears the buffer content, but buffering remains active
$foo->render(); // output still goes to the buffer
ob_flush(); // sends the buffer to the output
$none = ob_get_contents(); // the buffer content is now an empty string
ob_end_clean(); // disables output buffering
Output written to php://output
is also sent to the buffer, while
buffers can be bypassed by writing to php://stdout
(or
STDOUT
), which is available only under CLI, i.e., when running
scripts from the command line.
Nesting
Buffers can be nested, so while one buffer is active, calling ob_start()
activates a new buffer. Thus, ob_end_flush()
and
ob_flush()
send the buffer content not to the output but to the
parent buffer. Only when there is no parent buffer does the content get sent to
the actual output, i.e., the browser or terminal.
Therefore, it is important to end buffering, even if an exception occurs
during the process:
ob_start();
try {
$foo->render();
} finally { // finally available from PHP 5.5
ob_end_clean(); // or ob_end_flush()
}
Buffer Size
The buffer can also “speed up page generation (I haven't measured this,
but it sounds logical)” by not sending every single echo
to the
browser, but a larger amount of data (e.g., 4kB). Just call at the beginning of
the script:
ob_start(null, 4096);
When the buffer size exceeds 4096 bytes (the so-called
chunk size
), a flush
is performed automatically, i.e.,
the buffer is emptied and sent out. The same can be achieved by setting the output_buffering
directive. It is ignored in CLI mode.
But beware, starting buffering without specifying the size, i.e.,
simply with ob_start()
, will cause the page not to be sent
gradually but only after it is fully rendered, making the server appear
very slow!
Output buffering has no effect on sending HTTP headers, which are processed
by a different path. However, thanks to buffering, headers can be sent even
after some output has been printed, as it is still held in the buffer. This is a
side effect you shouldn't rely on, as there is no certainty when the output will
exceed the buffer size and be sent.
Security Hole
When the script ends, all unclosed buffers are outputted. This can be
considered an unpleasant security hole if, for example, you prepare sensitive
data in the buffer not intended for output and an error occurs. The solution is
to use a custom handler:
ob_start(function () { return ''; });
Handlers
You can attach a custom handler to output buffering, i.e., a function that
processes the buffer content before sending it out:
ob_start(
function ($buffer, $phase) { return strtoupper($buffer); }
);
echo 'Hello';
ob_end_flush(); // 'HELLO' is sent to the output
Functions ob_clean()
or ob_end_clean()
will call
the handler but discard the output without sending it out. The handler can
detect which function is called and respond accordingly. The second parameter
$phase
is a bitmask (from PHP 5.4):
PHP_OUTPUT_HANDLER_START
when the buffer is opened
PHP_OUTPUT_HANDLER_FINAL
when the buffer is closed
PHP_OUTPUT_HANDLER_FLUSH
when ob_flush()
is called
(but not ob_end_flush()
or ob_get_flush()
)
PHP_OUTPUT_HANDLER_CLEAN
when ob_clean()
,
ob_end_clean()
, and ob_get_clean()
are called
PHP_OUTPUT_HANDLER_WRITE
when an automatic flush occurs
The start, final, and flush (or clean) phases can occur simultaneously,
distinguished by the binary operator &
:
if ($phase & PHP_OUTPUT_HANDLER_START) { ... }
if ($phase & PHP_OUTPUT_HANDLER_FLUSH) { ... }
elseif ($phase & PHP_OUTPUT_HANDLER_CLEAN) { ... }
if ($phase & PHP_OUTPUT_HANDLER_FINAL) { ... }
The PHP_OUTPUT_HANDLER_WRITE
phase occurs only if the buffer has
a size (chunk size
) and that size was exceeded. This is the
mentioned automatic flush. Note, the constant
PHP_OUTPUT_HANDLER_WRITE
has a value of 0, so you can't use a bit
test, but:
if ($phase === PHP_OUTPUT_HANDLER_WRITE) { ... }
A handler doesn't have to support all operations. When activating with
ob_start()
, you can specify the bitmask of supported operations as
the third parameter:
PHP_OUTPUT_HANDLER_CLEANABLE
– allows calling
ob_clean()
and related functions
PHP_OUTPUT_HANDLER_FLUSHABLE
– allows calling
ob_flush()
PHP_OUTPUT_HANDLER_REMOVABLE
– buffer can be ended
PHP_OUTPUT_HANDLER_STDFLAGS
– combines all three flags, the
default behavior
This applies even to buffering without a custom handler. For example, if
I want to capture the output into a variable, I don't set the
PHP_OUTPUT_HANDLER_FLUSHABLE
flag, preventing the buffer from being
(accidentally) sent to the output with ob_flush()
. However, it can
still be done with ob_end_flush()
or ob_get_flush()
,
which somewhat defeats the purpose.
Similarly, not setting the PHP_OUTPUT_HANDLER_CLEANABLE
flag
should prevent the buffer from being cleared, but again it doesn't work.
Finally, not setting PHP_OUTPUT_HANDLER_REMOVABLE
makes the
buffer user-undeletable; it turns off only when the script ends. An example of a
handler that should be set this way is ob_gzhandler
,
which compresses output, thus reducing volume and increasing data transfer
speed. Once this buffer is opened, it sends the HTTP header
Content-Encoding: gzip
, and all subsequent output must be
compressed. Removing the buffer would break the page.
The correct usage is:
ob_start(
'ob_gzhandler',
16000, // without chunk size, the server would not send data gradually
PHP_OUTPUT_HANDLER_FLUSHABLE // but not removable or cleanable
);
You can also enable output compression by setting the zlib.output_compression
directive, which turns on buffering with a different handler (not sure how it
differs specifically), but it lacks the flag to be non-removable. Since
it's good to compress the transfer of all text files, not just PHP-generated
pages, it's better to activate compression directly on the HTTP
server side.
PHP ssh2 thread safe binaries for Microsoft Windows:
Command-line script to convert between array()
and
PHP 5.4's short syntax []
. It uses native PHP tokenizer, so
conversion is safe. The script was successfully tested against thousands of
PHP files.
Download from GitHub
To convert all *.php
and *.phpt
files in whole
directory recursively or to convert a single file use:
php convert.php <directory | file>
To convert source code from STDIN and print the output to STDOUT use:
php convert.php < input.php > output.php
To convert short syntax []
to older long syntax
array()
use option --reverse
:
php convert.php --reverse [<directory | file>]
The way applications are developed in PHP has dramatically
transformed over the last 5 years. Initially, we moved away from pure PHP and
learned to use frameworks. Later, Composer arrived, enabling library
installations from the command line. Now, we are witnessing the end of
frameworks as we know them.
Monolithic frameworks are gradually disintegrating into separate (decoupled)
components. This transition offers several advantages. While previously using
just one part of a framework was difficult or impossible, today you can simply
install its component. The development cycle of individual components can vary.
They have their own repositories, issue trackers, and can have their own
development teams.
You can update components to new versions continuously, without waiting for
the next version of the entire framework. Alternatively, you may decide not to
update a certain component, perhaps due to a BC break.
The meaning of the word “framework” is shifting; talking about versions
is almost obsolete. Instead of using framework XYZ in version 2.3.1, you use a
set of components in various versions that work together.
Splitting a framework into components is quite complex. For Nette, it took
2 years and was completed last year. The adoption of Composer and the
consistent use of dependency injection were absolutely essential. Nette now
consists of over 20 separate repositories, and the original one retains only a
single
class.
All major frameworks, such as Symfony, Zend, Laravel, or CakePHP, are divided
into components, though one step remains to be completed: splitting into
separate repositories (instead of a workaround like Git subtree split). Zend
promises to do this in version 2.5; we'll see what happens with Symfony.
Composing Nette
Through this long introduction, I wanted to lead you to the idea that
viewing Nette as a framework in any specific version is outdated. It's smarter
to approach it as a set of components.
That is, instead of declaring a dependency on nette/nette
, you
should declare dependencies on specific components. This is now being done
by Sandbox. For the foundation of a future application, you can also use the
Nette Web Project, which is a
minimalist version of the Sandbox. Download it using
composer create-project nette/web-project
and remove from composer.json
the components you do not need.
This will speed up Composer operations.
Bug fixes will also reach you faster. Once an error is fixed, you can
immediately tag a new version for the relevant component, whereas the release
cycle for the entire framework is much slower.
If you are creating add-ons for Nette, do not hesitate and immediately
replace the dependency on nette/nette
with a list of actually
required components.
Of course, new versions of the framework will continue to be released as
before, require nette/nette
will still work, and for version 2.3,
distributions in ZIP archives will also be released. But their significance will
gradually diminish.
In the Best
PHP Framework for 2015 survey conducted by SitePoint magazine, Nette
secured an impressive 3rd place. Thank you very much to everyone who
voted; I truly did not expect such a fantastic result. [perex]
What I find gratifying is that users seem to be satisfied with Nette,
otherwise, they probably wouldn't have sent their votes. And, of course, the
fact that Nette has thus made itself known in a world where it is less known due
to language barriers.
Another interesting aspect of the results is realizing how many PHP
frameworks are actually used, that there are other popular “local
frameworks,” and that there are still many who do not use any framework
at all.
One of the most interesting parts of Nette, praised even by users of other
frameworks, is the Dependency Injection
Container (hereinafter referred to as Nette DI). See how easily you can use
it anywhere, even outside of Nette. [perex]
Let's consider an application for sending newsletters. I've simplified the
code of the individual classes to the core. Here's an object representing
an email:
class Mail
{
public $subject;
public $message;
}
Someone who knows how to send it:
interface Mailer
{
function send(Mail $mail, $to);
}
We add support for logging:
interface Logger
{
function log($message);
}
And finally, a class that manages the distribution of newsletters:
class NewsletterManager
{
private $mailer;
private $logger;
function __construct(Mailer $mailer, Logger $logger)
{
$this->mailer = $mailer;
$this->logger = $logger;
}
function distribute(array $recipients)
{
$mail = new Mail;
...
foreach ($recipients as $recipient) {
$this->mailer->send($mail, $recipient);
}
$this->logger->log(...);
}
}
The code respects Dependency Injection, meaning each class only works with
the variables that we have passed to it. We also have the option to
implement Mailer
and Logger
in our own way, for
example like this:
class SendMailMailer implements Mailer
{
function send(Mail $mail, $to)
{
mail($to, $mail->subject, $mail->message);
}
}
class FileLogger implements Logger
{
private $file;
function __construct($file)
{
$this->file = $file;
}
function log($message)
{
file_put_contents(this->file, $message . "\n", FILE_APPEND);
}
}
The DI container is the supreme architect that can create individual
objects (referred to in DI terminology as services) and assemble and configure
them precisely according to our needs.
A container for our application could look something like this:
class Container
{
private $logger;
private $mailer;
function getLogger()
{
if (!$this->logger) {
$this->logger = new FileLogger('log.txt');
}
return $this->logger;
}
function getMailer()
{
if (!$this->mailer) {
$this->mailer = new SendMailMailer;
}
return this->mailer;
}
function createNewsletterManager()
{
return new NewsletterManager($this->getMailer(), $this->getLogger());
}
}
The implementation looks this way so that:
- the individual services are created only when needed (lazy)
- a double call to
createNewsletterManager
always uses the same
logger and mailer objects
Create an instance of Container
, let it produce a manager, and
you can start spamming users with newsletters:
$container = new Container;
$manager = $container->createNewsletterManager();
$manager->distribute(...);
The essence of Dependency Injection is that no class depends on the
container. Therefore, we can easily replace it with another, perhaps with a
container generated by Nette DI.
Nette DI
Nette DI is indeed a container generator. We instruct it (usually) using
configuration files, and perhaps this configuration generates roughly the same
as the Container
class:
services:
- FileLogger( log.txt )
- SendMailMailer
- NewsletterManager
A significant advantage is the brevity of the notation. Additionally, we can
add more and more dependencies to individual classes often without needing to
modify the configuration.
Nette DI generates actual PHP container code. It is therefore extremely fast,
and the programmer knows exactly what it does and can even step through it.
The container might have tens
of thousands of lines in the case of large applications, and maintaining
something like that manually would probably not be possible.
Deploying Nette DI into our application is very easy. First, we install it
using Composer (because downloading ZIPs is so outdated):
composer require nette/di
We save the above configuration in a file config.neon
and use
the class Nette\DI\ContainerLoader
to create the container:
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
$class = $loader->load(function($compiler) {
$
compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;
and then again let it create the NewsletterManager
object and we
can start sending emails:
$manager = $container->getByType('NewsletterManager');
$manager->distribute(['john@example.com', ...]);
But back to ContainerLoader
for a moment. The mentioned syntax
is subordinate to one thing: speed. The container is generated once, its code is
written to cache (directory __DIR__ . '/temp'
), and for subsequent
requests, it is just loaded from here. Therefore, the loading of the
configuration is placed into a closure in the $loader->load()
method.
During development, it is useful to activate the auto-refresh mode, where the
container is automatically regenerated if any class or configuration file
changes. Just mention true
as the second argument in the
ContainerLoader
constructor.
As you can see, using Nette DI is definitely not limited to applications
written in Nette, you can deploy it anywhere with just 3 lines of code. Try
playing with it, the whole example is available on GitHub.
It has been three years since Nette 2.0.0 was released. It was
a groundbreaking version that concluded several years of development and
introduced features that are indispensable in Nette development today.
- Dependency Injection
- NEON format
- Debug Bar extendable with custom panels
- Unobtrusive JavaScript validation in forms
- New API for extending Latte
- New structure of namespaces and classes
- Introduced the database layer Nette Database
and NDBT
- And a completely rewritten documentation
Coincidentally, at that time, major version twos of significant frameworks
such as Zend and Symfony were also released. It's worth mentioning that unlike
these frameworks, Nette did not abandon users of its previous versions. It did
not draw a thick line between versions but instead tried to preserve
compatibility as much as possible. For example, users received a tool that
replaced old class names with new ones in their source codes, etc.
PHP 5.2
The 2.0 series still supported PHP 5.2, including PHP 5.2.0, which was
indeed painful. This version of PHP was one of the less successful, yet Debian
had it pre-installed, and conservative administrators refused to
upgrade it.
Interestingly, since 2010, Nette was written purely in PHP 5.3 with all its
features like namespaces and anonymous functions. The (two) versions for PHP
5.2 were created using a machine converter. This converter not only replaced
class names with non-namespaced variants but also managed to rewrite anonymous
functions and handle various other differences, such as the inability to use
func_get_args()
as a function parameter, etc.
Example of code in PHP 5.3:
/**
* Caches results of function/method calls.
* @param mixed
* @param array dependencies
* @return Closure
*/
public function wrap($function, array $dependencies = null)
{
$cache = $this;
return function() use ($cache, $function, $dependencies) {
$key = array($function, func_get_args());
$data = $cache->load($key);
if ($data === null) {
$data = $cache->save($key, Nette\Callback::create($function)->invokeArgs($key[1]), $dependencies);
}
return $data;
};
}
And the converted code for PHP 5.2:
/**
* Caches results of function/method calls.
* @param mixed
* @param array dependencies
* @return NClosure
*/
public function wrap($function, array $dependencies = null)
{
$cache = $this;
return create_function('',
'extract($GLOBALS[0]['.array_push($GLOBALS[0], array('cache'=>$cache,'function'=> $function,'dependencies'=> $dependencies)).'-1], EXTR_REFS);
$_args=func_get_args(); $key = array($function, $_args);
$data = $cache->load($key);
if ($data === null) {
$data = $cache->save($key, NCallback::create($function)->invokeArgs($key[1]), $dependencies);
}
return $data;
');
}
Dependency Injection
Looking back, the most significant contribution of Nette 2.0 was Dependency
Injection. But as the old saying goes:
Dependency Injection is no simple matter. It really isn't. It's a concept
not everyone is well-versed in.
DI replaced the previously used object Service Locator and its static
version, the Environment class, completely overturning the way applications were
designed. It brought a qualitative leap to a new level. Therefore, rewriting an
application that used Environment to Dependency Injection is extremely
challenging, as it essentially means redesigning it better and from scratch.
End of Life
The first day of the year 2014 saw the release of Nette 2.0.14. Yes, it was
a neat coincidence 🙂 This marked the end of the 2.0 series, and the series
entered a one-year phase of critical issues only, where only severe
bugs were fixed. Today, this phase is ending. A few days ago, Nette 2.0.18, the
definitively last version of this series and also the last version for PHP 5.2,
was released.
So farewell and goodbye!
(The 2.1 series now enters the *critical issues only phase
. During 2015, only severe bugs will be fixed.)*