PHP Classes
elePHPant
Icontem

File: src/Pharaoh/PharDiff.php

Recommend this page to a friend!
  Classes of Scott Arciszewski  >  pharaoh  >  src/Pharaoh/PharDiff.php  >  Download  
File: src/Pharaoh/PharDiff.php
Role: Class source
Content type: text/plain
Description: Class source
Class: pharaoh
Compare two PHAR files to retrieve the differences
Author: By
Last change:
Date: 5 months ago
Size: 7,904 bytes
 

 

Contents

Class file image Download
<?php
declare(strict_types=1);
namespace
ParagonIE\Pharaoh;
use
ParagonIE\ConstantTime\Hex;

/**
 * Class PharDiff
 * @package ParagonIE\Pharaoh
 */
class PharDiff
{
   
/**
     * @var array<string, string>
     */
   
protected $c = [
       
'' => "\033[0;39m",
       
'red' => "\033[0;31m",
       
'green' => "\033[0;32m",
       
'blue' => "\033[1;34m",
       
'cyan' => "\033[1;36m",
       
'silver' => "\033[0;37m",
       
'yellow' => "\033[0;93m"
   
];

   
/** @var array<int, Pharaoh> */
   
private $phars = [];

   
/** @var bool $verbose */
   
private $verbose = false;
   
   
/**
     * Constructor uses dependency injection.
     *
     * @param \ParagonIE\Pharaoh\Pharaoh $pharA
     * @param \ParagonIE\Pharaoh\Pharaoh $pharB
     */
   
public function __construct(Pharaoh $pharA, Pharaoh $pharB)
    {
       
$this->phars = [$pharA, $pharB];
    }
   
   
/**
     * Prints a git-formatted diff of the two phars.
     *
     * @psalm-suppress ForbiddenCode
     * @return int
     */
   
public function printGitDiff(): int
   
{
       
// Lazy way; requires git. Will replace with custom implementaiton later.
       
       
$argA = \escapeshellarg($this->phars[0]->tmp);
       
$argB = \escapeshellarg($this->phars[1]->tmp);
       
/** @var string $diff */
       
$diff = `git diff --no-index $argA $argB`;
        echo
$diff;
        if (empty(
$diff) && $this->verbose) {
            echo
'No differences encountered.', PHP_EOL;
            return
0;
        }
        return
1;
    }
   
   
/**
     * Prints a GNU diff of the two phars.
     *
     * @psalm-suppress ForbiddenCode
     * @return int
     */
   
public function printGnuDiff(): int
   
{
       
// Lazy way. Will replace with custom implementaiton later.
       
$argA = \escapeshellarg($this->phars[0]->tmp);
       
$argB = \escapeshellarg($this->phars[1]->tmp);
       
/** @var string $diff */
       
$diff = `diff $argA $argB`;
        echo
$diff;
        if (empty(
$diff) && $this->verbose) {
            echo
'No differences encountered.', PHP_EOL;
            return
0;
        }
        return
1;
    }
   
   
/**
     * Get hashes of all of the files in the two arrays.
     *
     * @param string $algo
     * @param string $dirA
     * @param string $dirB
     * @return array<int, array<mixed, string>>
     * @throws \SodiumException
     */
   
public function hashChildren(string $algo,string $dirA, string $dirB)
    {
       
/**
         * @var string $a
         * @var string $b
         */
       
$a = $b = '';
       
$filesA = $this->listAllFiles($dirA);
       
$filesB = $this->listAllFiles($dirB);
       
$numFiles = \max(\count($filesA), \count($filesB));
       
       
// Array of two empty arrays
       
$hashes = [[], []];
        for (
$i = 0; $i < $numFiles; ++$i) {
           
$thisFileA = (string) $filesA[$i];
           
$thisFileB = (string) $filesB[$i];
            if (isset(
$filesA[$i])) {
               
$a = \preg_replace('#^'.\preg_quote($dirA, '#').'#', '', $thisFileA);
                if (isset(
$filesB[$i])) {
                   
$b = \preg_replace('#^'.\preg_quote($dirB, '#').'#', '', $thisFileB);
                } else {
                   
$b = $a;
                }
            } elseif (isset(
$filesB[$i])) {
               
$b = \preg_replace('#^'.\preg_quote($dirB, '#').'#', '', $thisFileB);
               
$a = $b;
            }
           
            if (isset(
$filesA[$i])) {
               
// A exists
               
if (\strtolower($algo) === 'blake2b') {
                   
$hashes[0][$a] = Hex::encode(\ParagonIE_Sodium_File::generichash($thisFileA));
                } else {
                   
$hashes[0][$a] = \hash_file($algo, $thisFileA);
                }
            } elseif (isset(
$filesB[$i])) {
               
// A doesn't exist, B does
               
$hashes[0][$a] = '';
            }
           
            if (isset(
$filesB[$i])) {
               
// B exists
               
if (\strtolower($algo) === 'blake2b') {
                   
$hashes[1][$b] = Hex::encode(\ParagonIE_Sodium_File::generichash($thisFileB));
                } else {
                   
$hashes[1][$b] = \hash_file($algo, $thisFileB);
                }
            } elseif (isset(
$filesA[$i])) {
               
// B doesn't exist, A does
               
$hashes[1][$b] = '';
            }
        }
        return
$hashes;
    }
   
   
   
/**
     * List all the files in a directory (and subdirectories)
     *
     * @param string $folder - start searching here
     * @param string $extension - extensions to match
     * @return array
     */
   
private function listAllFiles($folder, $extension = '*')
    {
       
/**
         * @var array<mixed, string> $fileList
         * @var string $i
         * @var string $file
         * @var \RecursiveDirectoryIterator $dir
         * @var \RecursiveIteratorIterator $ite
         */
       
$dir = new \RecursiveDirectoryIterator($folder);
       
$ite = new \RecursiveIteratorIterator($dir);
        if (
$extension === '*') {
           
$pattern = '/.*/';
        } else {
           
$pattern = '/.*\.' . $extension . '$/';
        }
       
$files = new \RegexIterator($ite, $pattern, \RegexIterator::GET_MATCH);

       
/** @var array<string, string> $fileList */
       
$fileList = [];

       
/**
         * @var string $fileSub
         */
       
foreach($files as $fileSub) {
           
$fileList = \array_merge($fileList, $fileSub);
        }

       
/**
         * @var string $i
         * @var string $file
         */
       
foreach ($fileList as $i => $file) {
            if (\
preg_match('#/\.{1,2}$#', (string) $file)) {
                unset(
$fileList[$i]);
            }
        }
        return \
array_values($fileList);
    }

   
/**
     * Prints out all of the differences of checksums of the files contained
     * in both PHP archives.
     *
     * @param string $algo
     * @return int
     * @throws \SodiumException
     */
   
public function listChecksums(string $algo = 'sha384'): int
   
{
        list(
$pharA, $pharB) = $this->hashChildren(
           
$algo,
           
$this->phars[0]->tmp,
           
$this->phars[1]->tmp
       
);

       
$diffs = 0;
       
/** @var string $i */
       
foreach (\array_keys($pharA) as $i) {
            if (isset(
$pharA[$i]) && isset($pharB[$i])) {
               
// We are NOT concerned about local timing attacks.
               
if ($pharA[$i] !== $pharB[$i]) {
                    ++
$diffs;
                    echo
"\t", (string) $i,
                   
"\n\t\t", $this->c['red'], $pharA[$i], $this->c[''],
                   
"\t", $this->c['green'], $pharB[$i], $this->c[''],
                   
"\n";
                } elseif (!empty(
$pharA[$i]) && empty($pharB[$i])) {
                    ++
$diffs;
                    echo
"\t", (string) $i,
                   
"\n\t\t", $this->c['red'], $pharA[$i], $this->c[''],
                   
"\t", \str_repeat('-', \strlen($pharA[$i])),
                   
"\n";
                } elseif (!empty(
$pharB[$i]) && empty($pharA[$i])) {
                    ++
$diffs;
                    echo
"\t", (string) $i,
                   
"\n\t\t", \str_repeat('-', \strlen($pharB[$i])),
                   
"\t", $this->c['green'], $pharB[$i], $this->c[''],
                   
"\n";
                }
            }
        }
        if (
$diffs === 0) {
            if (
$this->verbose) {
                echo
'No differences encountered.', PHP_EOL;
            }
            return
0;
        }
        return
1;
    }

   
/**
     * Verbose mode says something when there are no differences.
     * By default, you can just check the return value.
     *
     * @param bool $value
     * @return void
     */
   
public function setVerbose(bool $value)
    {
       
$this->verbose = $value;
    }
}