Yellow Seesaw on Blue Background

Earlier this year we discussed how you could implement your own version of Zod in PHP to define schemas and validate/sanitize data. Today, I’d like to do something similar with a different library: let’s implement our own version of Lodash for PHP!

First of all, though, let’s start by stating the obvious–if you need a Lodash-like library on PHP, there’s a Composer package that will get you the job done (just like there was one for Zod). But creating your own version of libraries like Lodash or Zod gives you a deeper understanding of their internals and it’s a way to learn a lot along the way and improve your coding skills. So why shouldn’t you give it a try?

Lodash

What is Lodash? I’m pretty sure if you’re here, you already know what it is… but let’s review it in a couple of minutes anyway.

Lodash is a JavaScript utility library. That is, it’s a collection of helper functions that perform common tasks with the aim to make our developer lives easier by solving frequent problems.

For example, let’s say you have a collection of elements like this one:

const collection = [
  { name: 'Alice', age: 41 },
  { name: 'Bob', age: 23 },
  { name: 'John', age: 38 },
];Code language: TypeScript (typescript)

and you’d like to have a dictionary of elements indexed by their name:

const dictionary = {
  Alice: { name: 'Alice', age: 41 },
  Bob:   { name: 'Bob', age: 23 },
  John:  { name: 'John', age: 38 },
};Code language: TypeScript (typescript)

How would you do it in JavaScript? How would you go from the collection to the dictionary programmatically?

Well, if you don’t want to use any external libraries, you could implement this transformation with reduce:

const dictionary = collection.reduce(
  ( r, i ) => ( { ...r, [ i.name ]: i } ),
  {}
);Code language: TypeScript (typescript)

but I’m sure you’ll agree: it looks a little bit confusing and it’s not immediately obvious what’s going on. Unless, of course, you abstract it into a function… which is precisely what Lodash does for you:

import { keyBy } from 'lodash';
const dictionary = keyBy( collection, 'name' );Code language: TypeScript (typescript)

And that’s precisely the beauty of utility libraries like Lodash. They give you convenient functions with well-defined behaviors that make your code faster to write and easier to understand. Pretty neat, huh? And, besides, if you’re a WordPress developer, you should know Lodash is already available, so why not use it?

Implementation

Without any further ado, let’s take a look at some Lodash functions, implement them in PHP, discuss some of the particularities you may encounter when doing so, and see how all the pieces fit together nicely.

Just one final notice: for the sake of simplicity, I will omit namespaces. But just assume all the functions we’ll see are defined inside a namespace to prevent name collisions.

Utils and Functions

Let’s start with some interesting helper functions.

flow

flow is an interesting function to write beautiful code by combining multiple transformations into one. Too abstract? Let’s take a look at the official documentation: “[flow] creates a function that returns the result of invoking the given functions […] (in sequence), where each successive invocation is supplied the return value of the previous.”

So, for example, let’s say you have a list of numbers ($numbers) and you want to convert into a string (stringify) the biggest number (get_max) in the list, and finally get the length (strlen) of said string. You’d probably do something like this:

$max     = get_max( $numbers );
$string  = stringify( $max );
$result  = strlen( $string );Code language: PHP (php)

In this snippet it’s very obvious how the data flows. Each line represents a step in the transformation process. The problem is there’s also a lot of noise: you have to define several intermediate variables and you have to explicitly invoke each function with the result of the previous step.

You could, of course, write the previous snippet more succinctly:

$result = strlen( stringify( get_max( $numbers ) ) );Code language: PHP (php)

but that also has its own problems, of course. For starters, there’s a lot of nested functions and, the more functions you have, the more complicated is to read. Moreover, the functions show up in the opposite order: the first function your eyes catch is strlen, even though that’s the last step of the transformation process. Not ideal.

Well, flow aims to solve all the problems presented in both snippets. When invoking flow, all you gotta do is provide a list of functions/transformations and the result will be, as we stated just a few lines above, a new function that will apply them all in sequence:

flow( [
  'get_max',
  'stringify',
  'strlen',
] )( $numbers )Code language: PHP (php)

So how can we implement this in PHP? It’s actually super easy:

function flow( array $functions ) {
  return fn( $value ) => array_reduce(
    $functions,
    fn( $result, $function ) => call_user_func( $function, $result ),
    $value
  );
}Code language: PHP (php)

Let’s discuss it line by line:

  • First of all, we define flow as a function that receives an array of $functions
  • We know flow produces a new function, so we immediately return a new anonymous arrow function fn( $value ) that, as expected, will receive a single $value (i.e. the value we’ll apply all the transformations to).
  • Finally, we need a method to combine all $functions in a way such that the result of one function becomes the input of the next one. That’s why we use array_reduce, a built-in PHP function that “iteratively reduces an array to a single value using a callback function.”
    • The array we want to reduce is, of course, our list of $functions.
    • The callback function we’ll use is an anonymous function that will take two arguments: the $result of the previous step and the $function we need to apply now using call_user_func.
    • We need to start this iterative process using the $value provided to the anonymous function returned by flow, so we obviously use $value as the initial value in our array_reduce.

negate

A predicate is a function that, given a certain value, returns a boolean value based on some condition or evaluation. For instance, the even predicate would return true if a number is even, and false otherwise.

negate is a helper function that, as its name suggests, negates the result of a given predicate. In other words, it helps you to create the opposite predicate, if that makes sense. For instance, if you want to create the odd predicate, you could define it as negate( even ).

As you can imagine, such a function is also extremely easy to implement but, again, it’ll be very useful when building more complex stuff in the next few paragraphs:

function negate( $predicate ) {
  return fn( ...$values ) => ! call_user_func( $predicate, ...$values );
}Code language: PHP (php)

As you can see, it simply creates a new function that invokes the original $predicate and negates its result. Easy peasy!

Collection

Now that we’ve laid some groundwork, let’s see how we can leverage (some of) the previous functions to build more interesting functions from Lodash.

some

Given a $collection of elements and a $predicate, some checks if the $predicate returns truthy for any element of collection. We can do this using, again, array_reduce:

function some( array $collection, callable $predicate ): bool {
  return array_reduce(
    $collection,
    fn( $result, $item ) => (
      $result ||
      ! empty( call_user_func( $predicate, $item ) )
    ),
    false
  );
}Code language: PHP (php)

However, in Lodash, it’s stated that “iteration is stopped once predicate returns truthy,” so if you want to implement this behavior as well, you’ll have to use a foreach-loop:

function some( array $collection, callable $predicate ): bool {
  foreach ( $collection as $item ) {
    if ( ! empty( call_user_func( $predicate, $item ) ) ) {
      return true;
    }
  }
  return false;
}Code language: PHP (php)

every

every is very similar to some, but in this particular case we check that all elements in the $collection satisfy the $predicate. You could, of course, implement this new function using an array_reduce using ands instead of ors:

function every( array $collection, callable $predicate ): bool {
  return array_reduce(
    $collection,
    fn( $result, $item ) => (
      $result &&
      ! empty( call_user_func( $predicate, $item ) )
    ),
    true
  );
}Code language: PHP (php)

However, in this particular instance we can’t short-circuit the implementation because we have to make sure that all elements in the $collection satisfy the $predicate. Or can we?

Well, let’s think about it for a moment. If all elements satisfy the predicate, it means there’s none that doesn’t. Pretty obvious, isn’t it? And, with this in mind, we can implement the function again using a foreach-loop:

function every( array $collection, callable $predicate ): bool {
  foreach ( $collection as $item ) {
    if ( empty( call_user_func( $predicate, $item ) ) ) {
      return false;
    }
  }
  return true;
}Code language: PHP (php)

that returns false as soon as we find an element that does not satisfy the $predicate. Cool!

But can we do it better? Look at the definition of every I’ve just given you: “All elements satisfy the predicate if, and only if, there’s not a single element that doesn’t.” Which means we can implement this function using some and negate! Look:

function every( array $collection, callable $predicate ): bool {
  return ! some( $collection, negate( $predicate ) );
}Code language: PHP (php)

We’ve now implemented a whole new method using the building-blocks we’ve been creating so far! Isn’t that wholesome?

key_by

The final example I wanted to bring today was, of course, the example we started this post with: keyBy. Let’s see how one could implement a first version of this function:

function key_by( $collection, $prop ) {
  $keys = array_map(
    fn( $item ) => isset( $item[ $prop ] ) ? $item[ $prop ] : '',
    $collection
  );
  return array_combine( $keys, $collection );
}Code language: PHP (php)

Let’s break it down:

  • First, for every element in the $collection, we get its key. To do that, we simply check if the element has the provided $prop and, if it does, we use it. Otherwise, we return the empty string ''.
  • This results in an array of $keys that has the exact same length as our $collection. Therefore, we can use PHP’s array_combine to “create an array by using one array for keys and another for its values.” In other words, we’re creating the dictionary.

Pretty neat, huh? So why did I say this is just a “first version”? Well, if you look at Lodash’s documentation for this function, you’ll notice that the second argument in keyBy is not necessarily a property of the elements included in the $collection–instead, it can be an iteratee function that, given an element in the $collection, produces a key.

Okay… so let’s rewrite the function with this in mind, shall we?

function key_by( $collection, $iteratee ) {
  $get_key = is_callable( $iteratee )
    ? $iteratee
    : fn( $item ) => isset( $item[ $iteratee ] ) ? $item[ $iteratee ] : '';
  $keys = array_map(
    fn( $item ) => call_user_func( $get_key, $item ),
    $collection
  );
  return array_combine( $keys, $collection );
}Code language: PHP (php)

“Wait, what?”

Let me explain:

  • As we’ve already discussed, the second argument in key_by, $iteratee, can either be (a) a function that, given an item in the $collection, produces a key or (b) a property the elements in the $collection may have.
  • Since $iteratee can be two things, we need to prepare our function to deal with both scenarios:
    • If it’s callable (that is, if it’s a function), we know we have to use it to get the key from an element in the collection.
    • If it’s not callable, then it’s a property we need to extract from all the elements. Therefore, we create an anonymous callable function that extracts that property from any given element.
  • Once we’ve dealt with both scenarios, we end up having a callable function named $get_key that we can use in our array_map to get all the necessary keys.

And that’s it! Pretty cool, huh?

“Are you sure this will always work?”

Okay, you got me. Unfortunately, no, this doesn’t always work. In PHP, you can specify callback functions by name:

$dictionary = key_by( $collection, 'something' );Code language: PHP (php)

which poses a problem. If 'something' is both a callable function named something and a property that all elements in the collection have:

function something( $item ) { ... }
$collection = [
  array( 'something' => 'a', ... ),
  array( 'something' => 'b', ... ),
  array( 'something' => 'c', ... ),
];
$dictionary = key_by( $collection, 'something' );Code language: PHP (php)

how the heck does our code know if we want to generate the keys using the function or the property? It can’t!

So what do we do next?

Well, it’s up to you how to solve this problems, but here there are a few ideas.

Solution 1. Create two different functions: key_by to use properties and key_by_fn to use a callback function. You can even write the former using the latter, as we’ve just seen:

function key_by( $collection, $prop ) {
  $iteratee = fn( $item ) => isset( $item[ $iteratee ] ) ? $item[ $iteratee ] : '';
  return key_by_fn( $collection, $iteratee );
}
function key_by_fn( $collection, $iteratee ) {
  $keys = array_map(
    fn( $item ) => call_user_func( $get_key, $item ),
    $collection
  );
  return array_combine( $keys, $collection );
}Code language: PHP (php)

Solution 2. Don’t accept stringified callbacks:

function key_by( $collection, $iteratee ) {
  $get_key = is_string( $iteratee ) || ! is_callable( $iteratee )
    ? fn( $item ) => isset( $item[ $iteratee ] ) ? $item[ $iteratee ] : '';
    : $iteratee;
  $keys = array_map(
    fn( $item ) => call_user_func( $get_key, $item ),
    $collection
  );
  return array_combine( $keys, $collection );
}Code language: PHP (php)

so, if you want to use the something function, you’d need to do something like this:

function something( $item ) { ... }
$dictionary = key_by( $collection, fn( $item ) => something( $item ) );Code language: PHP (php)

What Next?

Today we’ve seen that translating a library like Lodash into PHP is not necessarily an easy cut. You have to consider the particularities of each language and think how they can interfere with your desired outcomes. But, in doing so, you’ll get a deeper understanding of both JavaScript and PHP, which is always nice.

If you’re used to programming in JavaScript with Lodash, I’m sure you’ll miss some of its helpers when programming in PHP. And, when that occurs, all you gotta do is implement the function you’re missing when the need arises, as we’ve just seen and done today.

For example, there’s one method in particular that I also use quite often: get. Given an object or an array with nested properties, it’s pretty neat to do something like this:

$value = get( $object, [ 'some', 'nested', 'properties', 0', 'value' ], '' );Code language: PHP (php)

where you simply specify the path you want to access and the function will take care of the rest, like making sure each step in the path exists and what not. Heck, you can even tweak the previous function and make it more convenient by accepting a path that’s a single string instead of an array:

$value = get( $object, 'some.nested.properties[0].value', '' );Code language: PHP (php)

Why don’t you give it a try and implement it yourself? Share with me your implementation in the comment section below!

Featured Image by Chun Kit Soo on Unsplash.

Leave a Reply

Your email address will not be published. Required fields are marked *

I have read and agree to the Nelio Software Privacy Policy

Your personal data will be located on SiteGround and will be treated by Nelio Software with the sole purpose of publishing this comment here. The legitimation is carried out through your express consent. Contact us to access, rectify, limit, or delete your data.