Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composer Collector #1174

Merged
merged 4 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
use Qossmic\Deptrac\Core\Layer\Collector\CollectorProvider;
use Qossmic\Deptrac\Core\Layer\Collector\CollectorResolver;
use Qossmic\Deptrac\Core\Layer\Collector\CollectorResolverInterface;
use Qossmic\Deptrac\Core\Layer\Collector\ComposerCollector;
use Qossmic\Deptrac\Core\Layer\Collector\DirectoryCollector;
use Qossmic\Deptrac\Core\Layer\Collector\ExtendsCollector;
use Qossmic\Deptrac\Core\Layer\Collector\FunctionNameCollector;
Expand Down Expand Up @@ -303,6 +304,9 @@
$services
->set(PhpInternalCollector::class)
->tag('collector', ['type' => CollectorType::TYPE_PHP_INTERNAL->value]);
$services
->set(ComposerCollector::class)
->tag('collector', ['type' => CollectorType::TYPE_COMPOSER->value]);

/*
* Analyser
Expand Down
41 changes: 28 additions & 13 deletions deptrac.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Qossmic\Deptrac\Contract\Analyser\ProcessEvent;
use Qossmic\Deptrac\Contract\Config\Collector\BoolConfig;
use Qossmic\Deptrac\Contract\Config\Collector\ClassNameConfig;
use Qossmic\Deptrac\Contract\Config\Collector\ComposerConfig;
use Qossmic\Deptrac\Contract\Config\Collector\DirectoryConfig;
use Qossmic\Deptrac\Contract\Config\DeptracConfig;
use Qossmic\Deptrac\Contract\Config\EmitterType;
Expand Down Expand Up @@ -32,9 +33,11 @@
),
$ast = Layer::withName('Ast')->collectors(
DirectoryConfig::create('src/Core/Ast/.*'),
ClassNameConfig::create('^PHPStan\\PhpDocParser\\.*')->private(),
ClassNameConfig::create('^PhpParser\\.*')->private(),
ClassNameConfig::create('^phpDocumentor\\Reflection\\.*')->private(),
ComposerConfig::create()
->addPackage('phpstan/phpdoc-parser')
->addPackage('nikic/php-parser')
->addPackage('phpdocumentor/type-resolver')
->private(),
),
$console = Layer::withName('Console')->collectors(
DirectoryConfig::create('src/Supportive/Console/.*')
Expand All @@ -56,7 +59,8 @@
),
$outputFormatter = Layer::withName('OutputFormatter')->collectors(
DirectoryConfig::create('src/Supportive/OutputFormatter/.*'),
ClassNameConfig::create('^phpDocumentor\\GraphViz\\.*')->private(),
ComposerConfig::create('composer.json', 'composer.lock')
->addPackage('phpdocumentor/graphviz')->private(),
),
$file = Layer::withName('File')->collectors(
DirectoryConfig::create('src/Supportive/File/.*')
Expand All @@ -69,24 +73,35 @@
->mustNot(DirectoryConfig::create('src/Supportive/.*/.*'))
->must(DirectoryConfig::create('src/Supportive/.*'))
),
$symfony = Layer::withName('Symfony')->collectors(
ComposerConfig::create()
->addPackage('symfony/config')
->addPackage('symfony/console')
->addPackage('symfony/dependency-injection')
->addPackage('symfony/event-dispatcher')
->addPackage('symfony/filesystem')
->addPackage('symfony/finder')
->addPackage('symfony/yaml'),
),
)
->rulesets(
Ruleset::forLayer($layer)->accesses($ast),
Ruleset::forLayer($console)->accesses($analyser, $outputFormatter, $dependencyInjection, $file, $time),
Ruleset::forLayer($layer)->accesses($ast, $symfony),
Ruleset::forLayer($console)->accesses($analyser, $outputFormatter, $dependencyInjection, $file, $time, $symfony),
Ruleset::forLayer($dependency)->accesses($ast),
Ruleset::forLayer($analyser)->accesses($layer, $dependency, $ast),
Ruleset::forLayer($outputFormatter)->accesses($dependencyInjection),
Ruleset::forLayer($ast)->accesses($file, $inputCollector),
Ruleset::forLayer($inputCollector)->accesses($file),
Ruleset::forLayer($analyser)->accesses($layer, $dependency, $ast, $symfony),
Ruleset::forLayer($outputFormatter)->accesses($dependencyInjection, $symfony),
Ruleset::forLayer($ast)->accesses($file, $inputCollector, $symfony),
Ruleset::forLayer($inputCollector)->accesses($file, $symfony),
Ruleset::forLayer($supportive)->accesses($file),
Ruleset::forLayer($contract),
Ruleset::forLayer($contract)->accesses($symfony),
Ruleset::forLayer($file)->accesses($symfony),
Ruleset::forLayer($dependencyInjection)->accesses($symfony),
)
->formatters(
GraphvizConfig::create()
->pointsToGroup(true)
->groups('Contract', $contract)
->groups('Supportive', $supportive, $file)
->groups('Symfony', $console, $dependencyInjection, $outputFormatter)
->groups('Supportive', $supportive, $file, $symfony, $console, $dependencyInjection, $outputFormatter, $time)
->groups('Core', $analyser, $ast, $dependency, $inputCollector, $layer)
);
};
51 changes: 39 additions & 12 deletions deptrac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ deptrac:
Supportive:
- Supportive
- File
Symfony:
- Console
- Time
- DependencyInjection
- OutputFormatter
- Symfony
Core:
- Analyser
- Ast
Expand All @@ -46,15 +46,14 @@ deptrac:
collectors:
- type: directory
value: src/Core/Ast/.*
- type: className
value: ^PHPStan\\PhpDocParser\\.*
private: true
- type: className
value: ^PhpParser\\.*
private: true
- type: className
value: ^phpDocumentor\\Reflection\\.*
- type: composer
private: true
composerPath: composer.json
composerLockPath: composer.lock
packages:
- phpstan/phpdoc-parser
- nikic/php-parser
- phpdocumentor/type-resolver
- name: Console
collectors:
- type: directory
Expand Down Expand Up @@ -83,9 +82,12 @@ deptrac:
collectors:
- type: directory
value: src/Supportive/OutputFormatter/.*
- type: className
value: ^phpDocumentor\\GraphViz\\.*
- type: composer
private: true
composerPath: composer.json
composerLockPath: composer.lock
packages:
- phpdocumentor/graphviz
- name: File
collectors:
- type: directory
Expand All @@ -103,26 +105,51 @@ deptrac:
must:
- type: directory
value: src/Supportive/.*
- name: Symfony
collectors:
- type: composer
composerPath: composer.json
composerLockPath: composer.lock
packages:
- symfony/config
- symfony/console
- symfony/dependency-injection
- symfony/event-dispatcher
- symfony/filesystem
- symfony/finder
- symfony/yaml

ruleset:
Layer:
- Ast
- Symfony
Console:
- Analyser
- OutputFormatter
- DependencyInjection
- File
- Time
- Symfony
Dependency:
- Ast
Analyser:
- Layer
- Dependency
- Ast
- Symfony
OutputFormatter:
- DependencyInjection
- Symfony
Ast:
- File
- InputCollector
- Symfony
InputCollector:
- File
- Symfony
DependencyInjection:
- Symfony
Contract:
- Symfony
File:
- Symfony
20 changes: 20 additions & 0 deletions docs/collectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,26 @@ deptrac:
Every class name that matches the regular expression becomes a part of the
*controller* layer.

## `composer` Collector

The `composer` collector allows you to define dependencies on composer `require` or `require-dev` packages that follow PSR-0 or PSR-4 autoloading convention. With this collector you can for example enforce:
- That your `require-dev` dependencies are only used in you non-production code (like DB migrations or SA tools)
- That your code does not use any transitive dependencies (dependencies on packages installed only because your `composer.json` required packages depend on them themselves)
- That some packages are only used in particular layers

```yaml
deptrac:
layers:
- name: Symfony
collectors:
- type: composer
composerPath: composer.json
composerLockPath: composer.lock
packages:
- symfony/config
- symfony/console
```

## `directory` Collector

The `directory` collector allows collecting classes by matching their file path
Expand Down
58 changes: 58 additions & 0 deletions src/Contract/Config/Collector/ComposerConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace Qossmic\Deptrac\Contract\Config\Collector;

use Qossmic\Deptrac\Contract\Config\CollectorConfig;
use Qossmic\Deptrac\Contract\Config\CollectorType;

final class ComposerConfig extends CollectorConfig
{
protected CollectorType $collectorType = CollectorType::TYPE_COMPOSER;

/** @var list<string> */
private array $packages = [];

private function __construct(
private readonly string $composerPath,
private readonly string $composerLockPath,
) {
}

/**
* @param list<string> $packages
*/
public static function create(string $composerPath = 'composer.json', string $composerLockPath = 'composer.lock', array $packages = []): self
{
$result = new self($composerPath, $composerLockPath);
foreach ($packages as $package) {
$result->addPackage($package);
}

return $result;
}

public function addPackage(string $package): self
{
$this->packages[] = $package;

return $this;
}

/** @return array{
* composerPath: string,
* composerLockPath: string,
* packages: list<string>,
* private: bool,
* type: string}
*/
public function toArray(): array
{
return [
'composerPath' => $this->composerPath,
'composerLockPath' => $this->composerLockPath,
'packages' => $this->packages,
'private' => $this->private,
'type' => $this->collectorType->value,
];
}
}
1 change: 1 addition & 0 deletions src/Contract/Config/CollectorType.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ enum CollectorType: string
case TYPE_TRAIT = 'trait';
case TYPE_USES = 'uses';
case TYPE_PHP_INTERNAL = 'php_internal';
case TYPE_COMPOSER = 'composer';
}
50 changes: 50 additions & 0 deletions src/Core/Layer/Collector/ComposerCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Qossmic\Deptrac\Core\Layer\Collector;

use Qossmic\Deptrac\Contract\Ast\CouldNotParseFileException;
use Qossmic\Deptrac\Contract\Ast\TokenReferenceInterface;
use Qossmic\Deptrac\Contract\Layer\CollectorInterface;
use Qossmic\Deptrac\Contract\Layer\InvalidCollectorDefinitionException;
use RuntimeException;

final class ComposerCollector implements CollectorInterface
{
public function __construct()
{
}

public function satisfy(array $config, TokenReferenceInterface $reference): bool
{
if (!isset($config['composerPath']) || !is_string($config['composerPath'])) {
throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('ComposerCollector needs the path to the composer.json file as string.');
}

if (!isset($config['composerLockPath']) || !is_string($config['composerLockPath'])) {
throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('ComposerCollector needs the path to the composer.lock file as string.');
}

if (!isset($config['packages']) || !is_array($config['packages'])) {
throw InvalidCollectorDefinitionException::invalidCollectorConfiguration('ComposerCollector needs the list of packages as string.');
}

try {
$composerFilesParser = new ComposerFilesParser($config['composerLockPath']);
} catch (RuntimeException $exception) {
throw new CouldNotParseFileException('Could not parse composer files.', 0, $exception);
}

$namespaces = $composerFilesParser->autoloadableNamespacesForRequirements($config['packages'], true);
$token = $reference->getToken()->toString();

foreach ($namespaces as $namespace) {
if (str_starts_with($token, $namespace)) {
return true;
}
}

return false;
}
}
Loading