Playing with PHP

Lately I have been “playing a bit” with php and I cannot really say I liked it.
There’s nothing new in this. I mean, many brilliant people have written about it before me.

So I will not write about how bad php is, and how miserable life is when working with it.

Instead, I will write about some of the learnings I had along the way.
They might be something trivial for a php expert. But hey, I do not want to have this experience going for nothing :)

By the way, just for the sake of completeness, there are voices in the other direction (e.g. see this post from Vimeo engineering); personally, I agree more with the detractors.
Also, there are cool parts: like calling a method by a variable with its name – this is possible in other languages as well, but here is definitely easier:

$result = $this->connector->{ $method_name }( null, null )

Arrays

Arrays are both “vectors” and maps. This means that with the same syntax you can declare a “vector/list” and a map:

$v = [1, 2, 3];
$m = ['a' => 1, 'b' => 2, 'c' => 3];

There are very few primitives on that data structure; and most of the operations are done through functions, usually array_*. For instance, array_column, or array_map.
To add an element to an array, array_push; to add an array to another array, array_merge, To know if an element is in the array, use isset.
Use count to get the length of an array. Note that this is not a zero-cost operation(!) (see documentation). Also, sizeof is just an alias for count.

Iterating over an array

There are several ways to iterate over an array. I used for-each loops and array_map:

$a = [1, 2, 3];
$b = [];
foreach ( $a as $element ) {
  array_push( $b, $elem );
}

$c = array_map( function( $elem ) { return $elem; }, $a );

The second method usually involves using anonymous functions, which work pretty much in a similar way as in other languages.
Anonymous functions do not automatically create a closure; you need to define the used variables with use.
The element is always copied by value, unless a reference is captured:

function( $elem ) use ( &$value_to_look ) {
  return $elem == $value_to_look;
}

Array column

Noteworthy, when talking about iterating and array map, the variety of tricks available with array_column – which allows selecting a subset of a “tuple” ( which again is an array).
Personally, I used it once, and still wrap my head around it – it works and made code much leaner, but I am not really able to tell how it works, and to maintain it in a reasonable way.
Here is an example:

$headers = array_column(
  array_map(  // Translate an object to an array
    function( $object ) {
      return [
        'member1'  => strtoupper( $object->member1 ),
        'member2' => $object->member2,
      ];
    },
    $array_of_objects
  ),
  'member1',
  'member2'
);

First element of an array

Curiously, there is no array_* function to retrieve the first element of an array satisfying a predicate. For that purpose, there are two options: use current and array_filter (nicer, but always evaluates the entire array), or use a foreach loop to find the element.

$a = [1, 2, 3];
$value_to_look = 2;

$option1 = current(
  array_filter(
    $a,
    function( $elem ) use ( $value_to_look ) {
      return $elem == $value_to_look;
    }
  )
);

$option2 = null;
foreach ( $a as $elem ) {
  if $elem == $value_to_look {
    $option2 = $elem;
    break;
  }
}

Unpacking

Unpacking an array to function parameters is possible, but – as usual – requires using an helper function, call_user_func_array:

call_user_func_array ( 'function_name' , $args );

Unit testing

The standard unit testing library in php is phpunit, whose documentation is surprisingly not the first item when searching for “php unit” on ddg.
Anyways, you can find it on https://phpunit.readthedocs.io.

Documentation

Documentation is pretty good, especially for php standards – where it looks like most libraries expect you to read the code in order to understand how to use them. Not only: it has one of the clearest explanations about what mocks are and the various type of use cases are for them.
Still, there were cases (e.g. when analyzing the issue with stubbing) where I had actually to make tests and look at the source to see some of the issues.

Writing tests

Assertions are rich but a bit weird – however it might simply be another of the cases where I need to get used to php. I still don’t know the casing standards.
Small side note: I have never understood why most unit test libraries (with some notable exceptions, like pytest) use Expected == Actual assertEquals. I always think the other way round, assert actual == expected.

Parametrization – IMHO one of the most important features in a modern unit testing framework – is possible, using the @dataProvider attribute.

Mocks

Documentation on mocks is very good (even if not complete), as it clearly draws the differences between stubs, mocks and test doubles.

Some notes:

  • Assertions on the mock must be done before the call (differently from python). On the other hand, I do not like making assertions on mocks (because it means) asserting on implementation details, and not on the function interface), so I was not really able to spot the difference.

  • Lack of extreme dynamism like in python, means that you need to use standard DI techniques like in java (e.g.: move behavior in a class passed as function parameter, then mocked in tests). There are tricks using namespaces that might allow overcoming that issue; but they really feel like hacks, and do not work on objects/functions already defined in a namespace.
    To be honest, this is not really an issue – it was more just the “pythonista” in me trying to use paradigms from other languages.

  • I could not find a way to make complex assertions on a parameter being passed (e.g. using regular expressions)

One bigger issue I had encountered: when stubbing, it appears that any behavior redefinition will fail.

The reasons is that every time you use method, it will create a new matcher.
Since all of them will match, the mock will always select the first one, ignoring the new ones.
Unfortunately, there are no solutions, only workarounds; most notably, use a callback and then have it returning a class member:

public function setUp() {
  $this->bar_value = 'bar value';
  $this->baz_value = 'baz value';

  $this->foo_spy = $this->createMock(Foo::class);
  $this->foo_spy->method('getBar')->will( $this->returnCallback(function () { return $this->bar_value; } ) );
  $this->foo_spy->method('getBaz')->will( $this->returnCallback(function () { return $this->baz_value; } ) );

  $this->sut = new CreateFoo;
}

php

1107 Words

2021-01-22 20:10 +0100