The modern PHP language has many exciting new features. Many of these features will be brand new to PHP programmers upgrading from earlier versions, and they’ll be a nice surprise to programmers migrating to PHP from another language. These new features make the PHP language a powerful platform and provide a pleasant experience for building web applications and command-line tools.
Some of these features aren’t essential, but they still make our lives easier. Some features, however, are essential. Namespaces, for example, are a cornerstone of modern PHP standards and enable development practices that modern PHP developers take for granted (e.g., autoloading). I’ll introduce each new feature, explain why it is useful, and show you how to implement it in your own projects.
I encourage you to follow along on your own computer. You can find all of the text’s code examples in this book’s companion GitHub repository.
If there is one modern PHP feature I want you to know, it is namespaces. Introduced in PHP 5.3.0, namespaces are an important tool that organizes PHP code into a virtual hierarchy, comparable to your operating system’s filesystem directory structure. Each modern PHP component and framework organizes its code beneath its own globally unique vendor namespace so that it does not conflict with, or lay claim to, common class names used by other vendors.
Don’t you hate it when you walk into a coffee shop and this one obnoxious person has a mess of books, cables, and whatnot spread across several tables? Not to mention he’s sitting next to, but not using, the only available power outlet. He’s wasting valuable space that could otherwise be useful to you. Figuratively speaking, this person is not using namespaces. Don’t be this person.
Let’s see how a real-world PHP component uses namespaces. The Symfony Framework’s
own symfony/httpfoundation
is a popular PHP component that manages HTTP requests and responses. More important,
the symfony/httpfoundation component uses common PHP class names like Request,
Response, and Cookie. I guarantee you there are many other PHP components that use
these same class names. How can we use the symfony/httpfoundation PHP component if
other PHP code uses the same class names? We can safely use the symfony/httpfoundation
component precisely because its code is sandboxed beneath the unique Symfony vendor namespace.
Visit the symfony/httpfoundation component on GitHub
and navigate to the Response.php
file. It looks like Figure 2-1.
Look closely at line 12. It contains this code:
namespaceSymfony\Component\HttpFoundation;
This is a PHP namespace declaration, and it always appears on a new line immediately after the
opening <?php tag. This particular namespace declaration tells us several things.
First, we know the Response class lives beneath the Symfony vendor namespace
(the vendor namespace is the topmost namespace). We know the Response class
lives beneath the Component subnamespace. We also know the Response class
lives beneath yet another subnamespace named HttpFoundation. You can view other
files adjacent to Response.php, and you’ll see they use the
same namespace declaration. A namespace (or subnamespace) encapsulates and organizes
related PHP classes, just as a filesystem directory contains related files.
Subnamespaces are separated with a \ character.
Unlike your operating system’s physical filesystem, PHP namespaces are a virtual concept and do not necessarily map 1:1 with filesystem directories. That being said, most PHP components do, in fact, map subnamespaces to filesystem directories for compatibility with the popular PSR-4 autoloader standard (we’ll talk more about this in Chapter 3).
Technically speaking, namespaces are merely a PHP language notation referenced by the PHP interpreter to apply a common name prefix to a set of classes, interfaces, functions, and constants.
Namespaces are important because they let us create sandboxed code that works alongside other developers’ code. This is the cornerstone concept of the modern PHP component ecosystem. Component and framework authors build and distribute code for a large number of PHP developers, and they have no way of knowing or controlling what classes, interfaces, functions, and constants are used alongside their own code. This problem applies to your own in-house projects, too. If you write custom PHP components or classes for a project, that code must work alongside your project’s third-party dependencies.
As I mentioned earlier with the symfony/httpfoundation component, your
code and other developers’ code might use the same class, interface, function,
or constant names. Without namespaces, a name collision causes PHP
to fail. With namespaces, your code and other developers’ code can
use the same class, interface, function, or constant name assuming
your code lives beneath a unique vendor namespace.
If you’re building a tiny personal project with only a few dependencies, class name collisions probably won’t be an issue. But when you’re working on a team building a large project with numerous third-party dependencies, name collisions become a very real concern. You cannot control which classes, interfaces, functions, and constants are introduced into the global namespace by your project’s dependencies. This is why namespacing your code is important.
Every PHP class, interface, function, and constant lives beneath a
namespace (or subnamespace). Namespaces are declared at the top of a
PHP file on a new line immediately after the opening <?php tag.
The namespace declaration begins with namespace, then a space character,
then the namespace name, and then a closing semicolon ;
character.
Remember that namespaces are often used to establish a top-level vendor name.
This example namespace declaration establishes the Oreilly vendor name:
<?phpnamespaceOreilly;
All PHP classes, interfaces, functions, or constants declared
beneath this namespace declaration live in the Oreilly namespace and are,
in some way, related to O’Reilly Media. What if we wanted to organize code
related to this book? We use a subnamespace.
Subnamespaces are declared exactly the same as in the previous example.
The only difference is that we separate namespace and subnamespace names
with the \ character. The following example declares a subnamespace
named ModernPHP that lives beneath the topmost Oreilly vendor namespace:
<?phpnamespaceOreilly\ModernPHP;
All classes, interfaces, functions, and constants declared
beneath this namespace declaration live in the Oreilly\ModernPHP
subnamespace and are, in some way, related to this book.
All classes in the same namespace or subnamespace don’t have to be declared in the same PHP file. You can specify a namespace or subnamespace at the top of any PHP file, and that file’s code becomes a part of that namespace or subnamespace. This makes it possible to write multiple classes in separate files that belong to a common namespace.
Before we had namespaces, PHP developers solved the name collision problem with Zend-style class names. This was a class-naming scheme popularized by the Zend Framework where PHP class names used underscores in lieu of filesystem directory separators. This convention accomplished two things: it ensured class names were unique, and it enabled a naive autoloader implementation that replaced underscores in PHP class names with filesystem directory separators to determine the class file path.
For example, the PHP class Zend_Cloud_DocumentService_Adapter_WindowsAzure_Query
corresponds to the PHP file Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php.
A side effect of the Zend-style naming convention, as you can see, is
absurdly long class names. Call me lazy, but there’s no way I’m
typing that class name more than once.
Modern PHP namespaces present a similar problem. For example, the full Response class name
in the symfony\httpfoundation component is \Symfony\Component\HttpFoundation\Response.
Fortunately, PHP lets us import and alias namespaced code.
By import, I mean that I tell PHP which namespaces, classes, interfaces, functions, and constants I will use in each PHP file. I can then use these without typing their full namespaces.
By alias, I mean that I tell PHP that I will reference an imported class, interface, function, or constant with a shorter name.
You can import and alias PHP classes, interfaces, and other namespaces as of PHP 5.3. You can import and alias PHP functions and constants as of PHP 5.6.
The code shown in Example 2-1 creates and sends a 400 Bad Request HTTP response
without importing and aliasing.
<?php$response=new\Symfony\Component\HttpFoundation\Response('Oops',400);$response->send();
This isn’t terrible, but imagine you have to instantiate a Response instance
several times in a single PHP file. Your fingers will get tired quickly.
Now look at Example 2-2. It does the same thing with importing.
<?phpuseSymfony\Component\HttpFoundation\Response;$response=newResponse('Oops',400);$response->send();
We tell PHP we intend to use the Symfony\Component\HttpFoundation\Response class
with the use keyword. We type the long, fully qualified class name once. Then we
can instantiate the Response class without using its fully namespaced class name.
How cool is that?
Some days I feel really lazy. This is a good opportunity to use an alias. Let’s
extend Example 2-2. Instead of typing Response, maybe I just want to
type Res instead. Example 2-3 shows how I can do that.
<?phpuseSymfony\Component\HttpFoundation\ResponseasRes;$r=newRes('Oops',400);$r->send();
In this example, I changed the import line to import the Response class. I also appended
as Res to the end of the import line; this tells PHP to consider Res an alias for
the Response class. If we don’t append the as Res alias to the import line, PHP
assumes a default alias that is the same as the imported class name.
You should import code with the use keyword at the top of each PHP file,
immediately after the opening <?php tag or namespace declaration.
You don’t need a leading \ character when importing code with the use
keyword because PHP assumes imported namespaces are fully qualified.
The use keyword must exist in the global scope (i.e., not inside of
a class or function) because it is used at compile time. It can, however, be
located beneath a namespace declaration to import code into another namespace.
As of PHP 5.6, it’s possible to import functions and constants. This
requires a tweak to the use keyword syntax. To import
a function, change use to use func:
<?phpusefuncNamespace\functionName;functionName();
To import a constant, change use to use constant:
<?phpuseconstantNamespace\CONST_NAME;echoCONST_NAME;
Function and constant aliases work the same as classes.
If you import multiple classes, interfaces, functions, or constants
into a single PHP file, you’ll end up with multiple use statements
at the top of your PHP file. PHP accepts a shorthand import syntax
that combines multiple use statements on a single line like this:
<?phpuseSymfony\Component\HttpFoundation\Request,Symfony\Component\HttpFoundation\Response,Symfony\Component\HttpFoundation\Cookie;
Don’t do this. It’s confusing and easy to mess up. I recommend you keep
each use statement on its own line like this:
<?phpuseSymfony\Component\HttpFoundation\Request;useSymfony\Component\HttpFoundation\Response;useSymfony\Component\HttpFoundation\Cookie;
You’ll type a few extra characters, but your code is easier to read and troubleshoot.
PHP lets you define multiple namespaces in a single PHP file like this:
<?phpnamespaceFoo{// Declare classes, interfaces, functions, and constants here}namespaceBar{// Declare classes, interfaces, functions, and constants here}
This is confusing and violates the recommended one class per file good practice. Use only one namespace per file to make your code simpler and easier to troubleshoot.
If you reference a class, interface, function, or constant without a namespace,
PHP assumes the class, interface, function, or constant lives in the current
namespace. If this assumption is wrong, PHP attempts to resolve the class, interface,
function, or constant. If you need to reference
a namespaced class, interface, function, or constant inside another namespace,
you must use the fully qualified PHP class name (namespace + class name). You
can type the fully qualified PHP class name, or you can import the code
into the current namespace with the use keyword.
Some code might not have a namespace and, therefore, lives in the global namespace.
The native Exception class is a good example. You can reference globally namespaced
code inside another namespace by prepending a \ character to the class, interface, function, or constant
name. For example, the \My\App\Foo::doSomething()
method in Example 2-4 fails because PHP searches for a \My\App\Exception
class that does not exist.
<?phpnamespaceMy\App;classFoo{publicfunctiondoSomething(){$exception=newException();}}
Instead, add a \ prefix to the Exception class name, as shown in Example 2-5. This tells PHP to look for
the Exception class in the global namespace instead of the current namespace.
<?phpnamespaceMy\App;classFoo{publicfunctiondoSomething(){thrownew\Exception();}}
Namespaces also provide the bedrock for the PSR4 autoloader standard created by the PHP Framework Interop Group (PHP-FIG). This autoloader pattern is used by most modern PHP components, and it lets us autoload project dependencies using the Composer dependency manager. We’ll talk about Composer and the PHP-FIG in Chapter 4. For now, just understand that the modern PHP ecosystem and its emerging component-based architecture would be impossible without namespaces.
Learning how to code to an interface changed my life as a PHP programmer, and it profoundly improved my ability to integrate third-party PHP components into my own applications. Interfaces are not a new feature, but they are an important feature that you should know about and use on a daily basis.
So what is a PHP interface? An interface is a contract between two PHP objects that lets one object depend not on what another object is but, instead, on what another object can do. An interface decouples our code from its dependencies, and it allows our code to depend on any third-party code that implements the expected interface. We don’t care how the third-party code implements the interface; we care only that the third-party code does implement the interface. Here’s a more down-to-earth example.
Let’s pretend I just arrived in Miami, Florida for the Sunshine PHP Developer Conference. I need a way to get around town, so I head straight for the local car rental place. They have a tiny Hyundai compact, a Subaru wagon, and (much to my surprise) a Bugatti Veyron. I know I need a way to get around town, and all three vehicles can help me do that. But each vehicle does so differently. The Hyundai Accent is OK, but I’d like something with a bit more oomph. I don’t have kids, so the wagon has more seating than I need. I’ll take the Bugatti, please.
The reality is that I can drive any of these three cars because they all share a common and expected interface. Each car has a steering wheel, a gas pedal, a brake pedal, and turn signals, and each uses gasoline for fuel. The Bugatti is probably more power than I can handle, but the driving interface is the same as the Hyundai’s. Because all three cars share the same expected interface, and I have the opportunity to choose my preferred vehicle (and if we’re being honest, I’d probably go with the Hyundai).
This is the exact same concept in object-oriented PHP. If I write code that expects an object of a specific class (and therefore a specific implementation), my code’s utility is inherently limited because it can only use objects of that one class, forever. However, if I write code that expects an interface, my code immediately knows how to use any object that implements that interface. My code does not care how the interface is implemented; my code cares only that the interface is implemented. Let’s drive this home with a demo.
I have a hypothetical PHP class named DocumentStore that collects text from different
sources: it fetches HTML from remote URLs; it reads stream resources; and it collects
terminal command output. Each document stored in a DocumentStore instance has a unique ID.
Example 2-6 shows the DocumentStore class.
classDocumentStore{protected$data=[];publicfunctionaddDocument(Documentable$document){$key=$document->getId();$value=$document->getContent();$this->data[$key]=$value;}publicfunctiongetDocuments(){return$this->data;}}
How exactly does this work if the addDocument() method only accepts instances of
the Documentable class? That’s a good observation. However, Documentable is not a
class. It’s an interface, and it looks like Example 2-7.
interfaceDocumentable{publicfunctiongetId();publicfunctiongetContent();}
This interface definition says that any object implementing the Documentable interface
must provide a public getId() method and a public getContent() method.
So how exactly is this helpful? It’s helpful because we can create separate document-fetching classes with wildly different implementations. Example 2-8 shows an implementation that can fetch HTML from a remote URL with curl.
classHtmlDocumentimplementsDocumentable{protected$url;publicfunction__construct($url){$this->url=$url;}publicfunctiongetId(){return$this->url;}publicfunctiongetContent(){$ch=curl_init();curl_setopt($ch,CURLOPT_URL,$this->url);curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,3);curl_setopt($ch,CURLOPT_FOLLOWLOCATION,1);curl_setopt($ch,CURLOPT_MAXREDIRS,3);$html=curl_exec($ch);curl_close($ch);return$html;}}
Another implementation (Example 2-9) can read a stream resource.
classStreamDocumentimplementsDocumentable{protected$resource;protected$buffer;publicfunction__construct($resource,$buffer=4096){$this->resource=$resource;$this->buffer=$buffer;}publicfunctiongetId(){return'resource-'.(int)$this->resource;}publicfunctiongetContent(){$streamContent='';rewind($this->resource);while(feof($this->resource)===false){$streamContent.=fread($this->resource,$this->buffer);}return$streamContent;}}
And another implementation (Example 2-10) can fetch the result of a terminal command.
classCommandOutputDocumentimplementsDocumentable{protected$command;publicfunction__construct($command){$this->command=$command;}publicfunctiongetId(){return$this->command;}publicfunctiongetContent(){returnshell_exec($this->command);}}
Example 2-11 shows how we can use the DocumentStore class with our three document-collecting implementations.
<?php$documentStore=newDocumentStore();// Add HTML document$htmlDoc=newHtmlDocument('https://php.net');$documentStore->addDocument($htmlDoc);// Add stream document$streamDoc=newStreamDocument(fopen('stream.txt','rb'));$documentStore->addDocument($streamDoc);// Add terminal command document$cmdDoc=newCommandOutputDocument('cat /etc/hosts');$documentStore->addDocument($cmdDoc);print_r($documentStore->getDocuments());
This is really cool because the HtmlDocument, StreamDocument, and CommandOutputDocument classes
have nothing in common other than a common interface.
At the end of the day, coding to an interface creates more-flexible code that delegates implementation concerns to others. Many more people (e.g., your office buddies, your open source project’s users, or developers you’ve never met) can write code that works seamlessly with your code by knowing nothing more than an interface.
Many of my PHP developer friends are confused by traits, a new concept introduced in PHP 5.4.0. Traits behave like classes but look like interfaces. Which one are they? Neither and both.
A trait is a partial class implementation (i.e., constants, properties, and methods) that can be mixed into one or more existing PHP classes. Traits work double duty: they say what a class can do (like an interface), and they provide a modular implementation (like a class).
You may be familiar with traits in other languages. For example, PHP traits are similar to Ruby’s composable modules, or mixins.
The PHP language uses a classical inheritance model. This means you start with a single generalized root class that provides a base implementation. You extend the root class to create more specialized classes that inherit their immediate parent’s implementation. This is called an inheritance hierarchy, and it is a common pattern used by many programming languages.
If it helps, picture yourself back in grade school Biology. Remember how you learned about the biological classification system? There are six kingdoms. Each kingdom is extended by phyla. Each phylum is extended by biological classes. Classes are extended by orders, orders by families, families by genera, and genera by species. Each hierarchy extension represents further specialization.
The classical inheritance model works well most of the time. However,
what do we do if two unrelated PHP classes need to exhibit similar behavior?
For example, a PHP class RetailStore and another PHP class Car
are very different classes and don’t share a common parent in their
inheritance hierarchies. However, both classes should be geocodable
into latitude and longitude coordinates for display on a map.
Traits were created for exactly this purpose. They enable modular implementations that can be injected into otherwise unrelated classes. Traits also encourage code reuse.
My first (bad) reaction is to create a common parent class Geocodable
that both RetailStore and Car extend. This is a bad solution
because it forces two otherwise unrelated classes to share a common
ancestor that does not naturally belong in either inheritance hierarchy.
My second (better) reaction is to create a Geocodable interface that
defines which methods are required to implement the geocoding behavior.
The RetailStore and Car classes can both implement the Geocodable
interface. This is a good solution that allows each class to retain
its natural inheritance hierarchy, but it requires us to duplicate the
same geocoding behavior in both classes. This is not a DRY solution.
DRY is an acronym for Do not repeat yourself. It’s considered a good practice never to duplicate the same code in multiple locations. You should not need to change code in one location because you changed code in another location. Read more on Wikipedia.
My third (best) reaction is to create a Geocodable trait that defines
and implements the geocodable methods. I can then mix the Geocodable
trait into both the RetailStore and Car classes without polluting
their natural inheritance hierarchies.
Here’s how you define a PHP trait:
<?phptraitMyTrait{// Trait implementation goes here}
It is considered a good practice to define only one trait per file, just like class and interface definitions.
Let’s return to our Geocodable example to better demonstrate traits in
practice. We agree both RetailStore and Car classes need to provide
geocodable behavior, and we’ve decided inheritance and interfaces are
not the best solution. Instead, we create a Geocodable trait that
returns latitude and longitude coordinates that we can plot on a map.
Our complete Geocodable trait looks like Example 2-12.
<?phptraitGeocodable{/** @var string */protected$address;/** @var \Geocoder\Geocoder */protected$geocoder;/** @var \Geocoder\Result\Geocoded */protected$geocoderResult;publicfunctionsetGeocoder(\Geocoder\GeocoderInterface$geocoder){$this->geocoder=$geocoder;}publicfunctionsetAddress($address){$this->address=$address;}publicfunctiongetLatitude(){if(isset($this->geocoderResult)===false){$this->geocodeAddress();}return$this->geocoderResult->getLatitude();}publicfunctiongetLongitude(){if(isset($this->geocoderResult)===false){$this->geocodeAddress();}return$this->geocoderResult->getLongitude();}protectedfunctiongeocodeAddress(){$this->geocoderResult=$this->geocoder->geocode($this->address);returntrue;}}
The Geocodable trait defines only the properties and methods necessary
to implement the geocodable behavior. It does not do anything else.
Our Geocodable trait defines three class properties: an address
(string), a geocoder object (an instance of \Geocoder\Geocoder from
the excellent willdurand/geocoder component by
William Durand), and a geocoder result object (an instance of \Geocoder\Result\Geocoded).
We also define four public methods and one protected method. The setGeocoder() method is
used to inject the Geocoder object. The setAddress() method is used to
set an address. The getLatitude() and getLongitude() methods return
their respective coordinates. And the geocodeAddress() method
passes the address string into the Geocoder instance to retrieve the
geocoder result.
Using a PHP trait is easy. Add the code use MyTrait; inside a
PHP class definition. Here’s an example. Obviously, replace MyTrait
with the appropriate PHP trait name:
<?phpclassMyClass{useMyTrait;// Class implementation goes here}
Both namespaces and traits are imported with the use keyword. Where
they are imported is different. We import namespaces, classes, interfaces,
functions, and constants outside of a class definition. We import traits
inside a class definition. The difference is subtle but important.
Let’s return to our Geocodable example. We defined the Geocodable
trait in Example 2-12. Let’s update our RetailStore class so that it uses the
Geocodable trait (Example 2-13). For the sake of brevity, I do not provide the
complete RetailStore class implementation.
<?phpclassRetailStore{useGeocodable;// Class implementation goes here}
That’s all we have to do. Now each RetailStore instance can use the
properties and methods provided by the Geocodable trait, as shown in Example 2-14.
<?php$geocoderAdapter=new\Geocoder\HttpAdapter\CurlHttpAdapter();$geocoderProvider=new\Geocoder\Provider\GoogleMapsProvider($geocoderAdapter);$geocoder=new\Geocoder\Geocoder($geocoderProvider);$store=newRetailStore();$store->setAddress('420 9th Avenue, New York, NY 10001 USA');$store->setGeocoder($geocoder);$latitude=$store->getLatitude();$longitude=$store->getLongitude();echo$latitude,':',$longitude;
The PHP interpreter copies and pastes traits into class definitions at compile time, and it does not protect against incompatibilities introduced by this action. If your PHP trait assumes a class property or method exists (that is not defined in the trait itself), be sure those properties and methods exist in the appropriate classes.
PHP generators are an underutilized yet remarkably helpful feature introduced in PHP 5.5.0. I think many PHP developers are unaware of generators because their purpose is not immediately obvious. Generators are simple iterators. That’s it.
Unlike your standard PHP iterator, PHP generators don’t require you to
implement the Iterator interface in a heavyweight class. Instead,
generators compute and yield iteration values on-demand. This has
profound implications for application performance. Think about it.
A standard PHP iterator often iterates in-memory, precomputed
data sets. This is inefficient, especially with large and formulaic
data sets that can be computed instead. This is why we use generators
to compute and yield subsequent values on the fly without commandeering
valuable memory.
PHP generators are not a panacea for your iteration needs. Because generators never know the next iteration value until asked, it’s impossible to rewind or fast-forward a generator. You can iterate in only one direction—forward. Generators are also a once-and-done deal. You can’t iterate the same generator more than once. However, you are free to rebuild or clone a generator if necessary.
Generators are easy to create because they are just PHP functions that
use the yield keyword one or more times. Unlike regular PHP functions,
generators never return a value. They only yield values. Example 2-15 shows a simple generator.
<?phpfunctionmyGenerator(){yield'value1';yield'value2';yield'value3';}
Pretty simple, huh? When you invoke the generator function, PHP returns an
object that belongs to the Generator class. This object can be iterated
with the foreach() function. During each iteration, PHP asks the Generator
instance to compute and provide the next iteration value. What’s neat is
that the generator pauses its internal state whenever it yields a value.
The generator resumes internal state when it is asked for the next value. The generator
continues pausing and resuming until it reaches the end of its
function definition or an empty return; statement. We can invoke and
iterate the generator in Example 2-15 like this:
<?phpforeach(myGenerator()as$yieldedValue){echo$yieldedValue,PHP_EOL;}
This outputs:
value1value2value3
I like to demonstrate how a PHP generator saves memory by implementing
a simple range() function. First, let’s do it the wrong way (Example 2-16).
<?phpfunctionmakeRange($length){$dataset=[];for($i=0;$i<$length;$i++){$dataset[]=$i;}return$dataset;}$customRange=makeRange(1000000);foreach($customRangeas$i){echo$i,PHP_EOL;}
Example 2-16 makes poor use of memory. The makeRange() method in Example 2-16 allocates one million integers into a precomputed array.
A PHP generator can do the same thing while allocating memory for only one integer at any given time, as shown in Example 2-17.
<?phpfunctionmakeRange($length){for($i=0;$i<$length;$i++){yield$i;}}foreach(makeRange(1000000)as$i){echo$i,PHP_EOL;}
This is a contrived example. However, just imagine all of the potential data sets that you can compute. Number sequences (e.g., Fibonacci) are an obvious candidate. You can also iterate a stream resource. Imagine you need to iterate a 4 GB comma-separated value (CSV) file and your virtual private server (VPS) has only 1 GB of memory available to PHP. There’s no way you can pull the entire file into memory. Example 2-18 shows how we can use a generator instead!
<?phpfunctiongetRows($file){$handle=fopen($file,'rb');if($handle===false){thrownewException();}while(feof($handle)===false){yieldfgetcsv($handle);}fclose($handle);}foreach(getRows('data.csv')as$row){print_r($row);}
This example allocates memory for only one CSV row at a time instead of reading the entire 4 GB CSV file into memory. It also encapsulates the iteration implementation into a tidy package; this lets us quickly change how we get data (e.g., CSV, XML, JSON) without interrupting our application code that iterates the data.
Generators are a tradeoff between versatility and simplicity. Generators are forward-only iterators. This means you cannot use a generator to rewind, fast-forward, or seek a data set. You can only ask a generator to compute and yield its next value. Generators are most useful for iterating large or numerically sequenced data sets with only a tiny amount of system memory. They are also useful for accomplishing the same simple tasks as larger iterators with less code.
Generators do not add functionality to PHP. You can do what generators
do without a generator. However, generators greatly simply certain tasks
while using less memory. If you require more versatility to rewind,
fast-forward, or seek through a data set, you’re better off writing a
custom class that implements the Iterator interface,
or using one of PHP’s prebuilt Standard PHP Library (SPL) iterators.
For more generator examples, read What Generators Can Do For You by Anthony Ferrara (@ircmaxell on Twitter).
Closures and anonymous functions were introduced in PHP 5.3.0, and they’re two of my favorite and most used PHP features. They sound scary (at least I thought so when I first learned about them), but they’re actually pretty simple to understand. They’re extremely useful tools that every PHP developer should have in the toolbox.
A closure is a function that encapsulates its surrounding state at the time it is created. The encapsulated state exists inside the closure even when the closure lives after its original environment ceases to exist. This is a difficult concept to grasp, but once you do it’ll be a life-changing moment.
An anonymous function is exactly that—a function without a name. Anonymous functions can be assigned to variables and passed around just like any other PHP object. But it’s still a function, so you can invoke it and pass it arguments. Anonymous functions are especially useful as function or method callbacks.
Closures and anonymous functions are, in theory, separate things. However, PHP considers them to be one and the same. So when I say closure, I also mean anonymous function. And vice versa.
PHP closures and anonymous functions use the same syntax as a
function, but don’t let them fool you. They’re actually objects disguised as
PHP functions. If you inspect a PHP closure or anonymous function, you’ll
find they are instances of the Closure class. Closures are considered first-class
value types, just like a string or integer.
So we know PHP closures look like functions. You should not be surprised, then, that you create a PHP closure like Example 2-19.
<?php$closure=function($name){returnsprintf('Hello %s',$name);};echo$closure("Josh");// Outputs --> "Hello Josh"
That’s it. Example 2-19 creates a closure object and assigns it
to the $closure variable. It looks like a standard PHP function:
it uses the same syntax, it accepts arguments, and it returns a value.
However, it does not have a name.
We can invoke the $closure variable because the variable’s value is a closure,
and closure objects implement the \__invoke() magic method. PHP looks for and calls
the __invoke() method whenever () follows a variable name.
I typically use PHP closure objects as function and method callbacks. Many PHP functions expect callback functions, like array_map()
and preg_replace_callback(). This is a perfect opportunity to use PHP anonymous functions!
Remember, closures can be passed into other PHP functions as arguments, just like
any other value. In Example 2-20, I use a closure object as a callback argument
in the array_map() function.
<?php$numbersPlusOne=array_map(function($number){return$number+1;},[1,2,3]);print_r($numbersPlusOne);// Outputs --> [2,3,4]
OK, so that wasn’t that impressive. But remember, before closures PHP developers had no choice but to create a separate named function and refer to that function by name. This was slightly slower to execute, and it segregated a callback’s implementation from its usage. Old-school PHP developers used code like this:
<?php// Named callback implementationfunctionincrementNumber($number){return$number+1;}// Named callback usage$numbersPlusOne=array_map('incrementNumber',[1,2,3]);print_r($numbersPlusOne);
This code works, but it’s not as succinct and tidy as Example 2-20. We don’t need a separate incrementNumber()
named function if we use the function only once as a callback. Closures
used as callbacks create more concise and legible code.
So far I’ve demonstrated nameless (or anonymous) functions used as callbacks.
Let’s explore how to attach and enclose state with a PHP closure. JavaScript developers
might be confused by PHP closures because they do not automatically enclose application
state like true JavaScript closures. Instead, you must manually attach state
to a PHP closure with the closure object’s bindTo() method or the use keyword.
It’s far more common to attach closure state with the use keyword, so let’s look
at that first (Example 2-21). When you attach a variable to a closure via the use keyword, the attached variable
retains the value assigned to it at the time it is attached to the closure.
<?phpfunctionenclosePerson($name){returnfunction($doCommand)use($name){returnsprintf('%s, %s',$name,$doCommand);};}// Enclose "Clay" string in closure$clay=enclosePerson('Clay');// Invoke closure with commandecho$clay('get me sweet tea!');// Outputs --> "Clay, get me sweet tea!"
In Example 2-21, the enclosePerson() named function accepts
a $name argument, and it returns a closure object that encloses
the $name argument. The returned closure object preserves the $name
argument’s value even after the closure exits the enclosePerson()
function’s scope. The $name variable still exists in the closure!
You can pass multiple arguments into a closure with the use keyword.
Separate multiple arguments with a comma, just as you do with
any PHP function or method arguments.
Don’t forget, PHP closures are objects. Each closure instance has its
own internal state that is accessible with the $this keyword just like
any other PHP object. A closure object’s default state is pretty boring;
it has a magic __invoke() method and a bindTo() method. That’s it.
However, the bindTo() method opens the door to some interesting possibilities.
This method lets us bind a Closure object’s internal state to a
different object. The bindTo() method accepts an important second
argument that specifies the PHP class of the object to which the closure
is bound. This lets the closure access protected and private member
variables of the object to which it is bound.
You’ll find the bindTo() method is often used by
PHP frameworks that map route URLs to anonymous callback functions.
Frameworks accept an anonymous function and bind it to the application
object. This lets you reference the primary application object inside the
anonymous function with the $this keyword, as shown in Example 2-22.
01.<?php02.classApp03.{04.protected$routes=array();05.protected$responseStatus='200 OK';06protected$responseContentType='text/html';07.protected$responseBody='Hello world';08.09.publicfunctionaddRoute($routePath,$routeCallback)10.{11.$this->routes[$routePath]=$routeCallback->bindTo($this,__CLASS__);12.}13.14.publicfunctiondispatch($currentPath)15.{16.foreach($this->routesas$routePath=>$callback){17.if($routePath===$currentPath){18.$callback();19.}20.}21.22.header('HTTP/1.1 '.$this->responseStatus);23.header('Content-type: '.$this->responseContentType);24.header('Content-length: '.mb_strlen($this->responseBody));25.echo$this->responseBody;26.}27.}
Pay close attention to the addRoute() method. It accepts a route
path (e.g., /users/josh) and a route callback. The dispatch() method
accepts the current HTTP request path and invokes the matching route callback.
The magic happens on line 11 when we bind the route callback to the current
App instance. This lets us create a callback function that can manipulate
the App instance state:
<?php$app=newApp();$app->addRoute('/users/josh',function(){$this->responseContentType='application/json;charset=utf8';$this->responseBody='{"name": "Josh"}';});$app->dispatch('/users/josh');
Bytecode caches are not new to PHP. We’ve had optional standalone extensions like Alternative PHP Cache (APC), eAccelerator, ionCube, and XCache. But none of these was built into the PHP core distribution until now. As of PHP 5.5.0, PHP has its own built-in bytecode cache called Zend OPcache.
First, let me explain what a bytecode cache is and why it is important. PHP is an interpreted language. When the PHP interpreter executes a PHP script, the interpreter parses the PHP script code, compiles the PHP code into a set of existing Zend Opcodes (machine-code instructions), and executes the bytecode. This happens for each PHP file during every request. This is a lot of overhead, especially if PHP must parse, compile, and execute PHP scripts over and over again for every HTTP request. If only there were a way to cache precompiled bytecode to reduce application response times and reduce stress on our system resources. You’re in luck.
A bytecode cache stores precompiled PHP bytecode. This means the PHP interpreter does not need to read, parse, and compile PHP code on every request. Instead, the PHP interpreter can read the precompiled bytecode from memory and execute it immediately. This is a huge timesaver and can drastically improve application performance.
Zend OPcache isn’t enabled by default; you must explicitly enable Zend OPcache when you compile PHP.
If you choose a shared web host, be sure you choose a good hosting company that provides PHP 5.5.0 or newer with Zend OPcache enabled.
If you compile PHP yourself (i.e., on a VPS or dedicated server),
you must include this option in your PHP ./configure command:
--enable-opcache
After you compile PHP, you must also specify the path to the Zend OPcache extension in your php.ini file with this line:
zend_extension=/path/to/opcache.so
The Zend OPcache extension file path is displayed immediately after PHP compiles successfully. If you forget to look for this as I often do, you can also find the PHP extension directory with this command:
php-config --extension-dir
If you use the popular Xdebug profiler by the incomparable Derick Rethans, your php.ini file must load the Zend OPcache extension before Xdebug.
After you update the php.ini file, restart the PHP process and you’re ready to go. You can confirm Zend OPcache is working correctly by creating a PHP file with this content:
<?phpphpinfo();
View this PHP file in a web browser and scroll down until you see the Zend OPcache extension section shown in Figure 2-2. If you don’t see this section, Zend OPcache is not running.
When Zend OPcache is enabled, you should configure the Zend OPcache settings in your php.ini configuration file. Here are the OPcache settings I like to use:
opcache.validate_timestamps=1 // "0" in productionopcache.revalidate_freq=0opcache.memory_consumption=64opcache.interned_strings_buffer=16opcache.max_accelerated_files=4000opcache.fast_shutdown=1
This part’s easy because the Zend OPcache works automatically when enabled. Zend OPcache automatically caches precompiled PHP bytecode in memory and executes the bytecode if available.
Be careful if the opcache.validate_timestamps INI directive is false. When
this setting is false, the Zend OPcache does not know about changes to your
PHP scripts, and you must manually clear Zend OPcache’s bytecode cache before
it recognizes changes to your PHP files. This setting is good for production
but inconvenient for development. You can enable automatic cache revalidation
with these php.ini configuration settings:
opcache.validate_timestamps=1opcache.revalidate_freq=0
Did you know that PHP has a built-in web server as of PHP 5.4.0? This is another hidden gem unknown to PHP developers who assume they need Apache or nginx to preview PHP applications. You shouldn’t use it for production, but PHP’s built-in web server is a perfect tool for local development.
I use PHP’s built-in web server every day, whether I’m writing PHP or not. I use it to preview Laravel and Slim Framework applications. I use it while building websites with the Drupal content-management framework. I also use it to preview static HTML and CSS if I’m just building out markup.
Remember, the PHP built-in server is a web server. It speaks HTTP, and it can serve static assets in addition to PHP files. It’s a great way to write and preview HTML locally without installing MAMP, WAMP, or a heavyweight web server.
It’s easy to start the PHP web server. Open your terminal application, navigate to your project’s document root directory, and execute this command:
php -S localhost:4000
This command starts a new PHP web server accessible at localhost. It listens on port 4000. Your current working directory is the web server’s document root.
You can now open your web browser and navigate to http://localhost:4000 to
preview your application. As you browse your application in your web browser, each
HTTP request is logged to standard out in your terminal application so you can see
if you application throws 400 or 500 responses.
Sometimes it’s useful to access the PHP web server from other machines
on your local network (e.g., for previewing on your iPad or local Windows box).
To do this, tell the PHP web server to listen on all interfaces by using
0.0.0.0 instead of localhost:
php-S0.0.0.0:4000
When you are ready to stop the PHP web server, close your terminal application or press Ctrl+C.
It’s not uncommon for an application to require its own
PHP INI configuration file, especially if it has unique
requirements for memory usage, file uploads, profiling,
or bytecode caching. You can tell the PHP built-in
server to use a specific INI file with the -c option:
php -S localhost:8000 -c app/config/php.ini
It’s a good idea to keep the custom INI file beneath the application’s root directory and, optionally, version-control the INI file if it should be shared with other developers on your team.
The PHP built-in server has one glaring omission. Unlike Apache or nginx, it doesn’t support .htaccess files. This makes it difficult to use front controllers that are common in many popular PHP frameworks.
A front controller is a single PHP file to which all HTTP requests are forwarded (via .htaccess files or rewrite rules). The front-controller PHP file is responsible for routing the request and dispatching the appropriate PHP code. This is a common pattern used by Symfony and other popular frameworks.
The PHP built-in server mitigates this omission with router scripts. The router script is executed before every HTTP request. If the router script returns false, the static asset referenced by the current HTTP request URI is returned. Otherwise, the output of the router script is returned as the HTTP response body. In other words, if you use a router script you’re effectively hardcoding the same functionality as an .htaccess file.
Using a router script is easy. Just pass the PHP script file path as a an argument when you start up the PHP built-in server:
php -S localhost:8000 router.php
Sometimes it’s helpful to know if your PHP script is served by PHP’s
built-in web server versus a traditional web server like Apache or
nginx. Perhaps you need to set specific headers for nginx (e.g.,
Status:) that should not be set for the PHP web server. You can detect
the PHP web server with the php_sapi_name() function. This function
returns the string cli-server if the current script is served with
the PHP built-in server:
<?phpif(php_sapi_name()==='cli-server'){// PHP web server}else{// Other web server}
PHP’s built-in web server should not be used for production. It is for local development only. If you use the PHP built-in web server on a production machine, be prepared for a lot of disappointed users and a flood of Pingdom downtime notifications.
The built-in server performs suboptimally because it handles one request at a time, and each HTTP request is blocking. Your web application will stall if a PHP file must wait on a slow database query or remote API response.
The built-in server supports only a limited number of mimetypes.
The built-in server has limited URL rewriting with router scripts. You’ll need Apache or nginx for more advanced URL rewrite behavior.
The modern PHP language has a lot of powerful features that can improve your applications. I’ve talked about my favorite features in this chapter. You can learn more about PHP’s latest features on the PHP website.
I’m sure you’re excited to start using these fun features in your applications. However, it’s important that you use these features correctly according to PHP community standards. And that’s exactly what we talk about in the next chapter.