Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.30% covered (success)
96.30%
26 / 27
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Generator
96.30% covered (success)
96.30%
26 / 27
66.67% covered (warning)
66.67%
2 / 3
10
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 generate
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
7
 hasLicense
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5/**
6 * This file is part of fast-forward/dev-tools.
7 *
8 * This source file is subject to the license bundled
9 * with this source code in the file LICENSE.
10 *
11 * @copyright Copyright (c) 2026 Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
12 * @license   https://opensource.org/licenses/MIT MIT License
13 *
14 * @see       https://github.com/php-fast-forward/dev-tools
15 * @see       https://github.com/php-fast-forward
16 * @see       https://datatracker.ietf.org/doc/html/rfc2119
17 */
18
19namespace FastForward\DevTools\License;
20
21use Symfony\Component\Filesystem\Filesystem;
22
23/**
24 * Generates LICENSE files from composer.json metadata.
25 *
26 * This class orchestrates the license generation workflow:
27 * 1. Reads metadata from composer.json via Reader
28 * 2. Resolves the license identifier to a template filename
29 * 3. Loads the license template content
30 * 4. Resolves placeholders with metadata (year, author, project, organization)
31 * 5. Writes the resulting LICENSE file to the target path
32 *
33 * Generation is skipped if a LICENSE file already exists or if the
34 * license is not supported.
35 */
36  class Generator implements GeneratorInterface
37{
38    /**
39     * Creates a new Generator instance.
40     *
41     * @param ReaderInterface $reader The reader for extracting metadata from composer.json
42     * @param ResolverInterface $resolver The resolver for mapping license identifiers to templates
43     * @param TemplateLoaderInterface $templateLoader The loader for reading template files
44     * @param PlaceholderResolverInterface $placeholderResolver The resolver for template placeholders
45     * @param Filesystem $filesystem The filesystem component for file operations
46     */
47    public function __construct(
48        private ReaderInterface $reader,
49        private ResolverInterface $resolver,
50        private TemplateLoaderInterface $templateLoader,
51        private PlaceholderResolverInterface $placeholderResolver,
52        private Filesystem $filesystem = new Filesystem()
53    ) {}
54
55    /**
56     * Generates a LICENSE file at the specified path.
57     *
58     * @param string $targetPath The full path where the LICENSE file should be written
59     *
60     * @return string|null The generated license content, or null if generation failed
61     */
62    public function generate(string $targetPath): ?string
63    {
64        $license = $this->reader->getLicense();
65
66        if (null === $license) {
67            return null;
68        }
69
70        if (! $this->resolver->isSupported($license)) {
71            return null;
72        }
73
74        if ($this->filesystem->exists($targetPath)) {
75            return null;
76        }
77
78        $templateFilename = $this->resolver->resolve($license);
79
80        if (null === $templateFilename) {
81            return null;
82        }
83
84        $template = $this->templateLoader->load($templateFilename);
85
86        $authors = $this->reader->getAuthors();
87        $firstAuthor = $authors[0] ?? null;
88
89        $metadata = [
90            'year' => $this->reader->getYear(),
91            'organization' => $this->reader->getVendor(),
92            'author' => null !== $firstAuthor ? ($firstAuthor['name'] ?: ($firstAuthor['email'] ?? '')) : '',
93            'project' => $this->reader->getPackageName(),
94        ];
95
96        $content = $this->placeholderResolver->resolve($template, $metadata);
97
98        $this->filesystem->dumpFile($targetPath, $content);
99
100        return $content;
101    }
102
103    /**
104     * Checks whether a supported license is present in composer.json.
105     *
106     * @return bool True if a supported license is defined, false otherwise
107     */
108    public function hasLicense(): bool
109    {
110        $license = $this->reader->getLicense();
111
112        if (null === $license) {
113            return false;
114        }
115
116        return $this->resolver->isSupported($license);
117    }
118}