Pages

Saturday, February 6, 2016

PHP Arrays - The php array functions

Source: Xavier33300 on Flickr.com
In my previous article about arrays (the basicssimple operations and associative arrays or hash maps) I have shown what arrays are and what you can do with it. Now I want to dive into real fun and explain PHP's array functions with real-world examples and how you can apply them in your day-to-day work.

Collection to select form key-values

Many of the website registration forms uses a country select element or an autocomplete input field using data from a database.


If this was a single PHP with HTML combined page, there was no problem as you could easily reuse the query made to collect the data. But when using an MVC approach where your controller needs to push the data it fetches from the backend to the form before the form can be passed to the view you're in for a treat. We're addressing the issues for an MVC approach here.

Let's look at our country table first to get an impression how data is being stored in the database.

+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| iso       | char(2)     | NO   |     | NULL    |                |
| name      | varchar(80) | NO   |     | NULL    |                |
| nicename  | varchar(80) | NO   |     | NULL    |                |
| iso3      | char(3)     | YES  |     | NULL    |                |
| numcode   | smallint(6) | YES  |     | NULL    |                |
| phonecode | int(5)      | NO   |     | NULL    |                |
+-----------+-------------+------+-----+---------+----------------+

In order to use this data in our forms, the two fields "iso" and "printable_name" are most likely the fields that would make the most sense.

Two approaches we can take here:

  1. we query our database for only those fields we need, with the requirement that we need to make a second database call when we need to populate our select field for country phone prefixes
  2. we query all of our required fields "iso", "printable_name" and "phonecode" in one query and use PHP array functions to populate our select fields
We choose to take option 2.

Let's have a look on how most people do things nowadays. The first thing they do is retrieve the data from their backend in the controller. In the controller they prepare their form and pass the form back to the view.

public function registerAction()
{
    
$form $this->getForm('member');

    
$countryMapper = new CountryMapper();
    
$countryList $countryMapper->fetchAll();
    
$countrySelect = [];
    
$phoneSelect = [];
    foreach (
$countryList as $country) {
        
$countrySelect[] = [
            
'iso' => $country['iso'],
            
'nicename' => $country['nicename'],
        ];
        
$phoneSelect[] = [
            
'phonecode' => $country['phonecode'],
            
'nicename' => $country['nicename'],
        ];
    }
    
$form->getElement('country')->addMultiOptions($countrySelect);
    
$form->getElement('phoneprefix')->addMultiOptions($phoneSelect);

    if (isset (
$this->session->memberForm)) {
        
$form unserialize($this->session->memberForm);
        unset (
$this->session->memberForm);
    }
    return [
        
'memberForm' => $form,
    ];
}

So, within the controller a loop is created to populate two field elements "country" and "phoneprefix".  Even though this is an easy approach and works in most cases, the moment you have a lot of data to process, this kind of approach is slowing down your application. Say hello to two power functions: "array_walk" and "array_intersect_key"!

PHP function "array_walk" allows you to apply user functionality to each element of that array. This means we can apply our second functionality "array_intersect_key" on each row.

PHP function "array_intersect_key" gives us the ability to take the common elements from our country list row and the keys we provide as second argument.

$countrySelect = [];
array_walk($countryList, function ($row$key) use (&$countrySelect) {
    
$countrySelect[] = array_intersect_key(
        
$row,
        [
'iso' => null'nicename' => null]
    );
});

$phoneSelect = [];
array_walk($countryList, function ($row$key) use (&$phoneSelect) {
    
$phoneSelect[] = array_intersect_key(
        
$row,
        [
'iso' => null'phonecode' => null]
    );
});

Now we have two arrays that contain only the elements we can just pass on to the form select elements. And the whole controller action now looks like this:

public function registerAction()
{
    
$form $this->getForm('member');

    
$countryMapper = new CountryMapper();
    
$countryList $countryMapper->fetchAll();
    
$countrySelect = [];
    
array_walk($countryList, function ($row$key) use (&$countrySelect) {
        
$countrySelect[] = array_intersect_key(
            
$row,
            [
'iso' => null'nicename' => null]
        );
    }); 

    
$phoneSelect = [];
    
array_walk($countryList, function ($row$key) use (&$phoneSelect) {
        
$phoneSelect[] = array_intersect_key(
            
$row,
            [
'iso' => null'phonecode' => null]
        );
    });
    
$form->getElement('country')->addMultiOptions($countrySelect);
    
$form->getElement('phoneprefix')->addMultiOptions($phoneSelect);

    if (isset (
$this->session->memberForm)) {
        
$form unserialize($this->session->memberForm);
        unset (
$this->session->memberForm);
    }
    return [
        
'memberForm' => $form,
    ];
}

Finding primary keys from a collection

Another common pattern I see is when developers need to pull ID's from a collection of elements to pass on to a next request. Again, foreach is the common used loop to iterate over each element, adding the ID property to another array which then gets passed on to the query.

Let's first look our code that we're going to process.

class PhpFunction {
    public 
$id;
    public 
$label;

    public function 
__construct($id$label)
    {
        
$this->id $id;
        
$this->label $label;
    }
$functions = [
    new 
PhpFunction(123'array_map'),
    new 
PhpFunction(124'array_merge_recursive'),
    new 
PhpFunction(125'array_merge'),
];

Now this example is only containing 3 elements, but when you look at the php.net website there are thousands of functions, so consider this is a huge collection of php functions.

In many cases I see something like the following code to retrieve the ID's from this collection:

$functionIds = [];
foreach (
$functions as $function) {
    
$functionIds[] = $function->id;
}

Even though this is a common pattern, I would prefer to use a native PHP function for processing this. Say hello to "array_map", the function that allows you to use a custom functionality on each element of the given array.

$functionIds array_map(function ($element) {
    return 
$element->id;
}, 
$functions);

The result is exactly the same, but now using PHP's native C power processing huge arrays goes a bit faster than the "foreach"-loop.

Next article we're looking at more common patterns for processing arrays. Love to see you again.



Thursday, January 14, 2016

PHP Arrays - Associative Arrays or Hash Maps


Associative array or hash maps are listings of key and value pairs with a posibility to nest additional keys and values. An associative array is a very powerful construct within PHP.

In our previous article we discussed simple arrays, which in their turn are indexed associative arrays under the hood. Take the following example:

$array = [
  
'apple',
  
'banana',
  
'chocolate',
]; 


Is in fact an indexed associative array under the hood:

$array = [
    
=> 'apple',
    
=> 'banana',
    
=> 'chocolate',
]; 


But associative arrays can be so much more than just an indexed array, and you will find many database operations returning arrays where the fields of a table are the keys in the array while their values are also the values within the array.

$productRowData = [
    
'product_id' => 1234,
    
'brand_id' => 321,
    
'product_name' => 'Our awesome product',
    
'prodcut_description' => 'This is our most awesome product.',
    
'product_sku' => 'ABC1234-XYZ',
    
'product_price' => 59.95,
];


Another example of use is to store configuration information in a PHP array, like Zend Framework does. It's maybe why they call it the "Array Framework". But it's actually a good approach to store configuration settings inside a PHP array as you don't need to parse another format like INI, XML, YAML, JSON or CSV, saving a couple of CPU cycles loading the configuration for your application. Once parsed, it will be represented as an array anyways, maybe serialized for persistance.

$dbConfig = [
    
'db' => [
        
'erp' => [
            
'host' => '123.123.456.456',
            
'port' => 3306,
            
'username' => 'erp_user',
            
'password' => 'v3rRy$ecR3tP@ssMor7! ',
            
'dbname' => 'erp',
        ],
        
'crm' => [
            
'host' => '123.123.426.426',
            
'port' => 3306,
            
'username' => 'crm_user',
            
'password' => ' Y0u L1k3 f1&h 4nD Ch1p$?! ',
            
'dbname' => 'crm',
        ],
    ],
];


How can we iterate over these values? The easiest way is to use "foreach()" as we can define the key and value in the iteration construction:

foreach ($productRowData as $key => $value) {
    echo 
'key : '  $key PHP_EOL;
    echo 
'value: ' $value PHP_EOL;
    echo 
PHP_EOL;
}


For the other loops like "for", "do-while" and "while-do" we need to do a bit of extra work. As we don't have direct access to the "index" of the associative array, we cannot iterate over the keys. So we fetch the keys as a seperate array with "array_keys" function.

$count count($productRowData); 
$keys array_keys($productRowData);

echo 
'== Iteration with for' PHP_EOL;
for (
$i 0$i $count$i++) {
    echo 
'key : '  $keys[$i] . PHP_EOL;
    echo 
'value: ' $productRowData[$keys[$i]] . PHP_EOL;
    echo 
PHP_EOL;
}

echo 
'== Iteration with do-while' PHP_EOL
$i 0;
do {
    echo 
'key : '  $keys[$i] . PHP_EOL;
    echo 
'value: ' $productRowData[$keys[$i]] . PHP_EOL;
    echo 
PHP_EOL;
    
$i++;
} while (
$i $count);

echo 
'== Iteration with while-do' PHP_EOL
$i 0;
while (
$i $count) {
    echo 
'key : '  $keys[$i] . PHP_EOL;
    echo 
'value: ' $productRowData[$keys[$i]] . PHP_EOL;
    echo 
PHP_EOL;
    
$i++;
}


Both loops "for" and "while-do" will first evaluate the given condition before they start their iteration. The "do-while" loop will execute the loop at least once before validating a condition. In some conditions this can be convenient.

What if we want to look for a specific value, should we iterate over each element until we find the value? Let's say we are looking in our configuration for the array containing the value "123.123.426.426" because we found it in one of our logs. A very common practice is the following example:

$searchResult null
$search '123.123.426.426'

foreach (
$dbConfig as $db) {
    foreach (
$db as $target => $dbSettings) {
        if (
$search === $dbSettings['host']) {
            
$searchResult $dbSettings;
        }
    }

// do something with $searchResult

This is a very common practice I come accross and even though it's not a bad practice, there are php array functions available that do the trick as well. In this case, I choose "array_filter" to quickly retrieve the correct array.

foreach ($dbConfig as $db) {
    
$searchResult array_filter($db, function ($v) use ($search) {
        return 
in_array($search$v);
    });

// do something with $searchResult

Above functionality allows us to keep a clear understanding what's going on and we reduce the amount of iterations we need to execute. Consider doing this on an array with a couple of 1000 entries.

In our next article we're going to look a bit more at the various PHP array functions you can use in your day-to-day application development.

Wednesday, January 6, 2016

PHP arrays - simple operations

Source: Flickr.com -  Upupa4me
Like all things in life, we need to start with simple things. So, in order to understand what arrays are in PHP, we need to take a look at the PHP manual to read what it is all about:
An array in PHP is actually an ordered map. A map is a type that associates values to keys. This type is optimized for several different uses; it can be treated as an array, list (vector), hash table (an implementation of a map), dictionary, collection, stack, queue, and probably more.
The thing with PHP is that an array is probably one of the most used data structures in web applications build with PHP and used for a wide variety of purposes.

Let's look at a first usage of an array: storing multiple values in a list.

<?php
$array 
= [
    
'foo',
    
'bar',
    
'baz',
];


This is a PHP array with 3 elements. Now we can add an element at the end of this array.

// Adding an element at the of the array 
array_push($array'foobar');

Or even more efficient:

$array[] = 'barbaz';

Now our array looks like this:

$array = [
    
'foo',
    
'bar',
    
'baz',
    
'foobar',
    
'barbaz',
];


As you can see, PHP arrays are easy to create lists which you can now toss around in your application. But what if you need to reverse the process and remove elments from the end of your array?

// Removing the last element of an array 
$last array_pop($array);

Now $last will contain the value "barbaz" and the array itself is now reduced by one from the end.

$array = [
    
'foo',
    
'bar',
    
'baz',
    
'foobar',
]; 


Of course we can also pull elements from the beginning of the array.

// Removing the first element of an array 
$first array_shift($array); 

Now $first will contain the value "foo" and the array is now reduced by one from the beginning.

$array = [
    
'bar',
    
'baz',
    
'foobar',
]; 


In our next chapter let's have a look at associative arrays and what you can do with those.




Monday, January 4, 2016

PHP arrays - the basics




In 2015 we celebrated the twentieth birthday of PHP and we even had the release of php-7.0.0. Life is all good!

I wish it was all this peachy. In my work as a consultant or when I'm working on OSS projects I cannot help noticing that some of the basics of PHP are missing, especially when dealing with arrays.

To give you an example: developers and site owners often complain about their web application being slow. When I get called in to improve performance, I stumble on a huge number of foreach-loops when processing data retrieved from a database. The first iteration happens right at data retrieval where the result set is iterated over to produce a new array of data models.

/**
 * Fetches all entries, optionally matching provided conditions,
 * optionally ordered by provided order, optionally limited by
 * provided number of entries with an optional provided offset.
 *
 * @param array $where
 * @param array $order
 * @param int $limit
 * @param int $offset
 * @return array
 */
public function fetchAll($where = [], $order = [], $limit = 0, $offset = 0)
{
    $sql = 'SELECT * FROM ' . $this->getTable();
    if ([] !== $where) {
        $sql .= ' WHERE ';
        $fields = array_keys($where);
        $sql .= implode(' AND ', $fields);
    }
    if ([] !== $order) {
        $sql .= ' ORDER BY ' . implode(', ', $order);
    }
    if (0 < $limit) {
        $sql .= ' LIMIT ';
        if (0 < $offset) {
            $sql .= $offset . ', ';
        }
        $sql .= $limit;
    }
    if (false === ($statement = $this->pdo->prepare($sql))) {
        $this->error($this->pdo->errorInfo());
    }
    if (false === $statement->execute(array_values($where))) {
        $this->error($statement->errorInfo());
    }
    $result = $statement->fetchAll(\PDO::FETCH_ASSOC);
    $collection = [];
    foreach ($result as $entry) {
        $collection[] = new $this->modelName($entry);
    }
    return $collection;
}

A second iteration is often found somewhere at the controller or services level where the collection of data models is enriched or changed as preparation for the view (or output).

/**
 * Apply a discount percentage on all articles
 *
 * @param float $discount
 * @return array
 */
public function applyDiscountAsPercentage($discount)
{
    $entries = $this->mapper->fetchAll();
    $discounts = [];
    foreach ($entries as $entry) {
        $discountProduct = new DiscountProduct($entry->toArray());
        $discountCalc = round(($entry->getPrice() * (100 - $discount)) / 100, 2);
        $discountProduct->setDiscountPrice($discountCalc);
        $discounts[] = $discountProduct;
    }
    return $discounts;
}

A third iteration is often found at the output, where the collection is presented in a listing, table or grid.

require_once __DIR__ . '/php_arrays_examples.php';

use \DragonBe\ProductService;
use \DragonBe\ProductMapper;
use \DragonBe\ProductGateway;

$pdo = new \PDO('sqlite:phparray.db');

$productService = new ProductService(new ProductMapper(new ProductGateway($pdo)));
$discounts = $productService->applyDiscountAsPercentage(15);

echo sprintf('%-25s %10s %10s', 'Product', 'Sales', 'Promo') . PHP_EOL;
foreach ($discounts as $discountProduct) {
    echo sprintf(
        '%-25s %10.2f %10.2f',
        $discountProduct->getTitle(),
        $discountProduct->getPrice(),
        $discountProduct->getDiscountPrice()
    ) . PHP_EOL;
}

At this point, we already count 3 iterations between fetching data and outputting data. Often there are  a whole lot more iterations in between. Only to display a simple list of products with discounts.

Product                        Sales      Promo
demo_phone                    295.95     251.56
demo_computer                1999.95    1699.96
demo_tablet                   675.00     573.75
demo_drive                      5.99       5.09
demo_charger                   12.45      10.58
demo_coffee_mug                24.95      21.21
demo_phone_case                29.00      24.65
demo_usb_cable                 45.95      39.06
demo_external_screen          199.95     169.96
Added prodcut                 129.95     110.46
Added prodcut                 129.95     110.46
Added prodcut                 129.95     110.46

So what is the big deal here? Well, in development you probably test these routines with maybe 5 data entries (or 10 for extra edge cases) and performance is great. But in production, especially over time, you're dealing with a couple of thousand records. Millions if you're working for a large company. Simple math will give you an idea how 3 times many will start to slow things down. Unfortunately this is where PHP gets a bad reputation for being slow, even though I see similar mistakes in other technologies as well.

Luckily PHP has a few powerful array functions to help developers improve performance and their code.

In next articles I will highlight some of these functions and give real world examples where they will make a difference in performance.

Monday, December 7, 2015

Installing PHP 7 with XDebug, Apache and MySQL on OS X Yosemite


In my previous blog post "Installing PHP 7 on OS X Yosemite" I gave some insights on how to get quicly started with PHP7 on your Mac, specifically on your commandline. But before I continue on how to make it work with your installed Apache and MySQL I would like to address a subject many people found worth mentioning: PHP Installation Managers.

PHP Installation Managers

In the feedback I received on my article, people would like to point out that the package managers like "PHP-OSX" binary installer, "phpenv" multiversion php management and installer, "phpbrew" for installing and running multiple versions of PHP and "HomeBrew" the missing package manger for OS X. There will be probably more tools out there, but these were the suggestions made by some of my readers.

Again, I have nothing against these tools and I value their functionality a lot. The only reason I posted my article was to show people how you could have PHP 7 run immediately on your system without being too depending of any availability in a package manager or other tool. If you already have one of these PIM's configured, by all means use them. Saves you a bunch of manual work.

But if you don't have interest to install an additional tool, you can still follow these guidelines to have the latest and greatest PHP7 running on OS X.

If you would like to keep your system clean, there's also a Docker installation for PHP7 provided by Zend Technologies, Inc.


XDebug

When running PHP you also want to have XDebug compiled into it. Unfortunately there's not a stable release for XDebug 2.4, but I feel confident using the RC in the mean time as debugging is only something I will use locally.

Get the latest XDebug package (at this time of writing it's xdebug-4.2.0RC2), check the signature and unpack it.

$ cd /tmp/
$ wget http://xdebug.org/files/xdebug-2.4.0rc2.tgz
$ md5check xdebug-2.4.0rc2.tgz e00e92bb2e72f7c94e1300b2a980e79e
OK
$ tar -xvzf xdebug-2.4.0rc2.tgz
$ cd xdebug-2.4.0RC2/
Once done, we need to phpize and configure XDebug using our installed PHP7 in /opt/php7.

$ /opt/php7/bin/phpize
$ ./configure --enable-xdebug --with-php-config=/opt/php7/bin/php-config
Now all we need to do is to run make and make test to complete the build. This will create a xdebug.so shared object we need to copy to our PHP7 installation extension directory.

$ sudo cp modules/xdebug.so /opt/php7/lib/php/extensions/no-debug-non-zts-20151012/
Now we have the extension installed, we need to inform PHP7 we have the extension available. In the configuration of PHP7 in my previous article, I had stated that our configuration was going to be installed in /etc/php7 (see --with-config-file-path=/etc/${PHP} argument). This also means we need to ensure a php.ini file is there. Since this is a clean installation, we can easily copy the supplied php.ini-development into our configration path.

$ sudo cp /tmp/php-7.0.0/php.ini-development /etc/php7/php.ini
Now we need to add the xdebug configuration in it.

[xdebug]
zend_extension=/opt/php7/lib/php/extensions/no-debug-non-zts-20151012/xdebug.so
When we now run php -v on the commandline, we now see xdebug is installed correctly.

Further php.ini settings

In PHP7 if you omit to change the date.timezone, it will be set to UTC by default which might cause some conflicts if you have time and date operations. Best is you set it to the timezone you prefer (e.g. Europe/Brussels).

[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = Europe/Brussels

Apache Module for PHP7

OS X Yosemite comes with Apache 2.4 installed, so all we need to do is ensure PHP7 is compiled with flag --with-apxs2 enabled, which we already provided in my configure script mentioned in my previous article. This will modify our Apache /etc/apache2/httpd.conf and will add the following line:

LoadModule php7_module        libexec/apache2/libphp7.so
So now we have a PHP5 and a PHP7 module in our configuration. Prefer to uncoment the line for loading the PHP5 module so we only have the PHP7 module to take care of.

Now you just have to make the configuration for php7_module available. Therefor I added a new file /etc/apache2/other/php7.conf where I have specific PHP7 directives (comparable to the PHP5 directives).

<IfModule php7_module>
 AddType application/x-httpd-php .php
 AddType application/x-httpd-php-source .phps

 <IfModule dir_module>
  DirectoryIndex index.html index.php
 </IfModule>
</IfModule>
Now we can restart Apache and we should be able to see a nice phpinfo() page for PHP7 (I always have one available in the webroot).
Success! We have now PHP7 for our web applications too.

MySQL setup

The final step is to ensure we can connect with the installed MySQL database. Since we already took care of that in our configure-php.sh script, all database settings were already configured. No further settings are required.

--with-mysqli \
--with-pdo-mysql \
--with-mysql-sock=/tmp/mysql.sock
Done. That's it. Again, if you can work with a package manager of binary installer tool, go ahead use them. If you would like to have a bit more control, feel free to use this guide.

Saturday, December 5, 2015

Installing PHP 7 on OS X Yosemite

Yesterday was the release of PHP7.0.0 and I wanted to have it on my mac as fast as possible. Since I'm still using Mac OS X Yosemite I will post here the steps to upgrade my platform, it might be useful for you too.

Requirements

Since Apple is doing a great job to provide PHP, Apache and MySQL straight out of the box, I'm not worrying about the Apache and MySQL.

In order to to install PHP from source, you need to have XCode installed as it provides required libraries and headers that you will need to have a successful experience.
Of course, get the latest and greatest PHP7.0.0 bundle straight from php.net. Don't forget to verify the signatures before you get started unpacking the sources.

Time to bake the sources

I like to build my apps from sources, even though there are package managers that will do it in a single command. There are really some great benefits from building from sources:

  • You can switch on and off modules as you go
  • You can now choose how you want to run PHP (as module, cgi or fpm)
  • You can enable extra debugging and profiling features
  • And my favorite: you can automate it
When you've unpacked the bundle (let's say its in /tmp) go into the directory /tmp/php-7.0.0. Here you will find your master command configure that you will need to set up your PHP installation. When you're requiring to know what options you can use, configure --help|less is your friend.

For my own convenience, I've created a little tool that helps me to configure any version of PHP as it sets the bare minimum I need to achieve a running PHP platform.


#!/bin/sh

PHP=$1

if [ -t $PHP ]; then
  echo "Usage: $0 "
  echo "  prefix    A prefix for your installation you would like to use"
  echo "            e.g. 'php7', 'php56'"
  exit 0
fi


XCODE_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/
LIB_PATH=/usr

./configure \
--prefix=/opt/${PHP} \
--enable-cli \
--with-apxs2 \
--with-iconv=${LIB_PATH} \
--with-config-file-path=/etc/${PHP} \
--with-config-file-scan-dir=/Library/Server/Web/Config/php \
--with-libxml-dir=${XCODE_PATH} \
--with-openssl=/usr \
--with-mysqli \
--with-pdo-mysql \
--with-mysql-sock=/tmp/mysql.sock

I made this script (configure-php.sh) executable with chmod +x configure-php.sh. All that's required is to pick a great prefix (where you want php to be installed) and with ./configure-php.sh /opt/php7 you start configuring PHP.

Once your process is completed (and you don't get errors), it's time to run makemake test and make install.

Let's check it out

Now it's time to check if we have a working PHP. Let's find out with /opt/php7/bin/php -v and you should be getting something like this.


In a next article I will explain hot to add xdebug and have PHP7 run as an Apache module in the existing Mac OS environment. Until then.