Callbacks / Callables in php with unit tests

If you have used function like array_filter you have already used callbacks in php.

A simple example of call back is returning odd and even number from a array of numbers

Example code

function odd($var)
{
    // returns whether the input integer is odd
    return $var & 1;
}

function even($var)
{
    // returns whether the input integer is even
    return !($var & 1);
}

$array1 = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5];
$array2 = [6, 7, 8, 9, 10, 11, 12];

echo "Odd :\n";
print_r(array_filter($array1, "odd"));
echo "Even:\n";
print_r(array_filter($array2, "even"));

In this example I will show you how to write callback, with each callback function in its own class.

Lets say we have a function called goodName which accepts an array of names as first parameter. The second parameter is the callable function which will return the names which are correct for the specific task. Remember I said "will return the names which are correct for the specific task"

Class Names with callable function

class Names
{
    /**
     * @param array $names
     * @param callable $callback
     * @return array
     */
    public function goodNames(array $names, callable $callback): array
    {
        $names = call_user_func($callback, $names);
        return $names;
    }
}

Our task wants to get all the names starting with letter 's'

class WithLetterS
{
    /**
     * @param array $names
     * @return array
     */
    public function fetch(array $names): array
    {
        $data = [];
        foreach ($names as $name) {
            if (strtolower(str_split($name)[0]) === 's') {
                $data[] = $name;
            }
        }
        return $data;
    }
}

Our task wants to get get all the names starting with letter 'k'

class WithLetterK
{
    /**
     * @param array $names
     * @return array
     */
    public function fetch(array $names): array
    {
        $data = [];
        foreach ($names as $name) {
            if (strtolower(str_split($name)[0]) === 'k') {
                $data[] = $name;
            }
        }
        return $data;
    }
}

How to use this object in normal code

$names = ['salman', 'sunil', 'jamaal', 'kaiss', 'faiz', 'kamran'];

$object = new Names();
var_dump($object->goodNames($names, [new WithLetterS(), 'fetch']));
will return ['salman', 'sunil'];

var_dump($object->goodNames($names, [new WithLetterK(), 'fetch']))
will return ['kaiss', 'kamran'];

All good and we get our results. But the work is not yet finished. As good developers we as well have to write tests for this class. Below you will find the correct unit test for the class Names and the callback classes WithLetterS and WithLetterK.

use PHPUnit\Framework\TestCase;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;

class NamesTest extends TestCase
{

    public function testWithLettersStartingWithS(): void
    {
        $someValue = ['salman', 'kaiss', 'sunil', 'sam'];

        $mock = $this->createMock(WithLetterS::class);
        $mock
            ->expects($this->once())
            ->method('fetch')
            ->will($this->returnCallback(
                function () {
                    return ['salman', 'sunil', 'sam'];
                }
            ));

        $object = $this->getObjectToBeTested();
        $data = $object->goodNames($someValue, [$mock, 'fetch']);
        $this->assertEquals(3, count($data));
        $this->assertContains('salman', $data);
        $this->assertContains('sunil', $data);
        $this->assertContains('sam', $data);
        $this->assertNotContains('kaiss', $data);
    }

    public function testWithLettersStartingWithK(): void
    {
        $someValue = ['salman', 'kaiss', 'sunil'];

        $mock = $this->createMock(WithLetterK::class);
        $mock
            ->expects($this->once())
            ->method('fetch')
            ->will($this->returnCallback(
                function () {
                    return ['kaiss'];
                }
            ));

        $object = $this->getObjectToBeTested();
        $data = $object->goodNames($someValue, [$mock, 'fetch']);
        $this->assertEquals(1, count($data));
        $this->assertContains('kaiss', $data);
        $this->assertNotContains('salman', $data);
    }

    /**
     * @return Names
     */
    private function getObjectToBeTested(): Names
    {
        $objectManager = new ObjectManager($this);

        /** @var Names $object */
        $object = $objectManager->getObject(Names::class);

        return $object;
    }
}

Tip for magento2 developers
In magento2 you can you the walk function of \Magento\Framework\Model\ResourceModel\Iterator class to iterate over magento collection efficiently. It is highly recomended to use the walk function if you are going to run a command with lots of records.

what clients say about us

Client testimonials

Our sites look great and run smoothly. Thanks for all your hard work and patience in getting us to where we are today.

contact us

Contact Us

If you have questions or want us to send you a quote for project please email us.

latest news

Articles

PHPUnit mock magic functions
Learn more about PHPUnit Partial Mock.