Search code examples
phpcompressiondecoratorlibsodium

PHP How to use decompression after decryption using Decorator Pattern?


For studying purposes I was trying to create a GoF Decorator implementation using as example a possibility to convert a text to compressed text or encrypted text.

    <?php
    interface DataSource {
        public function gerar($texto): string;
        public function recuperar($texto) : string;
    }
    class TextoBase implements DataSource {
        public function gerar($texto): string {
            return $texto;
        }
        public function recuperar($texto) : string {
            return $texto;
        }
    }
    abstract class Decorator implements DataSource {
        private DataSource $decorado;
        public function __construct(DataSource $decorado) {
            $this->decorado = $decorado;
        }
        public function gerar($texto): string {
            return $this->decorado->gerar($texto);
        }
        public function recuperar($texto) : string {
            return $this->decorado->recuperar($texto);
        }
    }
   class CriptoDecorator extends Decorator {

    const KEY = 'vDIa5JdknBqfrKOu8d7UpddnBMCH1vza';
    const NONCE = 'Ra5LeH7ntW2rvkz3dmqI5Stx';

    public function gerar($texto): string {
        return $this->encrypt(parent::gerar($texto));
    }

    public function recuperar($texto): string {
        return $this->decrypt(parent::recuperar($texto));
    }

    public function encrypt($data) {
        return sodium_crypto_secretbox($data, self::NONCE, self::KEY);
    }

    private function decrypt(string $data): string {
        return sodium_crypto_secretbox_open($data, self::NONCE, self::KEY);
    }

}
    class CompressaoDecorator extends Decorator {
        const NIVEL_COMPRESSAO = 6;
        public function gerar($texto): string {
            return $this->comprimir(parent::gerar($texto));
        }
        public function recuperar($texto): string {
            return $this->descomprimir(parent::recuperar($texto));
        }
        private function comprimir(string $stringData): string {
            return gzcompress($stringData, self::NIVEL_COMPRESSAO);
        }
        private function descomprimir(string $stringData): string {
            return gzuncompress($stringData);
        }
    }
    $texto = "olá mundo !";
    $decorado = new CompressaoDecorator(new CriptoDecorator(new TextoBase()));
    $texto_decorado = $decorado->gerar($texto);
    echo PHP_EOL;
    echo $decorado->recuperar($texto_decorado);

For some reason I'm got warning:

Warning: gzuncompress(): data error in C:\wamp64\www\curso\designer_patterns\estrutural\decorator\real_life.php on line 93

So, Is there a way to fix this and allow Both Decorators to be stacked and be used to gerar(generate) and recuperar(retrieve) ?

Thanks in advance


Solution

  • You need to unwind in the same order that you setup. If you compress then encrypt, you need to decrypt and then uncompress.

    The fast fix for this specific code is to change your recuperar method in CompressaoDecorator

    class CompressaoDecorator extends Decorator
    {
        public function recuperar($texto): string
        {
            return parent::recuperar($this->descomprimir($texto));
        }
    }
    

    If you want to solve this in the abstract, I would handle this with a factory instead that can guarantee order. To do that, I don't think the individual objects themselves should concern themselves with parent, the factory should do the job of chaining things.

    Edit

    Actually, as I think about this more, you don't need the factory, you just need to swap your order for all of your recuperar methods, so this one would change, too:

    class CriptoDecorator extends Decorator
    {
        public function recuperar($texto): string
        {
            return parent::recuperar($this->decrypt($texto));
        }
    }
    

    This should allow you to call either encrypt or compress first, and as long as you use the same chain the reverse should work, too.