# Custom `QROutputInterface`

Let’s suppose that we want to create our own output interface because there’s no built-in output class that supports the format we need for our application. In this example we’ll create a string output class that outputs the coordinates for each module, separated by module type.

## Class skeleton

We’ll start with a skeleton that extends `QROutputAbstract` and implements the methods that are required by `QROutputInterface`:

    class MyCustomOutput extends QROutputAbstract{

    	public static function moduleValueIsValid($value):bool{}

    	protected function prepareModuleValue($value){}

    	protected function getDefaultModuleValue(bool $isDark){}

    	public function dump(string $file = null){}

    }

## Module values

The validator should check whether the given input value and range is valid for the output class and if it can be given to the `QROutputAbstract::prepareModuleValue()` method. For example in the built-in GD output it would check if the value is an array that has a minimum of 3 elements (for RGB), each of which is numeric.

In this example we’ll accept string values, the characters `a-z` (case-insensitive) and a hyphen `-`:

    	public static function moduleValueIsValid($value):bool{
    		return is_string($value) && preg_match('/^[a-z-]+$/i', $value) === 1;
    	}

To prepare the final module substitute, we should transform the given (validated) input value in a way so that it can be accessed without any further calls or transformation. In the built-in output for example this means it would return an `ImagickPixel` instance or the integer value returned by `imagecolorallocate()` on the current `GdImage` instance.

For our example, we’ll lowercase the validated string:

    	protected function prepareModuleValue($value):string{
    		return strtolower($value);
    	}

Finally, we need to provide a default value for dark and light, we can call `prepareModuleValue()` here if necessary. We’ll return an empty string `''` as we’re going to use the `QROutputInterface::LAYERNAMES` constant for non-existing values (returning `null` would run into an exception in `QROutputAbstract::getModuleValue()`).

    	protected function getDefaultModuleValue(bool $isDark):string{
    		return '';
    	}

## Transform the output

In our example, we want to collect the modules by type and have the collections listed under a header for each type. In order to do so, we need to collect the modules per `$M_TYPE` before we can render the final output.

    	public function dump(string $file = null):string{
    		$collections = [];

    		// loop over the matrix and collect the modules per layer
    		foreach($this->matrix->getMatrix() as $y => $row){
    			foreach($row as $x => $M_TYPE){
    				$collections[$M_TYPE][] = $this->module($x, $y, $M_TYPE);
    			}
    		}

    		// build the final output
    		$out = [];

    		foreach($collections as $M_TYPE => $collection){
    			$name  = ($this->getModuleValue($M_TYPE) ?: $this::LAYERNAMES[$M_TYPE]);
    			// the section header
    			$out[] = sprintf("%s (%012b)\n", $name, $M_TYPE);
    			// the list of modules
    			$out[] = sprintf("%s\n", implode("\n", $collection));
    		}

    		return implode("\n", $out);
    	}

We’ve introduced another method that handles the module rendering, which incooperates handling of the `QROptions::$drawLightModules` setting:

    	protected function module(int $x, int $y, int $M_TYPE):string{

    		if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
    			return '';
    		}

    		return sprintf('x: %s, y: %s', $x, $y);
    	}

Speaking of option settings, there’s also `QROptions::$connectPaths` which we haven’t taken care of yet - the good news is that we don’t need to as it is already implemented! We’ll modify the above `dump()` method to use `QROutputAbstract::collectModules()` instead.

The module collector accepts a `Closure` as its only parameter, which is called with 4 parameters:

- `$x` : current column

- `$y` : current row

- `$M_TYPE` : field value

- `$M_TYPE_LAYER`: (possibly modified) field value that acts as layer id

We only need the first 3 parameters, so our closure would look as follows:

    $closure = fn(int $x, int $y, int $M_TYPE):string => $this->module($x, $y, $M_TYPE);

As of PHP 8.1+ we can narrow this down with the first class callable syntax:

    $closure = $this->module(...);

This is our final output method then:

    	public function dump(string $file = null):string{
    		$collections = $this->collectModules($this->module(...));

    		// build the final output
    		$out = [];

    		foreach($collections as $M_TYPE => $collection){
    			$name  = ($this->getModuleValue($M_TYPE) ?: $this::LAYERNAMES[$M_TYPE]);
    			// the section header
    			$out[] = sprintf("%s (%012b)\n", $name, $M_TYPE);
    			// the list of modules
    			$out[] = sprintf("%s\n", implode("\n", $collection));
    		}

    		return implode("\n", $out);
    	}

## Run the custom output

To run the output we just need to set the `QROptions::$outputInterface` to our custom class:

    $options = new QROptions;
    $options->outputType       = QROutputInterface::CUSTOM;
    $options->outputInterface  = MyCustomOutput::class;
    $options->connectPaths     = true;
    $options->drawLightModules = true;

    // our custom module values
    $options->moduleValues    = [
    	QRMatrix::M_DATA      => 'these-modules-are-light',
    	QRMatrix::M_DATA_DARK => 'here-is-a-dark-module',
    ];

    $qrcode = new QRCode($options);
    $qrcode->addByteSegment('test');

    var_dump($qrcode->render());

The output looks similar to the following:

    these-modules-are-light (000000000010)

    x: 0, y: 0
    x: 1, y: 0
    x: 2, y: 0
    ...

    here-is-a-dark-module (100000000010)

    x: 4, y: 4
    x: 5, y: 4
    x: 6, y: 4
    ...

Profit!

## Summary

We’ve learned how to create a custom output class for a string based format similar to several of the built-in formats such as SVG or EPS.

The full code of our custom class below:

    class MyCustomOutput extends QROutputAbstract{

    	protected function prepareModuleValue($value):string{
    		return strtolower($value);
    	}

    	protected function getDefaultModuleValue(bool $isDark):string{
    		return '';
    	}

    	public static function moduleValueIsValid($value):bool{
    		return is_string($value) && preg_match('/^[a-z-]+$/i', $value) === 1;
    	}

    	public function dump(string $file = null):string{
    		$collections = $this->collectModules($this->module(...));

    		// build the final output
    		$out = [];

    		foreach($collections as $M_TYPE => $collection){
    			$name  = ($this->getModuleValue($M_TYPE) ?: $this::LAYERNAMES[$M_TYPE]);
    			// the section header
    			$out[] = sprintf("%s (%012b)\n", $name, $M_TYPE);
    			// the list of modules
    			$out[] = sprintf("%s\n", implode("\n", $collection));
    		}

    		return implode("\n", $out);
    	}

    	protected function module(int $x, int $y, int $M_TYPE):string{

    		if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
    			return '';
    		}

    		return sprintf('x: %s, y: %s', $x, $y);
    	}

    }