Scrape Screens with zend-dom

Even in this day-and-age of readily available APIs and RSS/Atom feeds, many
sites offer none of them. How do you get at the data in those cases? Through the
ancient internet art of screen scraping.

The problem then becomes: how do you get at the data you need in a pile of HTML
soup? You could use regular expressions or any of the various string functions
in PHP. All of these are easily subject to error, though, and often require some
convoluted code to get at the data of interest.

Alternately, you could treat the HTML as XML, and use the DOM
extension
, which is typically built-in to PHP. Doing so,
however, requires more than a passing familiarity with
XPath, which is something of a black art.

If you use JavaScript libraries or write CSS fairly often, you may be familiar
with CSS selectors, which allow you to target either specific nodes or groups of
nodes within an HTML document. These are generally rather intuitive:

jQuery('section.slide h2').each(function (node) {
  alert(node.textContent);
});

What if you could do that with PHP?

Introducing zend-dom

zend-dom provides CSS selector
capabilities for PHP, via the ZendDomQuery class, including:

  • element types (h2, span, etc.)
  • class attributes (.error, .next, etc.)
  • element identifiers (#nav, #main, etc.)
  • arbitrary element attributes (div[onclick="foo"]), including word matches
    (div[role~="navigation"]) and substring matches (div[role*="complement"])
  • descendents (div .foo span)

While it does not implement the full spectrum of CSS selectors, it does provide
enough to generally allow you to get at the information you need within a page.

Example: retrieving a navigation list

As an example, let’s fetch the navigation list from the ZendDomQuery
documentation page itself:

use ZendDomQuery;

$html = file_get_contents('https://docs.zendframework.com/zend-dom/query/');
$query = new Query($html);
$results = $query->execute('ul.bs-sidenav li a');

printf("Received %d results:n", count($results));
foreach ($results as $result) {
    printf("- [%s](%s)n", $result->getAttribute('href'), $result->textContent);
}

The above queries for ul.bs-sidenav li a — in other words, all links
within list items of the sidenav unordered list.

When you execute() a query, you are returned a ZendDomNodeList instance,
which decorates a DOMNodeList in order to
provide features such as Countable, and access to the original query and
document. In the example above, we count() the results, and then loop over them.

Each item in the list is a DOMNode, giving you
access to any attributes, the text content, and any child elements. In our
case, we access the href attribute (the link target), and report the text
content (the link text).

The results are:

Received 3 results:
- [#querying-html-and-xml-documents](Querying HTML and XML Documents)
- [#theory-of-operation](Theory of Operation)
- [#methods-available](Methods Available)

Other uses

Another use case is for testing. When you have classes that return HTML, or if
you want to execute requests and test the generated output, you often don’t want
to test exact contents, but rather look for specific data or fragments within
the document.

We provide these capabilities for zend-mvc
applications via the zend-test component,
which provides a number of CSS selector assertions
for use in querying the content returned in your MVC responses. Having these
capabilities allows testing for dynamic content as well as static content,
providing a number of vectors for ensuring application quality.

Start scraping!

While this post was rather brief, we hope you can appreciate the powerful
capabilities of this component! We have used this functionality in a variety of
ways, from testing applications to creating feeds based on content differences
in web pages, to finding and retrieving image URIs from pages.

Get more information from the zend-dom documentation.

Source: Zend feed

zend-config For All Your Configuration Needs

Different applications and frameworks have different opinions about how
configuration should be created. Some prefer XML, others YAML, some like JSON,
others like INI, and some even stick to the JavaProperties format; in Zend
Framework, we tend to prefer PHP arrays, as each of the other formats
essentially get compiled to PHP arrays eventually anyways.

At heart, though, we like to support developer needs, whatever they may be, and,
as such, our zend-config component
provides ways of working with a variety of configuration formats.

Installation

zend-config is installable via Composer:

$ composer require zendframework/zend-config

The component has two dependencies:

  • zend-stdlib, which provides some
    capabilities around configuration merging.
  • psr/container, to allow reader and
    writer plugin support for the configuration factory.

Latest version

This article covers the most recently released version of zend-config, 3.1.0,
which contains a number of features such as PSR-11 support that were not
previously available. If you are using Zend Framework, you should be able to
safely provide the constraint ^2.6 || ^3.1, as the primary APIs remain the
same.

Retrieving configuration

Once you’ve installed zend-config, you can start using it to retrieve and access
configuration files. The simplest way is to use ZendConfigFactory, which
provides tools for loading configuration from a variety of formats, as well as
capabilities for merging.

If you’re just pulling in a single file, use Factory::fromFile():

use ZendConfigFactory;

$config = Factory::fromFile($path);

Far more interesting is to use multiple files, which you can do via
Factory::fromFiles(). When you do, they are merged into a single
configuration, in the order in which they are provided to the factory. This is
particularly interesting using glob():

use ZendConfigFactory;

$config = Factory::fromFiles(glob('config/autoload/*.*'));

What’s particularly interesting about this is that it supports a variety of
formats:

  • PHP files returning arrays (.php extension)
  • INI files (.ini extension)
  • JSON files (.json extension)
  • XML files (using PHP’s XMLReader; .xml extension)
  • YAML files (using ext/yaml, installable via PECL; .yaml extension)
  • JavaProperties files (.javaproperties extension)

This means that you can choose the configuration format you prefer, or
mix-and-match multiple formats, if you need to combine configuration from
multiple libraries!

Configuration objects

By default, ZendConfigFactory will return PHP arrays for the merged
configuration. Some dependency injection containers do not support arrays as
services, however; moreover, you may want to pass some sort of structured object
instead of a plain array when injecting dependencies.

As such, you can pass a second, optional argument to each of fromFile() and
fromFiles(), a boolean flag. When true, it will return a
ZendConfigConfig instance, which implements Countable, Iterator, and
ArrayAccess, allowing it to look and act like an array.

What is the benefit?

First, it provides property overloading to each configuration key:

$debug = $config->debug ?: false;

Second, it offers a convenience method, get(), which allows you to specify a
default value to return if the value is not found:

$debug = $config->get('debug', false); // Return false if not found

This is largely obviated by the ?: ternary shortcut in modern PHP versions,
but very useful when mocking in your tests.

Third, nested sets are also returned as Config instances, which gives you the
ability to use the above get() method on a nested item:

if (isset($config->expressive)) {
    $config = $config->get('expressive'); // same API!
}

Fourth, you can mark the Config instance as immutable! By default, it acts
just like array configuration, which is, of course, mutable. However, this can
be problematic when you use configuration as a service, because, unlike an
array, a Config instance is passed by reference, and changes to values would
then propagate to any other services that depend on the configuration.

Ideally, you wouldn’t be changing any values in the instance, but
ZendConfigConfig can enforce that for you:

$config->setReadOnly(); // Now immutable!

Further, calling this will mark nested Config instances as read-only as well,
ensuring data integrity for the entire configuration tree.

Read-only by default!

One thing to note: by default, Config instances are read-only! The
constructor accepts an optional, second argument, a flag indicating whether or
not the instance allows modifications, and the value is false by default.
Whenever you use the Factory to create a Config instance, it never enables
that flag, meaning that if you return a Config instance, it will be read-only.

If you want a mutable instance from a Factory, use the following construct:

use ZendConfigConfig;
use ZendConfigFactory;

$config = new Config(Factory::fromFiles($files), true);

Including other configuration

Most of the configuration reader plugins also support "includes": directives
within a configuration file that will include configuration from another file.
(JavaProperties is the only configuration format we support that does not have
this functionality included.)

For instance:

  • INI files can use the key @include to include another file relative to the
    current one; values are merged at the same level:

    webhost = 'www.example.com'
    @include = 'database.ini'
    
  • For XML files, you can use XInclude:

    <?xml version="1.0" encoding="utf-8">
    <config xmlns:xi="http://www.w3.org/2001/XInclude">
      <webhost>www.example.com</webhost>
      <xi:include href="database.xml"/>
    </config>
    
  • JSON files can use an @include key:

    {
      "webhost": "www.example.com",
      "@include": "database.json"
    }
    
  • YAML also uses the @include notation:

    webhost: www.example.com
    @include: database.yaml
    

Choose your own YAML

Out-of-the-box we support the YAML PECL extension
for our YAML support. However, we have made it possible to use alternate
parsers, such as Spyc or the Symfony YAML component, by passing a callback to the
reader’s constructor:

use SymfonyComponentYamlYaml as SymfonyYaml;
use ZendConfigReaderYaml as YamlConfig;

$reader = new YamlConfig([SymfonfyYaml::class, 'parse']);
$config = $reader->fromFile('config.yaml');

Of course, if you’re going to do that, you could just use the original library,
right? But what if you want to mix YAML and other configuration with the
Factory class?

There aer two ways to register new plugins. One is to create an instance and
register it with the factory:

use SymfonyComponentYamlYaml as SymfonyYaml;
use ZendConfigFactory;
use ZendConfigReaderYaml as YamlConfig;

Factory::registerReader('yaml', new YamlConfig([SymfonyYaml::class, 'parse']));

Alternately, you can provide an alternate reader plugin manager. You can do that
by extending ZendConfigStandaloneReaderPluginManager, which is a barebones
PSR-11 container for use as a plugin manager:

namespace Acme;

use SymfonyComponentYamlYaml as SymfonyYaml;
use ZendConfigReaderYaml as YamlConfig;
use ZendConfigStandaloneReaderPluginManager;

class ReaderPluginManager extends StandaloneReaderPluginManager
{
    /**
     * @inheritDoc
     */
    public function has($plugin)
    {
        if (YamlConfig::class === $plugin
            || 'yaml' === strtolower($plugin)
        ) {
            return true;
        }

        return parent::has($plugin);
    }

    /**
     * @inheritDoc
     */
    public function get($plugin)
    {
        if (YamlConfig::class !== $plugin
            && 'yaml' !== strtolower($plugin)
        ) {
            return parent::get($plugin);
        }

        return new YamlConfig([SymfonyYaml::class, 'parse']);
    }
}

Then register this with the Factory:

use AcmeReaderPluginManager;
use ZendConfigFactory;

Factory::setReaderPluginManager(new ReaderPluginManager());

Processing configuration

zend-config also allows you to process a ZendConfigConfig instance and/or
an individual value. Processors perform operations such as:

  • substituting constant values within strings
  • filtering configuration data
  • replacing tokens within configuration
  • translating configuration values

Why would you want to do any of these operations?

Consider this: deserialization of formats other than PHP cannot take into
account PHP constant values or class names!

While this may work in PHP:

return [
    AcmeComponent::CONFIG_KEY => [
        'host' => AcmeComponent::CONFIG_HOST,
        'dependencies' => [
            'factories' => [
                AcmeMiddlewareAuthorization::class => AcmeMiddlewareAuthorizationFactory::class,
            ],
        ],
    ],
];

The following JSON configuration would not:

{
    "AcmeComponent::CONFIG_KEY": {
        "host": "AcmeComponent::CONFIG_HOST"
        "dependencies": {
            "factories": {
                "AcmeMiddlewareAuthorization::class": "AcmeMiddlewareAuthorizationFactory::class"
            }
        }
    }
}

Enter the Constant processor!

This processor looks for strings that match constant names, and replaces them
with their values. Processors generally only work on the configuration values,
but the Constant processor allows you to opt-in to processing the keys as
well.

Since processing modifies the Config instance, you will need to manually
create an instance, and then process it. Let’s look at that:

use AcmeComponent;
use ZendConfigConfig;
use ZendConfigFactory;
use ZendConfigProcessor;

$config = new Config(Factory::fromFile('config.json'), true);
$processor = new ProcessorConstant();
$processor->enableKeyProcessing();
$processor->process($config);
$config->setReadOnly();

var_export($config->{Component::CONFIG_KEY}->dependencies->factories);
// ['AcmeMiddlewareAuthorization' => 'AcmeMiddlewareAuthorizationFactory']

This is a really powerful feature, as it allows you to add more verifications
and validations to your configuration files, regardless of the format you use.

In version 3.1.0 forward

The ability to work with class constants and process keys was added only
recently in the 3.1.0 version of zend-config.

Config all the things!

This post covers the parsing features of zend-config, but does not even touch on
another major capability: the ability to write configuration! We’ll leave that
to another post.

In terms of configuration parsing, zend-config is simple, yet powerful. The
ability to process a number of common configuration formats, utilize
configuration includes, and process keys and values means you can highly
customize your configuration process to suit your needs or integrate different
configuration sources.

Get more information from the zend-config
documentation
.

Source: Zend feed

PHP and SQL Server for Linux

This week we tested the public preview of Microsoft SQL Server for Linux
using PHP 7 with our component zendframework/zend-db.

Microsoft announced the availability of a public
preview of SQL Server for Linux on the 16th of November, 2016. This new
version of SQL Server has some interesting features such as:

  • transparent data encryption;
  • always encrypted;
  • row level security;
  • in-memory tables;
  • columnstore indexing;
  • native JSON support;
  • support for in-database analytics with R-integration.

Moreover, the performance of the new DBMS seems to be very impressive. Microsoft
published a case study with 1.2 million requests per second with In-Memory OLTP
on a single commodity server
.

We tested the last preview of SQL Server (CTP1.2 – 14.0.200.24) using a Vagrant
box with Ubuntu 16.04 and 4 GB RAM.

Install SQL Server on Linux

We followed the instructions list on the Microsoft website
to install SQL Server for PHP on Ubuntu 16.04.

To ensure optimal performance of SQL Server, the Ubuntu box should have at least
4 GB of memory.

The first step is to add the GPG key for the Microsoft repositories.

$ sudo su
$ curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
$ curl https://packages.microsoft.com/config/ubuntu/16.04/mssql-server.list > /etc/apt/sources.list.d/mssql-server.list
$ exit

Then we can update the repository list and install the mssql-server package,
using the following commands:

$ sudo apt-get update
$ sudo apt-get install mssql-server

Now we can run the setup for sqlserver. We will be required to accept the EULA and
choose a password for the System Administrator (SA).

sudo /opt/mssql/bin/sqlservr-setup

After the installation, we will have SQL Server running on Linux!

Install the command line utility for SQL Server

Now that we have the DBMS running, we need a tool to access it. Microsoft
provides a command line tool named sqlcmd. This program is very similar to the
MySQL client tool, quite familiar to PHP developers.

To install sqlcmd, we need to run the following commands:

$ sudo su
$ curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-tools.list
$ exit
$ sudo apt-get update
$ sudo apt-get install msodbcsql mssql-tools unixodbc-dev
$ echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile
$ echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
$ source ~/.bashrc

msodbcsql

Though the Microsoft documentation does not indicate it, we also installed
msodbcsql; without it, we ran into dependency issues.

If the installation was successful, we can start using the command line tool.
For instance, we can ask for the SQL Server vesion using the following
instruction:

$ sqlcmd -S localhost -U sa -P yourpassword -Q "SELECT @@VERSION"

where yourpassword should be replaced with the SA password that you choose
during the SQL Server setup.

This command will return output like the following:

----------------------------------------------------------------
Microsoft SQL Server vNext (CTP1.2) - 14.0.200.24 (X64)
	Jan 10 2017 19:15:28
	Copyright (C) 2016 Microsoft Corporation. All rights reserved.
	on Linux (Ubuntu 16.04.1 LTS)

Install the SQL Server extension for PHP

Next, we need to install the PHP extension for SQL Server. This can be done
using PECL.

PECL

If you do not have PECL installed on Ubuntu, you can install it with the
following command:

$ sudo apt-get install php-dev

To install the sqlsrv and pdo_sqlsrv extensions for PHP, we need to execute
the following commands:

$ sudo apt-get install unixodbc-dev gcc g++ build-essential
$ sudo pecl install sqlsrv pdo_sqlsrv

Finally, we need to add the directives extension=pdo_sqlsrv.so and
extension=sqlsrv.so to our PHP configuration (generally php.ini). In our
case, running Ubuntu (or any other Debian-flavored distribution) we have the
PHP configuration files stored in /etc/php/7.0/mods-available. We can create
sqlsrv.ini and pdo_sqlsrv.ini containing the respective configurations. As
a last step, we need to link these configurations to our specific PHP environments.
For this, you can have two choices:

  • For Ubuntu, you can use the phpenmod command.
  • Alternately, you can symlink to the appropriate directory.

For our purposes, we are using PHP 7.0, from the CLI SAPI, so we can do either of the following:

# Using phpenmod:
$ sudo phpenmod -v 7.0 -s cli sqlsrv pdo_sqlsrv
# Manually symlinking:
$ sudo ln -s /etc/php/7.0/mods-available/sqlsrv.ini /etc/php/7.0/cli/conf.d/20-sqlsrv.ini
$ sudo ln -s /etc/php/7.0/mods-available/pdo_sqlsrv.ini /etc/php/7.0/cli/conf.d/20-pdo_sqlsrv.ini

Integration tests with zend-db

We used the above information to add support for SQL Server to the
zendframework/zend-db vagrant
configuration, used by developers to test against the various database platforms
we support.

We updated the Vagrantfile,
enabling integration tests for MySQL, PostgreSQL and SQL Server on Linux, to
read as follows:

$install_software = <<SCRIPT
export DEBIAN_FRONTEND=noninteractive
apt-get -yq update

# INSTALL PostgreSQL
apt-get -yq install postgresql

# Allow external connections to PostgreSQL as postgres
sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" /etc/postgresql/9.5/main/postgresql.conf
sed -i "s/peer/trust/" /etc/postgresql/9.5/main/pg_hba.conf
echo 'host all all 0.0.0.0/0 trust' >> /etc/postgresql/9.5/main/pg_hba.conf
service postgresql restart

# INSTALL MySQL
debconf-set-selections <<< "mysql-server mysql-server/root_password password Password123"
debconf-set-selections <<< "mysql-server mysql-server/root_password_again password Password123"
apt-get -yq install mysql-server

# Allow external connections to MySQL as root (with password Password123)
sed -i 's/127.0.0.1/0.0.0.0/g' /etc/mysql/mysql.conf.d/mysqld.cnf
mysql -u root -pPassword123 -e 'USE mysql; UPDATE `user` SET `Host`="%" WHERE `User`="root" AND `Host`="localhost"; DELETE FROM `user` WHERE `Host` != "%" AND `User`="root"; FLUSH PRIVILEGES;'
service mysql restart

# INSTALL SQL Server
# More info here: https://www.microsoft.com/en-us/sql-server/developer-get-started/php-ubuntu

curl -s https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
curl -s https://packages.microsoft.com/config/ubuntu/16.04/mssql-server.list > /etc/apt/sources.list.d/mssql-server.list
apt-get -yq update
apt-get -yq install mssql-server
printf "YESnPassword123nPassword123nyny" | /opt/mssql/bin/sqlservr-setup

curl -s https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-tools.list
apt-get -yq update
ACCEPT_EULA=Y apt-get -yq install msodbcsql mssql-tools unixodbc-dev
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> /home/vagrant/.bash_profile
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> /home/vagrant/.bashrc
source /home/vagrant/.bashrc
SCRIPT


$setup_vagrant_user_environment = <<SCRIPT
if ! grep "cd /vagrant" /home/vagrant/.profile > /dev/null; then
  echo "cd /vagrant" >> /home/vagrant/.profile
fi
SCRIPT

Vagrant.configure(2) do |config|
  config.vm.box = 'bento/ubuntu-16.04'
  config.vm.provider "virtualbox" do |v|
    v.memory = 4096
    v.cpus = 2
  end

  config.vm.network "private_network", ip: "192.168.20.20"

  config.vm.provision 'shell', inline: $install_software
  config.vm.provision 'shell', privileged: false, inline: '/vagrant/.ci/mysql_fixtures.sh'
  config.vm.provision 'shell', privileged: false, inline: '/vagrant/.ci/pgsql_fixtures.sh'
  config.vm.provision 'shell', privileged: false, inline: '/vagrant/.ci/sqlsrv_fixtures.sh'
  config.vm.provision 'shell', inline: $setup_vagrant_user_environment
end

This vagrant configuration installs Ubuntu 16.04 with 4 GB of RAM and the
following databases (user and password are reported in parenthesis):

  • MySQL 5.7.17 (root/Password123)
  • PostgreSQL 9.5 (postgres/postgres)
  • SQL Server 14.0.200.24 (sa/Password123)

We can use this virtual machine to run PHPUnit for the
zend-db integration tests. If you want to test this vagrant box, you can clone
the zend-db repository and run the following command:

$ vagrant up

This creates a VM running with IP 192.168.20.20.

More information

In this post, we’ve demonstrated how to install the public preview release of
SQL Server on Ubuntu. Microsoft also provides support for other Linux
distributions, such as Red Hat and Suse.

You can also install this new SQL Server preview on Windows, macOS, Azure or
using a preconfigured Docker container.

Get more information on SQL Server for Linux from the official website.
Find specific information on how to use SQL Server with PHP
here.

Source: Zend feed

Using Laravel Homestead with Zend Framework Projects

Laravel Homestead is an interesting
project by the Laravel community that provides a Vagrant
box for PHP developers. It includes a full set of services for PHP developers,
such as the Nginx web server, PHP 7.1, MySQL, Postgres, Redis, Memcached, Node, and
more.

One the most interesting features of this project is the ability to enable it per
project. This means you can run a vagrant box for your specific PHP project.

In this post, we’ll examine using it for Zend Framework MVC, Expressive, and
Apigility projects. In each case, installation and usage is exactly the same.

Install the Vagrant box

The first step is to install the laravel/homestead
vagrant box. This box works with a variety of providers: VirtualBox 5.1,
VMWare, or Parallels.

We used VirtualBox and the following command to install the laravel/homestead
box:

$ vagrant box add laravel/homestead

The box is 981 MB, so it will take some minutes to download.

Homestead, by default, uses the host name homestead.app, and requires that you
update your system hosts file to point that domain to the virtual machine IP
address. To faciliate that, Homestead provides integration with the
vagrant-hostsupdater
Vagrant plugin. We recommend installing that before your initial run of the
virtual machine:

$ vagrant plugin install vagrant-hostsupdater

Use Homestead in ZF projects

Once you have installed the laravel/homestead vagrant box, you can use it globally
or per project.

If we install Homestead per-project, we will have a full development server
configured directly in the local folder, without sharing services with other
projects. This is a big plus!

To use Homestead per-project, we need to install the laravel/homestead
package within our Zend Framework, Apigility, or Expressive project. This can be
done using Composer with the following command:

$ composer require --dev laravel/homestead

After installation, execute the homestead command to build the Vagrantfile:

$ vendor/bin/homestead make

This command creates both the VagrantFile and a Homestead.yaml configuration
file.

Configuring Homestead

By default, the vagrant box is set up at address 192.168.10.10 with the hostname
homestead.app. You can change the IP address in Homestead.yaml if you want,
as well as the hostname (via the sites[].map key).

The Homestead.yaml configuration file contains all details about the
vagrant box configuration. The following is an example:

---
ip: "192.168.10.10"
memory: 2048
cpus: 1
hostname: expressive-homestead
name: expressive-homestead
provider: virtualbox

authorize: ~/.ssh/id_rsa.pub

keys:
    - ~/.ssh/id_rsa

folders:
    - map: "/home/enrico/expressive-homestead"
      to: "/home/vagrant/expressive-homestead"

sites:
    - map: homestead.app
      to: "/home/vagrant/expressive-homestead/public"

databases:
    - homestead

This configuration file is very simple and intuitive; for instance, the folders
to be used are reported in the folders section; the map value is the local
folder of the project, the to value is the folder on the virtual machine.

If you want to add or change more features in the virtual machine you can used
the Homestead.yaml configuration file. For instance, if you prefer to add
MariaDB instead of MySQL, you need to add the mariadb option:

ip: "192.168.10.10"
memory: 2048
cpus: 1
hostname: expressive-homestead
name: expressive-homestead
provider: virtualbox
mariadb: true

This option will remove MySQL and install MariaDB.

SSH keys managed by GPG

One of our team uses the gpg-agent as an ssh-agent, which caused some
configuration problems initially, as the ~/.ssh/id_rsa and its .pub
sibling were not present.

When using gpg-agent for serving SSH keys, you can export the key using
ssh-add -L. This may list several keys, but you should be able to find the
correct one. Copy it to the file ~/.ssh/gpg_key.pub, and then copy that file
to ~/.ssh/gpg_key.pub.pub. Update the Homestead.yaml file to reflect
these new files:

authorize: ~/.ssh/gpg_key.pub.pub
keys:
    - ~/.ssh/gpg_key.pub

The gpg-agent will take care of sending the appropriate key from there.

Running Homestead

To run the vagrant box, execute the following within your project root:

$ vagrant up

If you open a browser to http://homestead.app you should now see your
application running.

Manually managing your hosts file

If you chose not to use vagrant-hostsupdater, you will need to update your
system hosts file.

On Linux and Mac, update the /etc/hosts file to add the following line:

192.168.10.10 homestead.app

On Windows, the host file is located in C:WindowsSystem32driversetchosts.

More information

We’ve tested this setup with each of the Zend Framework zend-mvc skeleton
application, Apigility, and Expressive, and found the setup "just worked"! We
feel it provides excellent flexibility in setting up development environments,
giving developers a wide range of tools and technologies to work with as they
develop applications.

For more information about Laravel Homestead, visit the
official documentation
of the project.

Source: Zend feed

Paginating data collections with zend-paginator

zend-paginator is a flexible
component for paginating collections of data and presenting that data to users.

Pagination is a standard UI solution
to manage the visualization of lists of items, like a list of posts in a blog
or a list of products in an online store.

zend-paginator is very popular among Zend Framework developers, and it’s often
used with zend-view, thanks to
the pagination control view helper zend-view provides.

It can be used also with other template engines. In this article, I will
demonstrate how to use it with Plates.

Usage of zend-paginator

The component can be installed via composer, using
the following command:

$ composer require zendframework/zend-paginator

To consume the paginator component, we need a collection of items.
zend-paginator ships with several different adapters for common collection
types:

  • ArrayAdapter, which works with PHP arrays;
  • Callback, which allows providing callbacks for obtaining counts of items and lists of items;
  • DbSelect, to work with a SQL collection (using zend-db);
  • DbTableGateway, to work with a Table Data Gateway (using the TableGateway
    feature from zend-db.
  • Iterator, to work with any Iterator instance.

If your collection does not fit one of these adapters, you can create a custom
adapter. To do so, you will need to implement
ZendPaginatorAdapterAdapterInterface, which defines two methods:

  • count() : int
  • getItems(int $offset, int $itemCountPerPage) : array

Each adapter need to return the total number of items in the collection,
implementing the count() method, and a portion (a page) of items starting
from $offset position with a size of $itemCountPerPage per page.

With these two methods we can use zend-paginator with any type of collection.

For instance, imagine we need to paginate a collection of blog posts and we
have a Posts class that manage all the posts. We can implement an adapter
like this:

require 'vendor/autoload.php';

use ZendPaginatorAdapterAdapterInterface;
use ZendPaginatorPaginator;
use ZendPaginatorScrollingStyleSliding;

class Posts implements AdapterInterface
{
    protected $posts = [];

    public function __construct()
    {
      // Read posts from file/database/whatever
    }

    public function count()
    {
        return count($this->posts);
    }

    public function getItems($offset, $itemCountPerPage)
    {
        return array_slice($this->posts, $offset, $itemCountPerPage);
    }
}

$posts = new Posts();
$paginator = new Paginator($posts);

Paginator::setDefaultScrollingStyle(new Sliding());
$paginator->setCurrentPageNumber(1);
$paginator->setDefaultItemCountPerPage(8);

foreach ($paginator as $post) {
  // Iterate on each post
}

$pages = $paginator->getPages();
var_dump($pages);

In this example, we created a zend-paginator adapter using a custom Posts
class. This class stores the collection of posts using a protected array
($posts). This adapter is then passed to an instance of Paginator.

When creating a Paginator, we need to configure its behavior.
The first setting is the scrolling style. In the example above, we used the
Sliding
style, a Yahoo!-like scrolling style that positions the current page number as
close as possible to the center of the page range.

Scrolling style

Note: the Sliding scrolling style is the default style used by zend-paginator. We need
to set it explicitly using Paginator::setDefaultScrollingStyle() only if we
do not use zend-servicemanager
as a plugin manager. Otherwise, the scrolling style is loaded by default from
the plugin manager.

The other two configuration values are the current page number and the number
of items per page. In the example above, we started from page 1, and we count 8
items per page.

We can then iterate on the $paginator object to retrieve the post of the
current page in the collection.

At the end, we can retrieve the information regarding the previous page, the
next page, the total items in the collection, and more. To get these values
we need to call the getPages() method. We will obtain an object like this:

object(stdClass)#81 (13) {
  ["pageCount"]=>
  int(3)
  ["itemCountPerPage"]=>
  int(8)
  ["first"]=>
  int(1)
  ["current"]=>
  int(1)
  ["last"]=>
  int(3)
  ["next"]=>
  int(2)
  ["pagesInRange"]=>
  array(3) {
    [1]=>
    int(1)
    [2]=>
    int(2)
    [3]=>
    int(3)
  }
  ["firstPageInRange"]=>
  int(1)
  ["lastPageInRange"]=>
  int(3)
  ["currentItemCount"]=>
  int(8)
  ["totalItemCount"]=>
  int(19)
  ["firstItemNumber"]=>
  int(1)
  ["lastItemNumber"]=>
  int(8)
}

Using this information, we can easily build an HTML footer to navigate across
the collection.

Note: using zend-view, we can consume the paginationControl
helper, which emits an HTML pagination bar.

In the next section, I’ll demonstrate using the Plates
template engine.

An example using Plates

Plates implements templates using native PHP; it is fast and easy to use,
without any additional meta language; it is just PHP.

In our example, we will create a Plates template to paginate a collection of
data using zend-paginator. We will use bootstrap as
the UI framework.

For purposes of this example, blog posts will be accessible via the following URL:

/blog[/page/{page:d+}]

where [/page/{page:d+}] represents the optional page number (using the regexp
d+ to validate only digits). If we open the /blog URL we will get the
first page of the collection. To return the second page we need to connect to
/blog/page/2, third page to /blog/page/3, and so on.

For instance, we can manage the page parameter using a PSR-7 middleware class
consuming the previous Posts adapter, that works as follow:

use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use LeaguePlatesEngine;
use ZendPaginatorPaginator;
use ZendPaginatorScrollingStyleSliding;
use Posts;

class PaginatorMiddleware
{
    /** @var Posts */
    protected $posts;

    /** @var Engine */
    protected $template;

    public function __construct(Posts $post, Engine $template = null)
    {
        $this->posts    = $post;
        $this->template = $template;
    }

    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response, callable $next = null
    ) {
        $paginator = new Paginator($this->posts);
        $page = $request->getAttribute('page', 1);

        Paginator::setDefaultScrollingStyle(new Sliding());
        $paginator->setCurrentPageNumber($page);
        $paginator->setDefaultItemCountPerPage(8);

        $pages = $paginator->getPages();
        $response->getBody()->write(
            $this->template->render('posts', [
                'paginator' => $paginator,
                'pages' => $pages,
            ])
        );
        return $response;
    }
}

We used a posts.php template, passing the paginator ($paginator) and
the pages ($pages) instances. That template could then look like the following:

<?php $this->layout('template', ['title' => 'Blog Posts']) ?>

<div class="container">
  <h1>Blog Posts</h1>

  <?php foreach ($paginator as $post): ?>
    <div class="row">
      <?php // prints the post title, date, author, ... ?>
    </div>
  <?php endforeach; ?>

  <?php $this->insert('page-navigation', ['pages' => $pages]) ?>
</div>

The page-navigation.php template contains the HTML code for the page
navigation control, with button like previous, next, and page numbers.

<nav aria-label="Page navigation">
  <ul class="pagination">
    <?php if (! isset($pages->previous)): ?>
      <li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>
    <?php else: ?>
      <li><a href="/blog/page/<?php echo $pages->previous ?>" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>
    <?php endif; ?>

    <?php foreach ($pages->pagesInRange as $num): ?>
      <?php if ($num === $pages->current): ?>
        <li class="active"><a href="/blog/page/<?php echo $num ?>"><?php echo $num ?> <span class="sr-only">(current)</span></a></li>
      <?php else: ?>
        <li><a href="/blog/page/<?php echo $num ?>"><?php echo $num ?></a></li>
      <?php endif; ?>
    <?php endforeach; ?>

    <?php if (! isset($pages->next)): ?>
      <li class="disabled"><a href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>
    <?php else: ?>
      <li><a href="/blog/page/<?php echo $pages->next ?>" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>
    <?php endif; ?>
  </ul>
</nav>

Summary

The zend-paginator component of Zend Framework is a powerful and easy to use
package that provides pagination of data. It can be used as standalone component
in many PHP projects using different frameworks and template engines. In this
article, I demonstrated how to use it in general purpose applications.
Moreover, I showed an example using Plates and Bootstrap, in a PSR-7 middleware
scenario.

Visit the zend-paginator documentation
to find out what else you might be able to do with this component!

Source: Zend feed

Implement a SOAP server with zend-soap

zend-soap provides a full-featured
SOAP implementation. SOAP is an XML-based
web protocol designed to allow describing messages, and, optionally,
operations to perform. It’s similar to XML-RPC,
but with a few key differences:

  • Arbitrary data structures may be described; you are not limited to the basic
    scalar, list, and struct types of XML-RPC. Messages are often serializations
    of specific object types on either or both the client and server. The
    SOAP message may include information on its own structure to allow the server
    or client to determine how to interpret the message.

  • Multiple operations may be described in a message as well, versus the one
    call, one operation structure of XML-RPC.

In other words, it’s an extensible protocol. This provides obvious benefits,
but also a disadvantage: creating and parsing SOAP messages can quickly become
quite complex!

To alleviate that complexity, Zend Framework provides the zend-soap component,
which includes a server implementation.

Why these articles on RPC services?

In the past couple weeks, we’ve covered JSON-RPC
and XML-RPC. One feedback we’ve
seen is: why bother — shouldn’t we be creating REST services instead?

We love REST; one of our projects is Apigility, which
allows you to simply and quickly build REST APIs. However, there are occasions
where RPC may be a better fit:

  • If your services are less resource oriented, and more function oriented
    (e.g., providing calculations).

  • If consumers of your services may need more uniformity in the service
    architecture in order to ensure they can quickly and easily consume the
    services, without needing to create unique tooling for each service exposed.
    While the goal of REST is to offer discovery, when every payload to send or
    receive is different, this can often lead to an explosion of code when
    consuming many services.

  • Some organizations and companies may standardize on certain web service
    protocols due to existing tooling, ability to train developers, etc.

While REST may be the preferred way to architect web services, these and
other reasons often dictate other approaches. As such, we provide these RPC
alternatives for PHP developers.

What benefits does it offer over the PHP extension?

PHP provides SOAP client and server capabilities already via its
SOAP extension; why do we offer a component?

By default, PHP’s SoapServer::handle() will:

  • Grab the POST body (php://input), unless an XML string is passed to it.
  • Emit the headers and SOAP XML response body to the output buffer.

Exceptions or PHP errors raised during processing may result in a SOAP fault
response, with no details, or can result in invalid/empty SOAP responses
returned to the client.

The primary benefit zend-soap provides, then, is error handling. You can
whitelist exception types, and, when encountered, fault responses containing the
exception details will be returned. PHP errors will be emitted as SOAP faults.

The next thing that zend-soap offers is WSDL generation. WSDL allows you to
describe the web services you offer, so that clients know how to work with
your services. ext/soap provides no functionality around creating WSDL; it
simply expects that you will have a valid one for use with the client or server.

zend-soap provides an AutoDiscover class that uses reflection on the classes
and functions you pass it in order to build a valid WSDL for you; you can then
provide this to your server and your clients.

Creating a server

There are two parts to providing a SOAP server:

  • Providing the server itself, which will handle requests.
  • Providing the WSDL.

Building each follows the same process; you simply emit them with different HTTP
Content-Type headers, and under different HTTP methods (the server will always
react to POST requests, while WSDL should be available via GET).

First, let’s define a function for populating a server instance with classes
and functions:

use AcmeModel;

function populateServer($server, array $env)
{
    // Expose a class and its methods:
    $server->setClass(ModelCalculator::class);

    // Expose an object instance and its methods:
    $server->setObject(new ModelEnv($env));

    // Expose a function:
    $server->addFunction('AcmeModelping');
}

Note that $server is not type-hinted; that will become more obvious soon.

Now, let’s assume that the above function is available to us, and use it to
create our WSDL:

// File /soap/wsdl.php

use ZendSoapAutoDiscover;

if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
    // Only handle GET requests
    header('HTTP/1.1 400 Client Error');
    exit;
}

$wsdl = new AutoDiscover();
populateServer($wsdl, $_ENV);
$wsdl->handle();

Done! The above will emit the WSDL for either the client or server to consume.

Now, let’s create the server. The server requires a few things:

  • The public, HTTP-accessible location of the WSDL.
  • SoapServer options, including the actor URI for the server and SOAP
    version targeted.

Additionally, we’ll need to notify the server of its capabilities, via the
populateServer() function.

// File /soap/server.php

use ZendSoapServer;

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    // Only handle POST requests
    header('HTTP/1.1 400 Client Error');
    exit;
}

$server = new Server(dirname($_SERVER['REQUEST_URI']) . '/wsdl.php', [
    'actor' => $_SERVER['REQUEST_URI'],
]);

populateServer($server, $_ENV);
$server->handle();

The reason for the lack of type-hint should now be clear; both the Server and
AutoDiscover classes have the same API for populating the instances with
classes, objects, and functions; having a common function for doing so allows us
to ensure the WSDL and server do not go out of sync.

From here, you can point your clients at /soap/server.php on your domain, and
they will have all the information they need to work with your service.

Using zend-soap within a zend-mvc application

The above details an approach using vanilla PHP; what about using zend-soap
within a zend-mvc context?

To do this, we’ll need to learn a few more things.

First, you can provide Server::handle() with the request to process. This
must be one of the following:

  • a DOMDocument
  • a DOMNode
  • a SimpleXMLElement
  • an object implementing __toString(), where that method returns an XML string
  • an XML string

We can grab this information from the MVC request instance’s body content.

Second, we will need the server to return the response, so we can use it to
populate the MVC response instance. We can do that by calling
Server::setReturnResponse(true). When we do, Server::handle() will return an
XML string representing the SOAP response message.

Let’s put it all together:

namespace AcmeController;

use AcmeModel;
use ZendSoapAutoDiscover as WsdlAutoDiscover;
use ZendSoapServer as SoapServer;
use ZendMvcControllerAbstractActionController;

class SoapController extends AbstractActionController
{
    private $env;

    public function __construct(ModelEnv $env)
    {
        $this->env = $env;
    }

    public function wsdlAction()
    {
        /** @var ZendHttpRequest $request */
        $request = $this->getRequest();

        if (! $request->isGet()) {
            return $this->prepareClientErrorResponse('GET');
        }

        $wsdl = new WsdlAutoDiscover();
        $this->populateServer($wsdl);

        /** @var ZendHttpResponse $response */
        $response = $this->getResponse();

        $response->getHeaders()->addHeaderLine('Content-Type', 'application/wsdl+xml');
        $response->setContent($wsdl->toXml());
        return $response;
    }

    public function serverAction()
    {
        /** @var ZendHttpRequest $request */
        $request = $this->getRequest();

        if (! $request->isPost()) {
            return $this->prepareClientErrorResponse('POST');
        }

        // Create the server
        $server = new SoapServer(
            $this->url()
                ->fromRoute('soap/wsdl', [], ['force_canonical' => true]),
            [
                'actor' => $this->url()
                    ->fromRoute('soap/server', [], ['force_canonical' => true]),
            ]
        );
        $server->setReturnResponse(true);
        $this->populateServer($server);

        $soapResponse = $server->handle($request->getContent());

        /** @var ZendHttpResponse $response */
        $response = $this->getResponse();

        // Set the headers and content
        $response->getHeaders()->addHeaderLine('Content-Type', 'application/soap+xml');
        $response->setContent($soapResponse);
        return $response;
    }

    private function prepareClientErrorResponse($allowed)
    {
        /** @var ZendHttpResponse $response */
        $response = $this->getResponse();
        $response->setStatusCode(405);
        $response->getHeaders()->addHeaderLine('Allow', $allowed);
        return $response;
    }

    private function populateServer($server)
    {
        // Expose a class and its methods:
        $server->setClass(ModelCalculator::class);
    
        // Expose an object instance and its methods:
        $server->setObject($this->env);
    
        // Expose a function:
        $server->addFunction('AcmeModelping');
    }
}

The above assumes you’ve created routes soap/server and soap/wsdl, and uses
those to generate the URIs for the server and WSDL, respectively; the
soap/server route should map to the SoapController::serverAction() method
and the soap/wsdl route should map to the SoapController::wsdlAction()
method.

Inject your dependencies!

You’ll note that the above example accepts the AcmeModelEnv instance via
its constructor, allowing us to inject a fully-configured instance into the
server and/or WSDL autodiscovery. This means that you will need to provide a
factory for your controller, to ensure that it is injected with a fully
configured instance — and that likely also means a factory for the
model, too.

To simplify this, you may want to check out the ConfigAbstractFactory
or ReflectionBasedAbstractFactory,
both of which were introduced in version 3.2.0 of zend-servicemanager.

Using zend-soap within PSR-7 middleware

Using zend-soap in PSR-7 middleware is essentially the same as what we detail
for zend-mvc: you’ll need to pull the request content for the server, and use
the SOAP response returned to populate a PSR-7 response instance.

The example below assumes the following:

  • You are using the UrlHelper and ServerUrlHelper from zend-expressive-helpers
    to generate URIs.
  • You are routing to each middleware such that:
    • The ‘soap.server’ route will map to the SoapServerMiddleware, and only
      allow POST requests.
    • The ‘soap.wsdl’ route will map to the WsdlMiddleware, and only
      allow GET requests.
namespace AcmeMiddleware;

use AcmeModel;
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use ZendDiactorosResponseTextResponse;
use ZendSoapAutoDiscover as WsdlAutoDiscover;
use ZendSoapServer as SoapServer;

trait Common
{
    private $env;

    private $urlHelper;

    private $serverUrlHelper;

    public function __construct(
        ModelEnv $env,
        UrlHelper $urlHelper,
        ServerUrlHelper $serverUrlHelper
    ) {
        $this->env = $env;
        $this->urlHelper = $urlHelper;
        $this->serverUrlHelper = $serverUrlHelper;
    }

    private function populateServer($server)
    {
        // Expose a class and its methods:
        $server->setClass(ModelCalculator::class);
    
        // Expose an object instance and its methods:
        $server->setObject($this->env);
    
        // Expose a function:
        $server->addFunction('AcmeModelping');
    }
}

class SoapServerMiddleware
{
    use Common;

    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        callable $next
    ) {
        $server = new SoapServer($this->generateUri('soap.wsdl'), [
            'actor' => $this->generateUri('soap.server')
        ]);
        $server->setReturnResponse(true);
        $this->populateServer($server);

        $xml = $server->handle((string) $request->getBody());

        return new TextResponse($xml, 200, [
            'Content-Type' => 'application/soap+xml',
        ]);
    }

    private function generateUri($route)
    {
        return ($this->serverUrlHelper)(
            ($this->urlHelper)($route)
        );
    }
}

class WsdlMiddleware
{
    use Common;

    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        callable $next
    ) {
        $server = new WsdlAutoDiscover();
        $this->populateServer($server);

        return new TextResponse($server->toXml(), 200, [
            'Content-Type' => 'application/wsdl+xml',
        ]);
    }
}

Since each middleware has the same basic construction, I’ve created a trait with
the common functionality, and composed it into each middleware. As you will
note, the actual work of each middleware is relatively simple; create a server,
and marshal a resposne to return.

In the above example, I use the zend-diactoros-specific
TextResponse type to generate the response; this could be any other response
type, as long as the Content-Type header is set correctly, and the status code
is set to 200.

Per the note above, you will need to
configure your dependency injection container to inject the middleware instances
with the model and helpers.

Summary

While SOAP is often maligned in PHP circles, it is still in wide use within
enterprises, and used in many cases to provide cross-platform web services with
predictable behaviors. It can be quite complex, but zend-soap helps smooth out
the bulk of the complexity. You can use it standalone, within a Zend Framework
MVC application, or within any application framework you might be using.

Visit the zend-soap documentation
to find out what else you might be able to do with this component!

Source: Zend feed

ZF2016-04: Potential remote code execution in zend-mail via Sendmail adapter

ZF2016-04: Potential remote code execution in zend-mail via Sendmail adapter

When using the zend-mail component
to send email via the ZendMailTransportSendmail transport, a malicious user
may be able to inject arbitrary parameters to the system sendmail program.
The attack is performed by providing additional quote characters within an
address; when unsanitized, they can be interpreted as additional command line
arguments, leading to the vulnerability.

The following example demonstrates injecting additional parameters to the
sendmail binary via the From address:

use ZendMail;

$mail = new MailMessage();
$mail->setBody('This is the text of the email.');

// inject additional parameters to sendmail command line
$mail->setFrom('"AAA" params injection"@domain', 'Sender's name');

$mail->addTo('hacker@localhost', 'Name of recipient');
$mail->setSubject('TestSubject');

$transport = new MailTransportSendmail();
$transport->send($mail);

The attack works because zend-mail filters the email addresses using
the RFC 3696 specification,
where the string "AAA" params injection"@domain is considered a valid
address. This validation is provided using the zend-validator component with
the following parameters:

ZendValidatorEmailAddress(
    ZendValidatorHostname::ALLOW_DNS | ZendValidatorHostname::ALLOW_LOCAL
)

The above accepts local domain with any string specified by double quotes as the
local part. While this is valid per RFC 3696, due to the fact that sender email
addresses are provided to the sendmail binary via the command line, they create
the vulnerability described above.

Action Taken

To fix the issue, we added a transport-specific email filter for the From
header in the Sendmail transport adapter. The filter checks for the sequence
" in the local part of the email From address.

$from = $headers->get('From');
if ($from) {
    foreach ($from->getAddressList() as $address) {
        if (preg_match('/\"/', $address->getEmail())) {
            throw new ExceptionRuntimeException("Potential code injection in From header");
        }
    }
}

The patch resolving the vulnerability is available in:

  • zend-mail, starting in version 2.7.2
  • zend-mail, 2.4.11
  • Zend Framework, 2.4.11

Zend Framework 2.5 and 3.0 versions will receive the update automatically, as
executing composer update in proejcts using these versions will update to
zend-mail
2.7.2+.

Acknowledgments

The Zend Framework team thanks the following for identifying the issues and
working with us to help protect its users:

  • The independent security researcher Dawid Golunski,
    who reported the vulnerability to Beyond Security’s SecuriTeam Secure
    Disclosure program;
  • Enrico Zimuel, who provided the patch.

Source: Zend security feed

ZF2016-03: Potential SQL injection in ORDER and GROUP functions of ZF1

ZF2016-03: Potential SQL injection in ORDER and GROUP functions of ZF1

The implementation of ORDER BY and GROUP BY in Zend_Db_Select remained
prone to SQL injection when a combination of SQL expressions and comments were
used. This security patch provides a comprehensive solution that identifies and
removes comments prior to checking validity of the statement to ensure no SQLi
vectors occur.

The implementation of ORDER BY and GROUP BY in Zend_Db_Select of ZF1 is
vulnerable by the following SQL injection:

$db = Zend_Db::factory(/* options here */);
$select = new Zend_Db_Select($db);
$select->from('p');
$select->order("MD5("a(");DELETE FROM p2; #)"); // same with group()

The above $select will render the following SQL statement:

SELECT `p`.* FROM `p` ORDER BY MD5("a(");DELETE FROM p2; #) ASC

instead of the correct one:

SELECT "p".* FROM "p" ORDER BY "MD5(""a("");DELETE FROM p2; #)" ASC

This security fix can be considered an improvement of the previous
ZF2016-02 and
ZF2014-04 advisories.

As a final consideration, we recommend developers either never use user input
for these operations, or filter user input thoroughly prior to invoking
Zend_Db. You can use the Zend_Db_Select::quoteInto() method to filter the
input data, as shown in this example:

$db    = Zend_Db::factory(...);
$input = "MD5("a(");DELETE FROM p2; #)"; // user input can be an attack
$order = $db->quoteInto("SQL statement for ORDER", $input);

$select = new Zend_Db_Select($db);
$select->from('p');
$select->order($order); // same with group()

Action Taken

We fixed the reported SQL injection by removing comments from the SQL statement
before passing it to either the order() or group() methods; this patch
effectively solves any comment-based SQLi vectors.

We used the following regex to remove comments from a SQL statement:

const REGEX_SQL_COMMENTS = '@
    ((['"]).*?[^\]) # $1 : Skip single & double quoted expressions
    |(                   # $3 : Match comments
        (?:#|--).*?$    # - Single line comments
        |                # - Multi line (nested) comments
         /*             #   . comment open marker
            (?: [^/*]    #   . non comment-marker characters
                |/(?!*) #   . ! not a comment open
                |*(?!/) #   . ! not a comment close
                |(?R)    #   . recursive case
            )*           #   . repeat eventually
        */             #   . comment close marker
    )s*                 # Trim after comments
    |(?<=;)s+           # Trim after semi-colon
    @msx';

The patch is available starting in Zend Framework 1.12.20.

Other Information

This SQL injection attack does not affect Zend Framework 2 and 3 versions because the
implementations of ZendDbSqlSelect::order() and ZendDbSqlSelect::group() do
not manage parenthetical expressions.

Acknowledgments

The Zend Framework team thanks the following for identifying the issues and
working with us to help protect its users:

  • Hiroshi Tokumaru (HASH Consulting Corp.), who discovered the issue;
  • Masanobu Katagi (Japan Computer Emergency Response Team Coordination Center),
    who reported the issue;
  • Enrico Zimuel, who provided the patch.

Source: Zend security feed

1 2 3