Skip to content

Commit

Permalink
Fix #80: Add fix-dependencies command (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
rugabarbo committed Jun 28, 2020
1 parent c087827 commit 61a34ed
Show file tree
Hide file tree
Showing 12 changed files with 719 additions and 1 deletion.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"ext-json": "*",
"ext-openssl": "*",
"cpliakas/git-wrapper": "^3.0",
"nikic/php-parser": "^4.5",
"squizlabs/php_codesniffer": "^3.3",
"symfony/console": "^5.0",
"symfony/filesystem": "^5.0",
Expand Down
93 changes: 93 additions & 0 deletions src/Command/FixDependenciesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace Yiisoft\YiiDevTool\Command;

use Yiisoft\YiiDevTool\Component\CodeUsage\ComposerPackageUsageAnalyzer;
use Yiisoft\YiiDevTool\Component\Composer\ComposerConfig;
use Yiisoft\YiiDevTool\Component\Composer\ComposerConfigDependenciesModifier;
use Yiisoft\YiiDevTool\Component\Composer\ComposerPackage;
use Yiisoft\YiiDevTool\Component\CodeUsage\CodeUsageEnvironment;
use Yiisoft\YiiDevTool\Component\CodeUsage\NamespaceUsageFinder;
use Yiisoft\YiiDevTool\Component\Console\PackageCommand;
use Yiisoft\YiiDevTool\Component\Package\Package;

class FixDependenciesCommand extends PackageCommand
{
protected function configure()
{
$this
->setName('fix-dependencies')
->setDescription('Fix <fg=yellow;options=bold>require</> and <fg=yellow;options=bold>require-dev</> sections in <fg=blue;options=bold>composer.json</> according to the actual use of classes');

$this->addPackageArgument();
}

protected function getMessageWhenNothingHasBeenOutput(): ?string
{
return '<success>✔ Nothing to fix</success>';
}

protected function processPackage(Package $package): void
{
$io = $this->getIO();
$io->preparePackageHeader($package, "Fixing package {package} dependencies");

$package = new ComposerPackage($package->getName(), $package->getPath());

if (!$package->installed()) {
$io->warning([
"Package <package>{$package->getName()}</package> is not installed.",
"Dependencies fixing skipped.",
]);

return;
}

$dependencyPackages = $package->getDependencyPackages('yiisoft');
foreach ($dependencyPackages as $dependencyPackage) {
if (!$dependencyPackage->installed()) {
$io->warning([
"Dependency <package>{$dependencyPackage->getName()}</package> is not installed.",
"Dependencies fixing skipped.",
]);

return;
}
}

$namespaceUsages =
(new NamespaceUsageFinder())
->addTargetPaths(CodeUsageEnvironment::PRODUCTION, [
'config/common.php',
'config/web.php',
'src',
], $package->getPath())
->addTargetPaths(CodeUsageEnvironment::DEV, [
'config/tests.php',
'tests',
], $package->getPath())
->getUsages();

$analyzer = new ComposerPackageUsageAnalyzer($dependencyPackages, $namespaceUsages);
$analyzer->analyze();

$composerConfig = $package->getComposerConfig();

(new ComposerConfigDependenciesModifier($composerConfig))
->removeDependencies($analyzer->getUnusedPackages())
->ensureDependenciesUsedOnlyInSection(
$analyzer->getPackagesUsedInSpecifiedEnvironment(CodeUsageEnvironment::PRODUCTION),
ComposerConfig::SECTION_REQUIRE,
)
->ensureDependenciesUsedOnlyInSection(
$analyzer->getPackagesUsedOnlyInSpecifiedEnvironment(CodeUsageEnvironment::DEV),
ComposerConfig::SECTION_REQUIRE_DEV,
);

$composerConfig->writeToFile($package->getComposerConfigPath());

$io->done();
}
}
86 changes: 86 additions & 0 deletions src/Component/CodeUsage/CodeUsage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace Yiisoft\YiiDevTool\Component\CodeUsage;

use InvalidArgumentException;

class CodeUsage
{
private string $identifier;

/**
* @var string[]
*/
private array $environments = [];

/**
* @param string $identifier Unique identifier of code usage: namespace, package name, etc.
* @param string[] $environments Environment in which the code is used.
*/
public function __construct(string $identifier, array $environments)
{
foreach ($environments as $environment) {
if (!is_string($environment)) {
throw new InvalidArgumentException('Each environment must be a string.');
}
}

$this->identifier = $identifier;
$this->environments = $environments;
}

public function getIdentifier(): string
{
return $this->identifier;
}

/**
* @return string[]
*/
public function getEnvironments(): array
{
return $this->environments;
}

public function registerUsageInEnvironment(string $environment): void
{
if (!in_array($environment, $this->environments, true)) {
$this->environments[] = $environment;
}
}

/**
* @param string[] $environments
*/
public function registerUsageInEnvironments(array $environments): void
{
foreach ($environments as $environment) {
if (!is_string($environment)) {
throw new InvalidArgumentException('Each environment must be a string.');
}

$this->registerUsageInEnvironment($environment);
}
}

public function usedInEnvironment(string $environment): bool
{
return in_array($environment, $this->environments, true);
}

public function usedOnlyInSpecifiedEnvironment(string $environment): bool
{
if (count($this->environments) !== 1) {
return false;
}

return in_array($environment, $this->environments, true);
}

public function used(): bool
{
return count($this->environments) > 0;
}
}
11 changes: 11 additions & 0 deletions src/Component/CodeUsage/CodeUsageEnvironment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Yiisoft\YiiDevTool\Component\CodeUsage;

class CodeUsageEnvironment
{
public const PRODUCTION = 'production';
public const DEV = 'dev';
}
118 changes: 118 additions & 0 deletions src/Component/CodeUsage/ComposerPackageUsageAnalyzer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

declare(strict_types=1);

namespace Yiisoft\YiiDevTool\Component\CodeUsage;

use InvalidArgumentException;
use Yiisoft\YiiDevTool\Component\Composer\ComposerPackage;

class ComposerPackageUsageAnalyzer
{
/**
* @var ComposerPackage[]
*/
private array $packages = [];

/**
* @var CodeUsage[]
*/
private array $namespaceUsages = [];

/**
* @var CodeUsage[]
*/
private array $packageUsages = [];

/**
* @param ComposerPackage[] $packages
* @param CodeUsage[] $namespaceUsages
*/
public function __construct(array $packages, array $namespaceUsages)
{
foreach ($packages as $package) {
if (!$package instanceof ComposerPackage) {
throw new InvalidArgumentException('$packages must be an array of ComposerPackage objects.');
}
}

foreach ($namespaceUsages as $namespaceUsage) {
if (!$namespaceUsage instanceof CodeUsage) {
throw new InvalidArgumentException('$namespaceUsages must be an array of CodeUsage objects.');
}
}

foreach ($packages as $package) {
$this->packages[$package->getName()] = $package;
}

foreach ($namespaceUsages as $namespaceUsage) {
$this->namespaceUsages[$namespaceUsage->getIdentifier()] = $namespaceUsage;
}
}

public function analyze(): void
{
foreach ($this->packages as $package) {
foreach ($package->getNamespaces() as $packageNamespace) {
foreach ($this->namespaceUsages as $namespaceUsage) {
if (strpos($namespaceUsage->getIdentifier(), "\\$packageNamespace") === 0) {
$this->registerPackageUsage($package->getName(), $namespaceUsage->getEnvironments());
}
}
}
}
}

public function getPackagesUsedInSpecifiedEnvironment(string $environment): array
{
$result = [];

foreach ($this->packageUsages as $packageUsage) {
if ($packageUsage->usedInEnvironment($environment)) {
$result[] = $this->packages[$packageUsage->getIdentifier()];
}
}

return $result;
}

public function getPackagesUsedOnlyInSpecifiedEnvironment(string $environment): array
{
$result = [];

foreach ($this->packageUsages as $packageUsage) {
if ($packageUsage->usedOnlyInSpecifiedEnvironment($environment)) {
$result[] = $this->packages[$packageUsage->getIdentifier()];
}
}

return $result;
}

public function getUnusedPackages(): array
{
$result = [];

foreach ($this->packageUsages as $packageUsage) {
if (!$packageUsage->used()) {
$result[] = $this->packages[$packageUsage->getIdentifier()];
}
}

return $result;
}

/**
* @param string $packageName
* @param string[] $environments
*/
private function registerPackageUsage(string $packageName, array $environments): void
{
if (!array_key_exists($packageName, $this->packageUsages)) {
$this->packageUsages[$packageName] = new CodeUsage($packageName, $environments);
} else {
$this->packageUsages[$packageName]->registerUsageInEnvironments($environments);
}
}
}

0 comments on commit 61a34ed

Please sign in to comment.