Potherca
5/3/2017 - 7:46 AM

Validate that a project does not require certain composer packages as dependencies. (Rough first draft).

Validate that a project does not require certain composer packages as dependencies. (Rough first draft).

<?php

/**
 * This file contains a first rough draft of logic to validate that a project
 * does not require certain composer packages as dependencies.
 *
 * It validates this by checking the project's `composer.lock` file.
 *
 * Usage:
 *          php composer-checker.php
 *
 * @TODO: Show which package causes violation
 */
namespace DealerDirect\QualityAssistance;

// @FIXME: Read $path from $argv
$path = '/path/to/project/composer.lock';

// @FIXME: Read default $blacklist from config file
// @FIXME: Read additional $blacklist from $argv
$blacklist = [
    'barryvdh/laravel-debugbar',
    'maximebf/debugbar',
    'symfony/debug',
];


class ValidateComposerDependencies
{
    ////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\
    /** @var array */
    private $blacklist;
    /** @var  string */
    private $contents;

    //////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    /**
     * @param string $contents
     * @param array $blacklist
     */
    final public function __construct($contents, array $blacklist)
    {
        $this->contents = $contents;
        $this->blacklist = $blacklist;
    }

    /**
     * @return bool
     */
    final public function validate()
    {
        $blacklist = $this->blacklist;
        $contents = $this->contents;

        $requirements = $this->retrievePackagesFromContents($contents);

        return $this->validatePackagesAgainstBlacklisted($requirements, $blacklist);
    }

    ////////////////////////////// UTILITY METHODS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    /**
     * @param string $contents
     *
     * @return array
     */
    private function retrievePackagesFromContents($contents)
    {
        $packages = $this->retrieveRequirementsFromContents($contents);

        return $this->retrievePackagesFromRequirements($packages);
    }

    /**
     * @param string $contents
     *
     * @return array
     */
    private function retrieveRequirementsFromContents($contents)
    {
        // @FIXME: Handle invalid JSON
        $json = json_decode($contents, true);

        // @FIXME: Handle missing 'packages' key
        return $json['packages'];
    }

    /**
     * @param array $requirements
     *
     * @return array
     */
    private function retrievePackagesFromRequirements(array $requirements)
    {
        $packages = [];

        array_walk($requirements, function (array $package) use (&$packages) {
            if (array_key_exists('require', $package)) {
                $name = $package['name'];
                if (is_array($package['require'])) {
                    array_walk($package['require'], function ($version, $subPackage) use (&$packages, $name) {
                        if (array_key_exists($subPackage, $packages) === false) {
                            $packages[$subPackage] = [];
                        }
                        $packages[$subPackage][] = $name;
                    });
                }
            }
        });

        return $packages;
    }

    /**
     * @param $requirements
     * @param $blacklist
     *
     * @return bool
     */
    private function validatePackagesAgainstBlacklisted(array $requirements, array $blacklist)
    {
        $diff = array_diff(
            $blacklist,
            array_keys($requirements)
        );

        return count($diff) === count($blacklist);
    }

}

/**
 * @param string $file
 * @param array $blacklist
 *
 * @return int
 */
function run($file, array $blacklist)
{
    if (is_readable($file) === false) {
        throw new \UnexpectedValueException('Could not find a composer lock file.');
    }

    $contents = file_get_contents($file);

    $validator = new ValidateComposerDependencies($contents, $blacklist);

    $isValid = $validator->validate();

    return (int)!$isValid;
}

$exitCode = run($path, $blacklist);

exit($exitCode);

/*EOF*/