Learn Continuously

As one of the best developers, you know you have to stay on top of all the new technologies. You know that you have to have a trusted training partner you can turn to when you need to learn something new.

We are your trusted training partner. We announce new training events on our mailing list often. Join us and come learn with us.

Our Students

Your needs are unique. What you need to learn depends largely on your career path and current level. We teach students at each level differently.

Senior Devs

Require a deep knowledge with very little hand-holding.

Mid-Level Devs

Deep learning with the ability to ask questions to fully understand.

New Devs

Need instructors who know how to lead them to understanding.

Our Instructors

The best instructors to teach developers are professional developers. We are professional developers.

Andrew Caya

Andrew Caya

Senior Consultant and Trainer

Andrew Caya is a Zend Certified PHP Engineer and a Zend Certified Architect. He is also the creator of Linux for PHP and LightMVC, the lead developer of a popular Joomla extension and a contributor to many open source projects.
Cal Evans

Cal Evans

Senior Consultant and Trainer

For the past 15 years Cal has worked with PHP and MySQL on Linux, OSX, and Windows. He has built a variety of projects ranging in size from simple web pages to multi-million dollar web applications.
Doug Bierer

Doug Bierer

Senior Consultant and Trainer

Doug is certified in PHP 5.1, 5.3, 5.5 and 7.1, Zend Framework 1 and 2. He has authored a bunch of books and videos for O'Reilly / Packt Publishing on PHP, Security and MongoDB.

Recent Blog Posts

Grabbing a Filtered Directory Tree Using PHP Iteration

Iterate or not to Iterate … that is the question!  Excuses for misquoting Shakespeare dear readers, but I had to grab your attention away from your current attention-grabbing-addiction somehow!  So, now that I’ve got your attention (presumably not having lost it by now!), I would like to discuss the pressing topic of grabbing entire directory trees with a single command.  Granted, this would normally be a rather mundane task, so to add a further twist, I want to exclude certain directories at the same time.

RecursiveDirectoryIterator

In the SPL (Standard PHP Library) there lives an incredibly useful iterator known as RecursiveDirectoryIterator.  As an example, we parse the directory structure of a typical Laminas project:

├── composer.json
├── config
│   ├── application.config.php
│   ├── autoload
│   │   ├── global.php
│   │   └── laminas-developer-tools.local-development.php
│   ├── development.config.php.dist
│   └── modules.config.php
├── COPYRIGHT.md
├── data
│   └── cache
├── docker-compose.yml
├── Dockerfile
├── LICENSE.md
├── module
│   ├── Application
│   │   ├── config
│   │   │   └── module.config.php
│   │   ├── src
│   │   │   ├── Controller
│   │   │   │   ├── IndexControllerFactory.php
│   │   │   │   └── IndexController.php
│   │   │   ├── Module.php
│   │   │   └── Service
│   │   │       └── Calendar.php
│   │   ├── test
│   │   │   └── Controller
│   │   │       └── IndexControllerTest.php
│   │   └── view
│   │       ├── application
│   │       │   └── index
│   │       │       ├── calendar.phtml
│   │       │       └── index.phtml
│   │       ├── error
│   │       │   ├── 404.phtml
│   │       │   └── index.phtml
│   │       └── layout
│   │           └── layout.phtml
├── public
│   ├── css
│   ├── img
│   ├── index.php
│   ├── js
│   └── web.config
├── README.md
├── Vagrantfile
└── vendor
    ├── autoload.php
    ├── bin
    ├── composer
    │   ├── autoload_classmap.php
    │   └── LICENSE
    ├── laminas
    │   ├── laminas-component-installer
    │   ├── laminas-config
    etc.

As you can imagine, the vendor directory has a ton of open-source software installed via Composer.  Further, the public directory includes lots of stylesheets and JavaScript.  So the task at hand is to iterate through the directory structure, excluding these two directories.  Your first thought might be to define a path and create a RecursiveDirectoryIterator and be done with it.  Throw in a simple foreach() loop and we’re good to go, right?  (Don’t answer!  Rhetorical question.) Before we dive into the code, please be aware that by default RecursiveDirectoryIterator returns an iteration with the full filename (including path) as a key, and an SplFileInfo object as the value.

So, let’s get down to producing some code to achieve the desired results.  A good place to start might be to define a function (or class method) that determines the acceptance criteria.  In this case we want to be able to exclude one or more directory paths from the final output.  Thus we define a simple function accept() that returns FALSE if the given path includes any of the directory paths in the $excludes array:

function accept(string $name, array $excludes = [])
{
    $result = TRUE;
    if ($excludes) {
        foreach ($excludes as $item) {
            if (strpos($name, $item) !== FALSE) {
                $result = FALSE;
                break;
            }
        }
    }
    return $result;
}

Next we define a function show() that performs the actual iteration, using accept() to include or exclude iteration entries.

function show(string $path, Iterator $iteration, array $excludes)
{
    $output = '';
    foreach ($iteration as $key => $value)
        if (accept($key, $excludes))
            $output .= str_replace($path, '', $key) . "\n";
    return $output;
}

Finally, we create the RecursiveDirectoryIterator instance, giving it the initial path, and a flag to eliminate the “dot” directories (e.g. “.” and “..”).

$path = '/path/to/laminas_project';
$excludes = ['/vendor','/public'];
$iteration = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
echo show($path, $iteration, $excludes);

And here is the resulting output:

README.md
module
phpcs.xml
composer.phar
COPYRIGHT.md
composer.json
docker-compose.yml
.gitignore
Vagrantfile
phpunit.xml.dist
data
config
composer.json.bak
CHANGELOG.md
Dockerfile
LICENSE.md
composer.lock

Wait, you might cry out (well, maybe not, but for the sake of argument, imagine an outraged developer screaming insults at the PHP engine :-), what happened to all the subdirectories and associated files?  Good question!  Oddly, the RecursiveDirectoryIterator was doing its job!  It returns the first entry in the path specified, and the recursively continues to provide subsequent entries in the path specified.  So, in the case of the RecursiveDirectoryIterator, its recursion isn’t that it goes “deep”, but rather that it goes through the entire directory path specified before it stops.  To up its game so to speak, we need to call upon the mighty RecursiveIteratorIterator class.

RecursiveIteratorIterator

The relationship between any given iterator and RecursiveIteratorIterator is like that of a bodybuilder to steroids.  This class causes the associated iterator to continue to iterate until all child nodes have been explored.  When associated with RecursiveDirectoryIterator, it is perfect for parsing entire directory sub-trees.  One word of caution, however, is that if you point it to the wrong path, especially paths with thousands of files and hundreds of subdirectories … you can quickly enter PHP Fatal Error territory. That consideration aside, RecursiveIteratorIterator is a really cool classname, isn’t it?  It gives one a warm fuzzy Department-Of-Redundancy-Department kind of feeling doesn’t it?  (Monty Python Fans take note!)

All joking aside, let’s have a look at its application to the code described above.  Really, the only thing that needs to be done is to wrap the RecursiveDirectoryIterator instance into a RecursiveIteratorIterator instance, and we’re good to go.  The modified code might appear as follows:

$iteration = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
$recurse = new RecursiveIteratorIterator($iteration);
echo show($path, $recurse, $excludes);

And here are the results we expected from the start:

README.md
module/Signups/view/signups/index/events.phtml
module/Signups/view/signups/index/index.phtml
module/Signups/src/Module.php
module/Signups/src/Controller/IndexController.php
module/Signups/config/module.config.php
module/Test/view/test/list/index.phtml
module/Test/view/test/index/index.phtml
module/Test/src/Module.php
module/Test/src/Controller/ListController.php
module/Test/src/Controller/IndexController.php
module/Test/config/module.config.php
module/Test/config/module.config.php.bak
module/Application/test/Controller/IndexControllerTest.php
module/Application/view/application/index/calendar.phtml
module/Application/view/application/index/index.phtml
module/Application/view/error/404.phtml
module/Application/view/error/index.phtml
module/Application/view/layout/layout.phtml
module/Application/src/Module.php
module/Application/src/Models/EventsModel.php
module/Application/src/Factory/AdapterFactory.php
module/Application/src/Factory/EventsModelFactory.php
module/Application/src/Controller/IndexControllerFactory.php
module/Application/src/Controller/IndexController.php
module/Application/src/Service/Calendar.php
module/Application/config/module.config.php
phpcs.xml
composer.phar
COPYRIGHT.md
composer.json
docker-compose.yml
.gitignore
Vagrantfile
phpunit.xml.dist
data/cache/.gitkeep
config/application.config.php
config/modules.config.php
config/autoload/README.md
config/autoload/laminas-developer-tools.local-development.php
config/autoload/db.local.php
config/autoload/development.local.php
config/autoload/global.php
config/autoload/.gitignore
config/autoload/local.php.dist
config/autoload/development.local.php.dist
config/development.config.php
config/development.config.php.dist
composer.json.bak
CHANGELOG.md
Dockerfile
LICENSE.md
composer.lock

But wait … there’s more!  Let me pose you a question: wouldn’t it be nice to do all this with a single iterator?  Hah!  That got your attention, didn’t it?  So, without further ado, let’s have a look at the last iterator class to be discussed in this article: FilterIterator.

FilterIterator

As with RecursiveIteratorIterator, the FilterIterator class cannot stand alone: it provides a wrapper for an existing iterator.  But there’s a bigger problem: this class is marked abstract which means you cannot use it directly!  This makes perfect sense when you understand that the abstract method accept() (sound familiar?) simply cannot be defined by the PHP core development team.  They have no idea what needs to be filtered.  Accordingly its definition is left to the developer.  This still doesn’t stop it from being super-annoying, however!  Why do I need to develop an entirely new class which extends FilterIterator just because it’s abstract?  Arghhhh!!!  Hang on folks … there is another way!

Many of us tend to forget one of the most discussed new feature of PHP 7.0: the anonymous class.  This feature was discussed endlessly, and the subject of many an article or blog post.  Eventually it was forgotten and faded into obscurity.  But, it just so happens that an anonymous class might be just the ticket in the situation we are discussing in this article.

Imagine the following:

  • We create an iterator in the form of an anonymous class that extends FilterIterator
  • In the anonymous class we define a static property to contain an array of directory paths to exclude
  • We move the logic from the accept() function described above into a class method
  • VOILA: we’re done!

The only real change that needs to be made in accept() is to not accept any arguments, and substitute parent::current() in place of $name.  If $excludes becomes a public static property, it can be assigned from the calling program. Here is how the alternative code solution might appear:

$iteration = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
$recurse = new RecursiveIteratorIterator($iteration);
$filter = new class($recurse) extends FilterIterator {
    public static $excludes = [];
    public function accept() {
        if (!self::$excludes) return TRUE;
        $actual = 0;
        foreach (self::$excludes as $item)
            if (strpos(parent::current(), $item) !== FALSE) $actual++;
        return !((bool) $actual);
    }
};
$filter::$excludes = $excludes;
foreach ($filter as $key => $value)
    echo str_replace($path, '', $key) . "\n";

An added benefit is that we no longer need the show() function.  In this example the iteration itself already includes filtering, so all we need to do is to iterate through the pre-filtered result.  The resulting output is not shown here as it’s identical to the output from the previous code example.  So, to summarize, a single iterator, FilterIterator, allows us to produce a single iteration that doesn’t need any additional logic.

That’s about all for today dear readers.  Happy coding!

MongoDB BulkWrite: the Highly Misunderstood Method

A Story of Pathos, Drama and Neglect

If a story of neglect, pretty much lacking drama, but loaded with pathos could be written about an OOP method, the main character in the book would be the MongoDB collection level bulkWrite() method.  This highly misunderstood method, often lightly skipped over by most DevOps (myself included!), is often misconstrued for a glorified version of the collection level insertMany() method.  When perusing the documentation for the command, one quickly sees that the syntax, at least at first glance, appears convoluted, and not worth the bother.  However, upon closer examination, come to find out, this potent little method actually packs quite a punch, allowing you to perform not only bulk inserts, but updates and deletes, all in a single command.

Now that I’ve (hopefully) gotten your attention, before diving into the nitty-gritty, the question of why bother needs to be addressed.

Why Bother With a Poor Cousin?

Given that bulkWrite() allows you to perform multiple inserts, deletes and updates in a single command, the next logical question that might come to mind is: why bother?  You could simply issue a series of insert, delete and update commands and be done with it.  Here is an example which adds four documents to the test.users collection, updates Betty to “active” status, and deletes Barney:

<?php
include __DIR__ . '/../vendor/autoload.php';
use MongoDB\Client;
$data = [
    ['key' => 'FRF','first' => 'Fred',  'last' => 'Flintstone','active' => 1],
    ['key' => 'WIF','first' => 'Wilma', 'last' => 'Flintstone','active' => 1],
    ['key' => 'BAR','first' => 'Barney','last' => 'Rubble',    'active' => 0],
    ['key' => 'BER','first' => 'Betty', 'last' => 'Rubble',    'active' => 0],
];
$client = new Client('mongodb://localhost:27017');
// drop users collection from test database
$client->test->users->drop();
$client->test->users->insertMany($data);
$client->test->users->updateOne(['key'=>'BER'],['$set' => ['active' => 1]]);
$client->test->users->deleteOne(['key'=>'BAR']);
$query = $client->test->users->find();
foreach ($query as $document)var_dump($document);

Absolutely nothing wrong with this block of code … except that the insertMany(), updateOne() and deleteOne() calls each cost a round trip to and from the database.  If this example were to be converted into the same operation but using bulkWrite() instead, only one round trip would be required: much more efficient!  The same argument can also apply to your decision on whether or use bulkWrite() or deleteMany() or updateMany().

At this point, at least if you’ve gotten this far, you might be sold on the concept, but want to know more.  Let’s now look at bulkWrite() operations.

What Are BulkWrite Operations?

bulkWrite() operations are a set of pre-defined keys you need to add to the bulk write document that dictate which method is to be called next.  Here is a table that summarizes the operations, and associated options:

Operation Options Arguments
insertOne document <insert doc>
updateOne filter <query doc>
update <update doc>
updateMany filter <query doc>
update <update doc>
replaceOne filter <query doc>
replacement <replacement doc>
deleteOne filter <query doc>

Where the argument mentions “doc”, when running a bulkWrite() operation using the mongo shell, this would be a JSON document.  On the other hand, when running the same operation using the PHP MongoDB library, “doc” would take the form of a PHP associative array.  The operation and options would be  array keys.  The arguments would be a sub-array, itself consisting of key/value pairs.

OK, yes, I hear you: get to the good stuff will you?  Show me how to do it!

Using BulkWrite in the Mongo Shell

The beauty of using the PHP MongoDB Library, which leverages the MongoDB extension, is that you can first model commands using the mongo shell, and then later map the same command almost directly into your PHP app.  First step is to define the bulk write document, including operations and options.

bulkDoc = [
  {"insertOne" : { "document" : {"key" : "FRF", "first" : "Fred","last" : "Flintstone","active" : 1}}},
  {"insertOne" : { "document" : {"key" : "WIF", "first" : "Wilma","last" : "Flintstone","active" : 1}}},
  {"insertOne" : { "document" : {"key" : "BAR", "first" : "Barney","last" : "Rubble","active" : 0}}},
  {"insertOne" : { "document" : {"key" : "BER", "first" : "Betty","last" : "Rubble","active" : 0}}},
  {"updateOne" : { "filter" : {"key" : "BER"}, "update" : {"$set" : {"active" : 1}}}},
  {"deleteOne" : { "filter" : {"key" : "BAR"}}}
]

After that, it’s just a matter of running bulkWrite(), in this example on the test.users collection.  Note that the users collection is first dropped so that we get consistent test results.  We also throw in a find() to view results:

use test;
db.users.drop();
db.users.bulkWrite(bulkDoc);
db.users.find({},{"_id":0});

Here is how the output might appear:

So far, so good, eh?   Now to translate the query using the PHP MongoDB library.

Happily Bulk Writing in PHP

The first step is to translate the query from a JSON document into a PHP array for consumption by the MongoDB\Collection::bulkWrite() method.

$bulkDoc = [
    ['insertOne' => [['key' => 'FRF', 'first' => 'Fred', 'last' => 'Flintstone', 'active' => 1]]], 
    ['insertOne' => [['key' => 'WIF', 'first' => 'Wilma', 'last' => 'Flintstone', 'active' => 1]]], 
    ['insertOne' => [['key' => 'BAR', 'first' => 'Barney','last' => 'Rubble', 'active' => 0]]], 
    ['insertOne' => [['key' => 'BER', 'first' => 'Betty', 'last' => 'Rubble', 'active' => 0]]], 
    ['updateOne' => [['key' => 'BER'], ['$set' => ['active' => 1]]]], 
    ['deleteOne' => [['key' => 'BAR']]]
];

Right away (er … if you’re still awake at this point!), you might notice the double square brackets after each operation key.  The reason for this is because the MongoDB PHP library is not written to accept the keys document, filter and update needed by the equivalent shell method.  However, when the command is transmitted to MongoDB, the array-within-array structure needs to be maintained, thus the redundant square brackets.

Next, as with the shell example, we drop the users collection, run bulkWrite(), and run find() to view results:

$client = new Client('mongodb://localhost:27017');
$client->test->users->drop();
$client->test->users->bulkWrite($bulkDoc);
$query = $client->test->users->find([],['projection' => ['_id' => 0]]);
    foreach ($query as $document)
        vprintf("%4s : %12s : %12s : %2d\n", $document->getArrayCopy());

The result is pretty much the same as above, taking into account the minor formatting difference:

And that about wraps things up.  To summarize: bulkWrite() is an efficient way to perform bulk operations involving a combination of insert, update and/or delete.  Use this command if you need to perform mass operations involving more than just insert, update or delete as it saves round trips between the application and database.

 

 

PHP 7.4: The Strange Case of ArrayObject

When upgrading a website from one version of PHP to the next, there is one phrase that inevitably strikes fear into the heart of even the most intrepid PHP developer: Backwards Compatible Breaks. There … I’ve said it.  Might as well be screaming Voldemort, Voldemort, Voldemort at the top of my lungs in Hogwarts, right?  (Harry Potter fans take note!) OK … but what does this have to do with The Strange Case of ArrayObject?  To explain, let’s first have a look at ArrayObject and get_object_vars().

 

Enter get_object_vars()

Although hotly disputed by many reputable developers, personally I really like get_object_vars().  There are caveats to its use, of course.  For example, if used from the “outside,” it can only read public properties.  Otherwise, it’s extremely simple to use, and is an excellent tool for converting object properties into an associative array.

In the simple code block shown here, an ArrayObject instance is created and initialized with an array.  Running getArrayCopy()  and get_object_vars() should return the same results … right?

$obj = new ArrayObject(['A' => 1,'B' => 2,'C' => 3]);
var_dump($obj->getArrayCopy());
var_dump(get_object_vars($obj));

To test this absurdly simple example, I spun up two Linux for PHP docker containers, one running PHP 7.3, and the other running PHP 7.4.  As expected, when running this example under PHP 7.3, identical results were obtained:

php 7.3 get_object_vars()

Whistling happily to myself, I then ran the exact same code on the PHP 7.4 docker container.  I then had to rub my eyes, clean my glasses (not done often enough, at least according to my wife!), and look again.  It seems there’s something missing …  Have a look for yourselves!

php 7.4 get_object_vars()

At this point, as you may have suspected, I stopped whistling.  Uh … hmmm …  NOTE TO SELF: after upgrading to PHP 7.4, make sure you use getArrayCopy() and not get_object_vars() when working with ArrayObject!

 

It Gets Worse!

BC breaks … BC breaks … I kept mumbling to myself, they’re normal, right?  even expected, right?.  Taking a deep breath, I barreled ahead with my test.  Thinking back to the countless times I’ve used get_object_vars(), I created another simple example.  How many of you have done something like the getVars() method shown here?  (No, don’t answer that question: I don’t want to be the only one in the room to raise my hand!)

class Test extends ArrayObject
{
    public $id = 12345;
    public $name = 'Cal Evans';
    public function getVars()
    {
        return get_object_vars($this);
    }
}

Now, granted, when extending ArrayObject, even running versions of PHP earlier than 7.4, you can expect the unexpected.  Putting the above class to use, here is an example code block:

$test = new Test(['A' => 1,'B' => 2,'C' => 3]);
var_dump($test->getVars());
var_dump($test->getArrayCopy());
var_dump($test);

So, again, a class that extends ArrayObject is initialized with an array.  Now here’s where it gets even more strange.  What you would expect is that get_object_vars() would return both $id as well as $name, in addition to whatever was created upon instantiation, right?  Wrong!  Here is the output from PHP 7.3:

get_object_vars() as method

 

And the output from PHP 7.4:

get_object_vars() as method

On second glance, the output from PHP 7.4 seems to make more sense.  If we run get_object_vars() internally, we would expect it to pick up the internally defined properties.  On the other hand, getArrayCopy() returns the value of the array created upon initialization.  Thus, to get both, the getVars() method would just need to combine the two as shown in this example:

public function getVars()
{
    return array_merge(
        get_object_vars($this),
        $this->getArrayCopy()
    );
}

 

Got It … Anything Else?

Well … now that you ask … yes, there is something else, and please stop moaning and pounding your desk won’t you?  The changes internal to PHP 7.4 that resulted in the behavior demonstrated above also affects array navigation functions such as reset(), current() and next().  In case you haven’t used these functions extensively, they’re “old school” ways of navigating arrays.  reset() moves the array pointer to the “top” of the array (i.e. first element), current() returns the current value, and next() moves the array pointer to the next element.

As before, we start with a simple ArrayObject instance, initialized with an associative array:

$obj = new ArrayObject(['A','B','C']);

Here is the “traditional” way of navigating arrays, using reset(), current() and next():

reset($obj);
while ($item = current($obj)) {
    echo $item . ' ';
    next($obj);
}

And here is the same thing, using iteration methods instead:

$it = $obj->getIterator();
while ($item = $it->current()) {
    echo $item . ' ';
    $it->next();
}

As you probably expected, given that you’ve read this article up to this point, the results in PHP 7.3 are identical:

ArrayObject Iteration

And, as you might also have expected, the traditional array navigation functions don’t work against a PHP 7.4 ArrayObject instance:

ArrayObject Iteration

And that’s all she wrote!

 

Conclusion

Recognition of potential code breaks is half the battle.  As long as you are aware of potential breaks, you know what to look for potential breaks in your application post-upgrade.  As you have from the test code discussed in this post, using the internal methods will return results consist with earlier versions of PHP 7.4.  You can still use get_object_vars(), however you need to be aware that the internally generated storage array is treated differently in PHP 7.4.  This means that it may be necessary to mix the results of get_object_vars() with getArrayCopy() to arrive at the desired results.

All in all, it’s extremely important to bear in mind that a PHP 7.4 upgrade is definitely a good thing.  Performance has been improved, and there are many many enhancements that give you greater control over your code.  To name just one: PHP 7.4 introduces property level types.  These and other improvements will be addressed here in future posts.

SHAMELESS PLUG: we offer an online PHP 7.4 update course that covers, in approximately 2.5 hours, not only backwards compatibility breaks, but also infrastructural changes, language enhancements, and the ability to make direct “C” language calls, and other really cool stuff.

Thanks for reading and Happy Coding!