|
| 1 | +# `QROutputAbstract` |
| 2 | + |
| 3 | +The abstract class `QROutputAbstract` contains several commonly used methods and properties and can be used as a basis for a custom output class. |
| 4 | + |
| 5 | + |
| 6 | +## Properties |
| 7 | + |
| 8 | +### `$options` and `$matrix` |
| 9 | + |
| 10 | +The `QROptions` and `QRMatrix` instances that were passed to the constructor of the output class. |
| 11 | +Both objects can be modified during runtime, for example to override settings or add matrix modifications. |
| 12 | + |
| 13 | + |
| 14 | +### `$moduleCount`, `$scale` and `$length` |
| 15 | + |
| 16 | +These are convenience variables mostly to avoid multiple method calls to `QRMatrix::getSize()` and `QROptions::__get('scale')` inside loops, |
| 17 | +the `$length` is calculated from the aforementioned values (`$moduleCount * $scale`). |
| 18 | +The method `setMatrixDimensions()` can be called to update these 3 values after the matrix has been modified, e.g. by adding a quiet zone during output. |
| 19 | + |
| 20 | + |
| 21 | +### `$moduleValues` |
| 22 | + |
| 23 | +The finalized map of `$M_TYPE` to value for the current output. This map is generated during invocation of the output class via `setModuleValues()`. |
| 24 | + |
| 25 | + |
| 26 | +### Copies of `QROptions` values |
| 27 | + |
| 28 | +Some values from the `QROptions` instance are copied to properties to avoid calling the magic getters in long loops for a significant performance increase, e.g. in the module collector. |
| 29 | +Currently, the following values are copied via `copyVars()` during invocation: |
| 30 | +`$connectPaths`, `$excludeFromConnect`, `$eol`, `$drawLightModules`, `$drawCircularModules`, `$keepAsSquare`, `$circleRadius`. |
| 31 | + |
| 32 | + |
| 33 | +## Methods |
| 34 | + |
| 35 | +### `setModuleValues()` |
| 36 | + |
| 37 | +This method calls the abstract/interface methods `moduleValueIsValid()`, `prepareModuleValue()` and `getDefaultModuleValue()` to prepare the module values map. |
| 38 | + |
| 39 | + |
| 40 | +### `moduleValueIsValid()` |
| 41 | + |
| 42 | +This method is declared in the `QROutputInterface` and needs to be implemented by the output class; it is `static` so that it can be called before invocation. |
| 43 | +The purpose is to determine whether the given `mixed` input is a valid module value for the current output class and returns `bool`. |
| 44 | +It's also useful to check values from `QROptions` such as `$bgColor` or `$transparencyColor`. |
| 45 | + |
| 46 | +Below is a pseudo implementation, check the code of the several output classes for actual implementations |
| 47 | +(e.g. [`QRImagick::moduleValueIsValid()`](https://github.com/chillerlan/php-qrcode/blob/4bd4b59fdec72397f5b1f70da9cadcb76764191b/src/Output/QRImagick.php#L68-L96)) |
| 48 | + |
| 49 | +```php |
| 50 | +class MyOutput extends QROutputAbstract{ |
| 51 | + |
| 52 | + public static function moduleValueIsValid(mixed $value):bool{ |
| 53 | + |
| 54 | + // check the type of the input value first |
| 55 | + if(!is_expected_type($value)){ |
| 56 | + return false; |
| 57 | + } |
| 58 | + |
| 59 | + // do some more checks to determine the value |
| 60 | + if(!is_somehow_valid($value)){ |
| 61 | + return false; |
| 62 | + } |
| 63 | + |
| 64 | + // looks like we got a match |
| 65 | + return true; |
| 66 | + } |
| 67 | + |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | + |
| 72 | +### `prepareModuleValue()` |
| 73 | + |
| 74 | +This method prepares the final replacement value from the given input. |
| 75 | +It might still be necessary to validate the given value despite it being checked earlier by `moduleValueIsValid()` - |
| 76 | +if nothing helps, this is a good place to throw an exception. |
| 77 | +Below a pseudo implementation example (see [`QRGdImage::prepareModuleValue()`](https://github.com/chillerlan/php-qrcode/blob/4bd4b59fdec72397f5b1f70da9cadcb76764191b/src/Output/QRGdImage.php#L138-L158)): |
| 78 | + |
| 79 | +```php |
| 80 | +class MyOutput extends QROutputAbstract{ |
| 81 | + |
| 82 | + protected function prepareModuleValue(mixed $value):mixed{ |
| 83 | + |
| 84 | + // extended validation to make sure the values are valid for output |
| 85 | + // e.g. examine array values, clamp etc. |
| 86 | + if(!is_valid($value)){ |
| 87 | + throw new QRCodeOutputException('invalid module value'); |
| 88 | + } |
| 89 | + |
| 90 | + return $this->modifyValue($value); |
| 91 | + } |
| 92 | + |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | + |
| 97 | +### `getDefaultModuleValue()` |
| 98 | + |
| 99 | +Finally, setting a default value is required, in case a value for an `$M_TYPE` is not set or it's invalid. |
| 100 | + |
| 101 | +```php |
| 102 | +class MyOutput extends QROutputAbstract{ |
| 103 | + |
| 104 | + protected function getDefaultModuleValue(bool $isDark):mixed{ |
| 105 | + $defaultValue = ($isDark === true) |
| 106 | + ? 'default value for dark' |
| 107 | + : 'default value for light'; |
| 108 | + |
| 109 | + return $this->prepareModuleValue($defaultValue); |
| 110 | + } |
| 111 | + |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | + |
| 116 | +### `getModuleValue()` and `getModuleValueAt()` |
| 117 | + |
| 118 | +Both methods return a module value, the main difference is that `getModuleValueAt()` is a convenience method |
| 119 | +that makes an extra call to retrieve the `$M_TYPE` from the given matrix coordinate to return the value via `getModuleValue()`. |
| 120 | + |
| 121 | +A `foreach` loop over the matrix gives you the key (coordinate) *and* value of an array element: |
| 122 | + |
| 123 | +```php |
| 124 | +class MyOutput extends QROutputAbstract{ |
| 125 | + |
| 126 | + public function dump(string $file = null):string{ |
| 127 | + $lines = []; |
| 128 | + |
| 129 | + foreach($this->matrix->getMatrix() as $y => $row){ |
| 130 | + $lines[$y] = ''; |
| 131 | + |
| 132 | + foreach($row as $x => $M_TYPE){ |
| 133 | + $lines[$y] .= $this->getModuleValue($M_TYPE); |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + return implode($this->options->eol, $lines); |
| 138 | + } |
| 139 | + |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +However, sometimes you might happen to use a `for` loop instead. The `for` loop leaves you only with the matrix coordinates, so you need to call `getModuleValueAt()`: |
| 144 | + |
| 145 | +```php |
| 146 | +class MyOutput extends QROutputAbstract{ |
| 147 | + |
| 148 | + public function dump(string $file = null):string{ |
| 149 | + $lines = []; |
| 150 | + |
| 151 | + for($y = 0; $y < $this->moduleCount; $y++){ |
| 152 | + $lines[$y] = ''; |
| 153 | + |
| 154 | + for($x = 0; $x < $this->moduleCount; $x++){ |
| 155 | + $lines[$y] .= $this->getModuleValueAt($x, $y); |
| 156 | + } |
| 157 | + |
| 158 | + } |
| 159 | + |
| 160 | + return implode($this->options->eol, $lines); |
| 161 | + } |
| 162 | + |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | + |
| 167 | +### `setMatrixDimensions()` |
| 168 | + |
| 169 | +As mentioned before, this method is supposed to set the values for the properties `$moduleCount`, `$scale` and `$length`. |
| 170 | +It is called in the constructor during invocation, but it might be necessary to call it again if the size of the matrix was changed in the output class |
| 171 | +(see [the round quiet zone example](https://github.com/chillerlan/php-qrcode/blob/99b1f9cf454ab1316cb643950a71caed3a6c0f5a/examples/svgRoundQuietzone.php#L38-L44) for a use case). |
| 172 | + |
| 173 | + |
| 174 | +### `getOutputDimensions()` |
| 175 | + |
| 176 | +This method provides a simple way for consistent width/height values for the output (if applicable) which then can be changed by simply overriding this method. |
| 177 | +It returns a 2-element array that contains the values in a format that can be used by the output class, which is `QROutputAbstract::$length` (`$moduleCount * $scale`): |
| 178 | + |
| 179 | +```php |
| 180 | +[$width, $height] = $this->getOutputDimensions(); |
| 181 | +``` |
| 182 | + |
| 183 | +The output width and height can be changed in all places by simply overriding the method: |
| 184 | + |
| 185 | +```php |
| 186 | +class MyOutput extends QROutputAbstract{ |
| 187 | + |
| 188 | + protected function getOutputDimensions():array{ |
| 189 | + // adjust the height in order to add something under the QR Code |
| 190 | + return [$this->length, ($this->length + 69)]; |
| 191 | + } |
| 192 | + |
| 193 | +} |
| 194 | +``` |
| 195 | + |
| 196 | + |
| 197 | +### `collectModules()` |
| 198 | + |
| 199 | +The module collector is particularly useful for plain text based file formats, for example the various markup languages like SVG and HTML or other structured file formats such as EPS. |
| 200 | +This method takes a `Closure` as a parameter, which is called with 4 parameters: the module coordinates `$x` and `$y`, the `$M_TYPE` and `$M_TYPE_LAYER`. |
| 201 | +The `$M_TYPE_LAYER` is a copy of the `$M_TYPE` that represents the array key of the returned array and that may have been reassigned in the collector to another path layer, e.g. through `QROptions::$connectPaths`. |
| 202 | + |
| 203 | +```php |
| 204 | +class MyOutput extends QROutputAbstract{ |
| 205 | + |
| 206 | + public function dump(string $file = null):string{ |
| 207 | + |
| 208 | + // collect the modules for the path elements |
| 209 | + $paths = $this->collectModules(fn(int $x, int $y, int $M_TYPE):string => sprintf('%d %d %012b', $x, $y, $M_TYPE)); |
| 210 | + |
| 211 | + // loop over the paths |
| 212 | + foreach($paths as $M_TYPE_LAYER => &$path){ |
| 213 | + |
| 214 | + if(empty($path)){ |
| 215 | + continue; |
| 216 | + } |
| 217 | + |
| 218 | + $path = implode($this->options->eol, $path); |
| 219 | + } |
| 220 | + |
| 221 | + return implode($this->options->eol, $paths); |
| 222 | + } |
| 223 | + |
| 224 | +} |
| 225 | +``` |
| 226 | + |
| 227 | +Sometimes it can be necessary to override `collectModules()` in order to apply special effects such as random colors - you can find some implementations in [the SVG examples](https://github.com/chillerlan/php-qrcode/tree/main/examples). |
| 228 | + |
| 229 | + |
| 230 | +### `saveToFile()` and `toBase64DataURI()` |
| 231 | + |
| 232 | +The void method `saveToFile()` takes a data blob and the `$file` given in `QROutputInterface::dump()` and save to the path if it is not `null` - the file path itself is not checked except for writability. |
| 233 | + |
| 234 | +The final output can be transformed to a [base64 data URI](https://en.wikipedia.org/wiki/Data_URI_scheme) with `toBase64DataURI()`, where the data blob and a valid mime type as parameters - the mime type is not checked. |
| 235 | + |
| 236 | + |
| 237 | + |
| 238 | +```php |
| 239 | +class MyOutput extends QROutputAbstract{ |
| 240 | + |
| 241 | + public function dump(string $file = null):string{ |
| 242 | + $output = 'qrcode data string'; |
| 243 | + |
| 244 | + // save the plain data to file |
| 245 | + $this->saveToFile($output, $file); |
| 246 | + |
| 247 | + // base64 encoding may be called optionally |
| 248 | + if($this->options->outputBase64){ |
| 249 | + $output = $this->toBase64DataURI($output, 'text/plain'); |
| 250 | + } |
| 251 | + |
| 252 | + return $output; |
| 253 | + } |
| 254 | + |
| 255 | +} |
| 256 | +``` |
0 commit comments