Sort Composer packages for your git merging pleasure

A lot of git merge conflicts occur when multiple developers add lines to the end of a file/list. This also happens in composer.json, AppKernel.php, translation files, config/application.config.php, CSS files, CHANGELOG, etc…

diff --cc composer.json
index 62e875e,0c526d8..0000000
--- a/composer.json
+++ b/composer.json
@@@ -10,6 -10,6 +10,10 @@@
      "require": {
          "ramsey/uuid": "^3.0",
          "roave/security-advisories": "dev-master",
++<<<<<<< HEAD
 +        "beberlei/assert": "^2.3@dev"
++=======
+         "miljar/php-exif": "dev-master"
++>>>>>>> exif
      }
  }

A nice solution is that every developer adds lines or blocks to random places in a list or a file, but a better solution is to sort these lines (especially for lists like composer.json, translation files, etc…). This way you insert new lines in “random” places, keeping it clear for everyone how things are added.

diff --git a/changelog.rst b/changelog.rst
index d9db648..886b168 100644
--- a/changelog.rst
+++ b/changelog.rst
@@ -19,7 +19,9 @@ June, 2015
 New Documentation
 ~~~~~~~~~~~~~~~~~

+* `#5423 <https://github.com/symfony/symfony-docs/pull/5423>`_ [Security] add & update doc entries on AbstractVoter implementation (Inoryy, javiereguiluz)
 * `#5401 <https://github.com/symfony/symfony-docs/pull/5401>`_ Added some more docs about the remember me feature (WouterJ)
+* `#5384 <https://github.com/symfony/symfony-docs/pull/5384>`_ Added information about the new date handling in the comparison constraints and Range (webmozart, javiereguiluz)
 * `#5382 <https://github.com/symfony/symfony-docs/pull/5382>`_ Added support for standard Forwarded header (tony-co, javiereguiluz)
 * `#5361 <https://github.com/symfony/symfony-docs/pull/5361>`_ Document security.switch_user event (Rvanlaak)

Full diff on Github

Some tools and hacks that assist you with this:

Something that does not work for composer.json, but something I highly recommend, is to add trailing commas in PHP arrays and to not align code (see the blogpost by Made With Love about “How clean are your diffs?”). This eases up merging too!

Happy merging!

Controlling a receipt printer with ZeroMQ and PHP

When you create a POS system as a web app, you have the problem that not all the devices are controllable from your server.

In my case I have the following hardware connected to the workstation:

The input hardware is easy, because a barcode scanner is seen as a keyboard, and both devices can communicate to the server through the web browser.

The tricky part is when a sale is completed, and the customer wants a receipt.

Since the printer is connected to the (dummy) workstation and not to the (remote) server, we cannot print the receipt directly.

The solution I used for this problem is ZeroMQ.

On the server I have a PHP process running which binds to 2 ZeroMQ sockets. One (ZMQ::SOCKET_PULL) is waiting for incoming print request from the web app, one (ZMQ::SOCKET_PUB) is publishing a print request to all subscribing workstations.

On the workstation (in my case a Windows laptop with an HP receipt printer connected to it) I have another PHP process running which is waiting for print jobs (on a ZMQ::SOCKET_SUB socket).

I can add the final piece of the puzzle to the web app, by opening a ZMQ::SOCKET_PUSH socket and sending the URL of the receipt page.

Now I have created a lightweight system for controlling the receipt printer, instead of using cronjobs (or scheduled tasks) to check for print jobs.

Some remarks:

  • I could remove the print-dispatcher part, and let the web app connect to the print receiver directly, but I prefer to have a stable part (the binding sockets) on the known server (so both connecting sockets know the host to connect to).
  • The HTML page could be transported over ZeroMQ, but I like the extra request so the web app is sure the receipt is printed.

make test every PHP version and send feedback

Since David Coallier’s talk during PHPBenelux, I realized the importance of running make test on all PHP releases and send feedback to PHP.

It is so easy, that I will run it from now on every time a new release is announced.

If you’re running on Mac, you might want to install Xcode, so you can run “make” on command line. If you’re on Linux, you’re all set to go.

How to do it:

  • Open a shell
  • Create a directory (e.g. mkdir ~/src/php)
  • Download the latest version into the directory (e.g. wget http://downloads.php.net/stas/php-5.4.0RC6.tar.gz)
  • Untar the file (e.g. tar -xzf php-5.4.0RC6.tar.gz)
  • Go into the directory and run ./configure, you should get output like:
    checking for grep that handles long lines and -e... /usr/bin/grep
    checking for egrep... /usr/bin/grep -E
    checking for a sed that does not truncate output... /usr/bin/sed
    checking build system type... i386-apple-darwin11.2.0
    checking host system type... i386-apple-darwin11.2.0
    checking target system type... i386-apple-darwin11.2.0
    ...
    Thank you for using PHP. 
  • After that, you can run make and should get a lot of output ending in:
    Build complete.
    Don't forget to run 'make test'.
  • After that, you should do as the previous command suggests and run make test. You should see all tests passing by.
  • After all tests are run, you will get a summary. In my case, I get the message: “You may have found a problem in PHP.”
    Bug #55509 (segfault on x86_64 using more than 2G memory) [Zend/tests/bug55509.phpt]
    Sort with SORT_LOCALE_STRING [ext/standard/tests/array/locale_sort.phpt]
  • Whether you get an error or not, you should always send the report back to PHP. You can do that by just answering Y to the question: “Do you want to send this report now?”
  • Enter your email address and PHP will thank you.

How hard was that?

SOAP-ERROR: Parsing WSDL

If you’re writing or consuming webservices with PHP SOAP, it’s possible you run into the SOAP-ERROR: Parsing WSDL problem once.

The complete error string is:
SOAP-ERROR: Parsing WSDL: Couldn't load from 'http://host/service?wsdl' : <specific error>

If you copy and paste the url (including the ?wsdl parameter) in the browser and you see the WSDL file, the problem lies in the fact that the PHP cannot reach the host.

When you connect to http://host/service, PHP fetches the XML from the WSDL page via fopen(‘http://host/service?wsdl‘) so it can use it to handle the request. In some cases, that request is not routed correct, resulting in the SOAP-ERROR.

Some solutions:

  • Add the hostname in the hostfile of the server (127.0.0.1 hostname).
  • Add the hostname or IP address in the correct VirtualHost (ServerAlias hostname).

You can test the code by adding a file on the server:

<?php
echo htmlentities(file_get_contents('http://host/service?wdsl'));

That way, you know if the server can reach and read the XML file.

Random PHP function explained

file1_Open_Source_PHP_logo_667A few weeks ago, Jamie Bicknell notified me he read my article about the Random PHP function. Because I didn’t publish any code, he wrote a 3 line implementation of how he would fetch the functions for the randomness.

I promised him I would publish the code that weekend, and here we are, a few weeks later…

The script actually parses the http://php.net/quickref.php page, saves the result, and redirects the user.

$cacheFile './functions.tmp';
if (!
is_file($cacheFile)) {
    
$quickref 'http://php.net/quickref.php';
    
$xml DOMDocument::loadHTMLFile($quickref);
    
$links $xml->getElementsByTagName('a');
    
$functions = array();
    foreach (
$links as $link) {
        if (
substr($link->getAttribute('href'), 08) == '/manual/') {
            
$functions[$link->nodeValue] = $link->getAttribute('href');
        }
    }
    
file_put_contents($cacheFileserialize($functions));
} else {
    
$functions unserialize(file_get_contents($cacheFile));
}
$function $functions[array_rand($functions)];
$function 'http://php.net' $function;
header('Location: ' $functiontrue303);
die();

Some improvements:

  • Invalidate the cache file when a new PHP version is announced.
  • Read random lines from the file (instead of loading the complete array in the memory).

Update: Forgot the mention the actual blogpost of Jamie Bicknell…

phpBenelux

phpbenelux_logoAs you can see in my Blogroll panel, on my homepage, profile and LinkedIn, I’m a proud member of phpBenelux.

Tuesday there was another meeting, this time in the offices of Netlog, with talks from Ivo Jansch about “PHP and the Cloud” and Felix De Vliegher about “High gear PHP with Gearman”.

Because it took me quite a while to figure out what user group was/is the real one and what’s the plan for the future, I wanted to share my “knownledge” with you:

  • phpBenelux is the new name after phpBelgium and phpgg merged.
    I’m still waiting for Luxembourg, but they’ll come along :).
  • Meetings are held monthly, one month in the Netherlands, the other month in Belgium.
  • A new website (phpBenelux.eu) is on the way, and I’m offering my help, together with my colleague Jochen.

I will post updates on the list above, because there are still some questions that I haven’t figured out yet:

  • How the website is working.
  • What should have happened, after I payed € 15.
  • Why the “statutes” are still those of phpgg.

That being said: phpBenelux is a great user group, where I’ll offer my help to expand the knowledge of PHP into the benelux.

See you on the next meeting!

php msort() – multidimensional array sort

There have been several moments where I had a rowset from a database as a PHP array, and I had to sort it based on some columns.

$tickets = array(
    array(
        
'id' => 13,
        
'owner' => 'jachim',
        
'time' => '2009-09-25 10:39:42.011612',
        
'project' => 'jachim.be',
        
'title' => 'Some random ticket'
    
),
    array(
        
'id' => 31,
        
'owner' => 'jachim',
        
'time' => '2009-09-24 14:38:47.945020',
        
'project' => 'joggink.be',
        
'title' => 'Some other random ticket'
    
),
    array(
        
'id' => 22,
        
'owner' => 'root',
        
'time' => '2009-09-24 10:58:02.904198',
        
'project' => 'joggink.be',
        
'title' => 'A specific ticket'
    
)
);

I searched for functions for a while, and even try to understand/use the array_multisort function, but I never managed to get something working on a simple array (like the one above). The new function msort() should be a solution for this.

The function works normally when you use a string for the second parameter. That way the $sort_flag works like you would expect it. When using an array of keys however, a string key is built to sort the array on. This part could use some improvement.

Here an example of the usage:


var_dump($tickets);

$tickets msort($tickets, array('owner''time'));

var_dump($tickets);

And here’s the function itself:

/**
 * Sort a 2 dimensional array based on 1 or more indexes.
 * 
 * msort() can be used to sort a rowset like array on one or more
 * 'headers' (keys in the 2th array).
 * 
 * @param array        $array      The array to sort.
 * @param string|array $key        The index(es) to sort the array on.
 * @param int          $sort_flags The optional parameter to modify the sorting 
 *                                 behavior. This parameter does not work when 
 *                                 supplying an array in the $key parameter. 
 * 
 * @return array The sorted array.
 */
function msort($array$key$sort_flags SORT_REGULAR) {
    if (
is_array($array) && count($array) > 0) {
        if (!empty(
$key)) {
            
$mapping = array();
            foreach (
$array as $k => $v) {
                
$sort_key '';
                if (!
is_array($key)) {
                    
$sort_key $v[$key];
                } else {
                    
// @TODO This should be fixed, now it will be sorted as string
                    
foreach ($key as $key_key) {
                        
$sort_key .= $v[$key_key];
                    }
                    
$sort_flags SORT_STRING;
                }
                
$mapping[$k] = $sort_key;
            }
            
asort($mapping$sort_flags);
            
$sorted = array();
            foreach (
$mapping as $k => $v) {
                
$sorted[] = $array[$k];
            }
            return 
$sorted;
        }
    }
    return 
$array;
}

Random PHP function

To explore new things, I sometimes use the ‘Random article‘ link on Wikipedia. It is a simple and basic principle, but it gets you to articles you would never get by just browsing the site.

I was looking for something that did the same job for the php.net site, but could not find anything. As a programmer, you’re most likely to end up in the same chapters of the manual over and over again, and a ‘Random function’ link would be nice to find out new stuff.

So I wrote on myself…

The script parses the http://php.net/quickref.php page, and picks a random link to redirect you to. Anyone heard of HaruDoc? 🙂

Try it yourself: Random PHP Function. Good luck!

ini_restore() and str_replace()

Today I’ve found a new function (new for me, that is). The function is called ini_restore(). It’s a small and simple function call, but it’s just cool to use built-in functions and save some typing and variables.

I use ini_set() from time to time in large scripts where I know it will take a while to execute and need some additional resources. I always try to avoid to set resets on the top of the script, because most of the scripts is doing regular stuff. When it would go wrong there, I want to know about it.

This is where ini_restore() comes in to play:

// old
$origMaxExecutionTime = ini_get('max_execution_time');
ini_set('max_execution_time', 900); // 15 minutes
// do some amazingly long work
ini_set('max_execution_time', $origMaxExecutionTime);

// new
ini_set('max_execution_time', 900); // 15 minutes
// do some amazingly long work
ini_restore('max_execution_time');

Aside from that one, I’ve discovered the fourth parameter in str_replace(): &$count. Nice to know whether the function call did anything or not.

Next step is to beat that annoying multibyte thing in str_replace().