Skip to content

7 Create your own module

Juan Morales edited this page Mar 5, 2021 · 2 revisions

Create your own Module

Requirements

Please go through the wiki, and through the built-in modules documentation and code comments, so you can understand ... really understand what are we building here.

This tutorial assumes that you already have the base knowledge proided by the wiki section.

What are we going to build

Lets imagine, that we want to use a Tracker during the execution of our php script.

Also we would like to generate logs values showing the number of files included, and we want this valus to be part of the final log generated by Tracker.

Basically we are talking about a module that is part of the generation of a log record, because we dont care how we are going to "see" these values later (in txt, csv, chart, etc.), we just want to get them.

Technical perspective

This module will be mainly using the get_included_files() php function, which returns an array with all the files included in the running php instance.

Coding the module

Create a file called IncludedFiles.php in the Modules folder. This file will hold a php class called IncludedFiles that will implement the IModule interface.

It is necessary to implement the IModule interface, because the Tracker instance that will use this module, will be expecting to receive "something" tat "implements certain behaviour", this is out of the scope of the tutorial.

So far our module should look like this:

<?php declare(strict_types=1);

namespace Footprint\Modules;

use Footprint\Interfaces\IModule;
use Footprint\Tracker;

class IncludedFiles implements IModule
{
    public function getId() {
        return;
    }

    public function onInit(Tracker &$tracker) {
        return;
    }

    public function onEnd(Tracker &$tracker) {
        return;
    }

    public function onLoad(Tracker &$tracker) {
        return;
    }

    public function onUnload(Tracker &$tracker) {
        return;
    }

    public function onLog(Tracker &$tracker) {
        return;
    }

    public function onLogBuild(Tracker &$tracker) {
        return;
    }

    public function getKeys() : array {
        return [];
    }
}

So let's think about how this module should work.

As we said before, the purposse of this module is to include the number of included files into the actual log record being generated, so later, when its saved/store somewhere else (like a CSV file or ChartJS report, etc.), the values of this module can be visualize.

In order to achieve the mentioned goal:

  • Need to use the get_included_files() php function in order to retrieve the number of included files
  • We are going to use the following hooks from the Tracker:
    • onLogBuild

And basically this is it, we dont need to be aware about anything else.

Lets first define the module id and the "keys" of the module, where the data should store in the generated log.

Your code should like something like this by now:

<?php declare(strict_types=1);

namespace Footprint\Modules;

use Footprint\Interfaces\IModule;
use Footprint\Tracker;

class IncludedFiles implements IModule
{
    const MODULE_ID = "INCFILES";
    const KEY_TOTAL_NUMBER = self::MODULE_ID."_total_number";

    public function getId() {
        return self::MODULE_ID;
    }

    public function onInit(Tracker &$tracker) {
        return;
    }

    public function onEnd(Tracker &$tracker) {
        return;
    }

    public function onLoad(Tracker &$tracker) {
        return;
    }

    public function onUnload(Tracker &$tracker) {
        return;
    }

    public function onLog(Tracker &$tracker) {
        return;
    }

    public function onLogBuild(Tracker &$tracker) {
        return;
    }

    public function getKeys() : array {
        return [
            self::KEY_TOTAL_NUMBER,
        ];
    }
}

Lets move on.

Now lets use the onLogBuild() hook, to let this module be part of the log generation by the Tracker.

Our code should look like this by now:

<?php declare(strict_types=1);

namespace Footprint\Modules;

use Footprint\Interfaces\IModule;
use Footprint\Tracker;

class IncludedFiles implements IModule
{
    const MODULE_ID = "INCFILES";
    const KEY_TOTAL_NUMBER = self::MODULE_ID."_total_number";

    public function getId() {
        return self::MODULE_ID;
    }

    public function onInit(Tracker &$tracker) {
        return;
    }

    public function onEnd(Tracker &$tracker) {
        return;
    }

    public function onLoad(Tracker &$tracker) {
        return;
    }

    public function onUnload(Tracker &$tracker) {
        return;
    }

    public function onLog(Tracker &$tracker) {
        return;
    }

    public function onLogBuild(Tracker &$tracker) {
        $tracker->addLogData(self::KEY_TOTAL_NUMBER, count(get_included_files()));
    }

    public function getKeys() : array {
        return [
            self::KEY_TOTAL_NUMBER,
        ];
    }
}

So this seems to be finish ... but as good programmers as we are, lets write a test for this new module.

Lets create a file called IncludedFilesTest.php under the test folder with the following content:

<?php declare(strict_types = 1);

use PHPUnit\Framework\TestCase;
use Footprint\Tracker;
use Footprint\Modules\IncludedFiles;

class IncludedFilesTest extends TestCase 
{
    public function testModule() {
        $this->assertTrue(true);
    }
}

Now lets think about a little bit what we want to test and how.

First we need an instance of our module so lets created and run the test.

<?php declare(strict_types = 1);

use PHPUnit\Framework\TestCase;
use Footprint\Tracker;
use Footprint\Modules\IncludedFiles;
use Footprint\Interfaces\IModule;

class IncludedFilesTest extends TestCase 
{
    public function testModule() {
        $moduleIncFiles = new IncludedFiles();
        $this->assertInstanceOf(IModule::class, $moduleIncFiles);
    }
}

and run this:

$ ./vendor/bin/phpunit test/IncludedFilesTest.php

And if everything is OKish , you should get an output like this one

PHPUnit 9.5.0 by Sebastian Bergmann and contributors.

Runtime:       PHP 7.4.13
Configuration: /home/jcm/dev/php/footprint-php/phpunit.xml

.                                                                   1 / 1 (100%)

Time: 00:00.063, Memory: 4.00 MB

OK (1 test, 1 assertion)

So looks good so far, now lets think about how to test the usage of this module.

The goal of the module is to add the number of the included files into the log record being created by the Tracker, this means that somehow we should include files during testing in order to et different values from this module we are creating, so our test will produce two temporal php files, that will be use during the testing.

Our testing file so far:

<?php declare(strict_types = 1);

use PHPUnit\Framework\TestCase;
use Footprint\Tracker;
use Footprint\Modules\IncludedFiles;
use Footprint\Interfaces\IModule;

class IncludedFilesTest extends TestCase 
{
    public function testModule() {
        $fileOne = tempnam(sys_get_temp_dir(), 'tmpIncFiles');
        file_put_contents($fileOne, '<?php ?>');

        $fileTwo = tempnam(sys_get_temp_dir(), 'tmpIncFiles');
        file_put_contents($fileTwo, '<?php ?>');

        include $fileOne;
        include $fileTwo;

        $moduleIncFiles = new IncludedFiles();
        $this->assertInstanceOf(IModule::class, $moduleIncFiles);
    }
}

Now lets make use of our module, by creating an instance of a Tracker, loading our module, generating a log, and later check the log generated. By using our modules's getKeys() method we are able to exactly point to the part of the log that is concerning to our module (In this test our module is the only one involved during log generaiton, but in real scenario, more than one module will be involved, actually, from the module's perspective, modules should not care "who" is involved during log creation).

So first lets check that the "keys" for our module are present in the generated log:

<?php declare(strict_types = 1);

use PHPUnit\Framework\TestCase;
use Footprint\Tracker;
use Footprint\Modules\IncludedFiles;
use Footprint\Interfaces\IModule;

class IncludedFilesTest extends TestCase 
{
    public function testModule() {
        $fileOne = tempnam(sys_get_temp_dir(), 'tmpIncFiles');
        file_put_contents($fileOne, '<?php ?>');

        $fileTwo = tempnam(sys_get_temp_dir(), 'tmpIncFiles');
        file_put_contents($fileTwo, '<?php ?>');

        include $fileOne;
        include $fileTwo;

        $moduleIncFiles = new IncludedFiles();
        $this->assertInstanceOf(IModule::class, $moduleIncFiles);

        $tracker = new Tracker();
        $tracker->loadModule($moduleIncFiles);
        $tracker->init();
        $tracker->log();
        $logData = $tracker->getLogData();
        $tracker->end();

        $this->assertTrue(array_key_exists($moduleIncFiles::KEY_TOTAL_NUMBER, $logData));
    }
}

Now ... how many files you we have loaded? ... during the test we have included two files manually, so (actual running script + two manually added = 3 files ... right?), lets check this too:

<?php declare(strict_types = 1);

use PHPUnit\Framework\TestCase;
use Footprint\Tracker;
use Footprint\Modules\IncludedFiles;
use Footprint\Interfaces\IModule;

class IncludedFilesTest extends TestCase 
{
    public function testModule() {
        $fileOne = tempnam(sys_get_temp_dir(), 'tmpIncFiles');
        file_put_contents($fileOne, '<?php ?>');

        $fileTwo = tempnam(sys_get_temp_dir(), 'tmpIncFiles');
        file_put_contents($fileTwo, '<?php ?>');

        include $fileOne;
        include $fileTwo;

        $moduleIncFiles = new IncludedFiles();
        $this->assertInstanceOf(IModule::class, $moduleIncFiles);

        $tracker = new Tracker();
        $tracker->loadModule($moduleIncFiles);
        $tracker->init();
        $tracker->log();
        $logData = $tracker->getLogData();
        $tracker->end();

        $this->assertTrue(array_key_exists($moduleIncFiles::KEY_TOTAL_NUMBER, $logData));

        $loadedFiles = (int)($logData[$moduleIncFiles::KEY_TOTAL_NUMBER]);

        $this->assertEquals(3, $loadedFiles);
    }
}

If you execute the test you might not get the expected result ... but why ?

PHPUnit 9.5.0 by Sebastian Bergmann and contributors.

Runtime:       PHP 7.4.13
Configuration: /home/jcm/dev/php/footprint-php/phpunit.xml

F                                                                   1 / 1 (100%)

Time: 00:00.006, Memory: 6.00 MB

There was 1 failure:

1) IncludedFilesTest::testModule
Failed asserting that 99 matches expected 3.

/home/jcm/dev/php/footprint-php/test/IncludedFilesTest.php:34

FAILURES!
Tests: 1, Assertions: 3, Failures: 1.

Lets see what the get_included_files() used by the module, returns ...

<?php declare(strict_types = 1);

use PHPUnit\Framework\TestCase;
use Footprint\Tracker;
use Footprint\Modules\IncludedFiles;
use Footprint\Interfaces\IModule;

class IncludedFilesTest extends TestCase 
{
    public function testModule() {
        $fileOne = tempnam(sys_get_temp_dir(), 'tmpIncFiles');
        file_put_contents($fileOne, '<?php ?>');

        $fileTwo = tempnam(sys_get_temp_dir(), 'tmpIncFiles');
        file_put_contents($fileTwo, '<?php ?>');

        include $fileOne;
        include $fileTwo;

        $moduleIncFiles = new IncludedFiles();
        $this->assertInstanceOf(IModule::class, $moduleIncFiles);

        $tracker = new Tracker();
        $tracker->loadModule($moduleIncFiles);
        $tracker->init();
        $tracker->log();
        $logData = $tracker->getLogData();
        $tracker->end();

        $this->assertTrue(array_key_exists($moduleIncFiles::KEY_TOTAL_NUMBER, $logData));

        var_dump(get_included_files());return;
        
        $loadedFiles = (int)($logData[$moduleIncFiles::KEY_TOTAL_NUMBER]);

        $this->assertEquals(3, $loadedFiles);
    }
}

As you can see in the dump ... there are a lot ... a lot of phpunit files being loaded, and this number might change betwen differnt phpunit version ... so we cannot trust this number.

So seems we can add a feature to our module to be able to filter the number of files to be considered in the counting, lets implement this, I will show you the final code for this feature:

With such a feature our module looks like this now (please look for the //check marks in the code):

<?php declare(strict_types=1);

namespace Footprint\Modules;

use Footprint\Interfaces\IModule;
use Footprint\Tracker;

class IncludedFiles implements IModule
{
    const MODULE_ID = "INCFILES";
    const KEY_TOTAL_NUMBER = self::MODULE_ID."_total_number";

    //check
    protected $filter = null;

    public function getId() {
        return self::MODULE_ID;
    }

    public function onInit(Tracker &$tracker) {
        return;
    }

    public function onEnd(Tracker &$tracker) {
        return;
    }

    public function onLoad(Tracker &$tracker) {
        return;
    }

    public function onUnload(Tracker &$tracker) {
        return;
    }

    public function onLog(Tracker &$tracker) {
        return;
    }

    //check
    public function onLogBuild(Tracker &$tracker) {
        $includedFiles = get_included_files();

        if ($this->filter) {
            $includedFiles = array_filter($includedFiles, $this->filter, ARRAY_FILTER_USE_BOTH);
        }

        $tracker->addLogData(self::KEY_TOTAL_NUMBER, count($includedFiles));
    }

    public function getKeys() : array {
        return [
            self::KEY_TOTAL_NUMBER,
        ];
    }

    //check
    public function setFilter(callable $func) {
        $this->filter = $func;
    }

    //check
    public function unsetFilter() {
        $this->filter = null;
    }
}

And our test looks like this:

<?php declare(strict_types = 1);

use PHPUnit\Framework\TestCase;
use Footprint\Tracker;
use Footprint\Modules\IncludedFiles;
use Footprint\Interfaces\IModule;

class IncludedFilesTest extends TestCase 
{
    public function testModule() {
        $fileOne = tempnam(sys_get_temp_dir(), 'tmpIncFiles');
        file_put_contents($fileOne, '<?php ?>');

        $fileTwo = tempnam(sys_get_temp_dir(), 'tmpIncFiles');
        file_put_contents($fileTwo, '<?php ?>');

        include $fileOne;
        include $fileTwo;

        $moduleIncFiles = new IncludedFiles();
        $this->assertInstanceOf(IModule::class, $moduleIncFiles);

        //check
        $moduleIncFiles->setFilter(function ($value, $key) {
            if (strpos($value, '/phpunit/') === false && strpos($value, '/vendor/') === false) {
                return true;
            }

            return false;
        });

        $tracker = new Tracker();
        $tracker->loadModule($moduleIncFiles);
        $tracker->init();
        $tracker->log();
        $logData = $tracker->getLogData();
        $tracker->end();

        $this->assertTrue(array_key_exists($moduleIncFiles::KEY_TOTAL_NUMBER, $logData));
       
        $loadedFiles = (int)($logData[$moduleIncFiles::KEY_TOTAL_NUMBER]);

        $this->assertEquals(6, $loadedFiles); //check
    }
}

and that is it! you have created your first module with its proper test, congratz!!!!!