• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tempestphp / tempest-framework / 11904119374

18 Nov 2024 12:08PM UTC coverage: 81.946% (-0.1%) from 82.07%
11904119374

push

github

web-flow
chore(core): fixes to path helper (#744)

1 of 1 new or added line in 1 file covered. (100.0%)

27 existing lines in 3 files now uncovered.

7848 of 9577 relevant lines covered (81.95%)

52.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.33
/src/Tempest/Core/src/PublishesFiles.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Core;
6

7
use Closure;
8
use Tempest\Console\HasConsole;
9
use Tempest\Container\Inject;
10
use Tempest\Generation\ClassManipulator;
11
use Tempest\Generation\DataObjects\StubFile;
12
use Tempest\Generation\Enums\StubFileType;
13
use Tempest\Generation\Exceptions\FileGenerationAbortedException;
14
use Tempest\Generation\Exceptions\FileGenerationFailedException;
15
use Tempest\Generation\StubFileGenerator;
16
use function Tempest\path;
17
use Tempest\Support\NamespaceHelper;
18
use function Tempest\Support\str;
19
use Tempest\Validation\Rules\EndsWith;
20
use Tempest\Validation\Rules\NotEmpty;
21
use Throwable;
22

23
/**
24
 * Provides a bunch of methods to publish and generate files and work with common user input.
25
 */
26
trait PublishesFiles
27
{
28
    use HasConsole;
29

30
    #[Inject]
31
    private readonly Composer $composer;
32

33
    #[Inject]
34
    private readonly StubFileGenerator $stubFileGenerator;
35

36
    private array $publishedFiles = [];
37

38
    private array $publishedClasses = [];
39

40
    /**
41
     * Publishes a file from a source to a destination.
42
     * @param string $source The path to the source file.
43
     * @param string $destination The path to the destination file.
44
     * @param Closure(string $source, string $destination): void|null $callback A callback to run after the file is published.
45
     */
46
    public function publish(
4✔
47
        string $source,
48
        string $destination,
49
        ?Closure $callback = null,
50
    ): void {
51
        try {
52
            if (! $this->console->confirm(
4✔
53
                question: sprintf('Do you want to create "%s"', $destination),
4✔
54
                default: true,
4✔
55
            )) {
4✔
UNCOV
56
                throw new FileGenerationAbortedException('Skipped.');
×
57
            }
58

59
            if (! $this->askForOverride($destination)) {
3✔
UNCOV
60
                throw new FileGenerationAbortedException('Skipped.');
×
61
            }
62

63
            $stubFile = StubFile::from($source);
3✔
64

65
            // Handle class files
66
            if ($stubFile->type === StubFileType::CLASS_FILE) {
3✔
67
                $oldClass = new ClassManipulator($source);
2✔
68

69
                $this->stubFileGenerator->generateClassFile(
2✔
70
                    stubFile: $stubFile,
2✔
71
                    targetPath: $destination,
2✔
72
                    shouldOverride: true,
2✔
73
                    manipulations: [
2✔
74
                        fn (ClassManipulator $class) => $class->removeClassAttribute(DoNotDiscover::class),
2✔
75
                    ],
2✔
76
                );
2✔
77

78
                $newClass = new ClassManipulator($destination);
2✔
79

80
                $this->publishedClasses[$oldClass->getClassName()] = $newClass->getClassName();
2✔
81
            }
82

83
            // Handle raw files
84
            if ($stubFile->type === StubFileType::RAW_FILE) {
3✔
85
                $this->stubFileGenerator->generateRawFile(
2✔
86
                    stubFile: $stubFile,
2✔
87
                    targetPath: $destination,
2✔
88
                    shouldOverride: true,
2✔
89
                );
2✔
90
            }
91

92
            $this->publishedFiles[] = $destination;
3✔
93

94
            if ($callback !== null) {
3✔
95
                $callback($source, $destination);
1✔
96
            }
97

98
            $this->console->success(sprintf('File successfully created at "%s".', $destination));
3✔
99
        } catch (FileGenerationAbortedException $exception) {
×
100
            $this->console->info($exception->getMessage());
×
101
        } catch (Throwable $throwable) {
×
102
            throw new FileGenerationFailedException(
×
103
                message: 'The file could not be published.',
×
104
                previous: $throwable,
×
UNCOV
105
            );
×
106
        }
107
    }
108

109
    /**
110
     * Publishes the imports of the published classes.
111
     * Any published class that is imported in another published class will have its import updated.
112
     */
113
    public function publishImports(): void
1✔
114
    {
115
        foreach ($this->publishedFiles as $file) {
1✔
116
            $contents = str(file_get_contents($file));
1✔
117

118
            foreach ($this->publishedClasses as $old => $new) {
1✔
119
                $contents = $contents->replace($old, $new);
1✔
120
            }
121

122
            file_put_contents($file, $contents);
1✔
123
        }
124
    }
125

126
    /**
127
     * Gets a suggested path for the given class name.
128
     * This will use the user's main namespace as the base path.
129
     * @param string $className The class name to generate the path for, can include path parts (e.g. 'Models/User').
130
     * @param string|null $pathPrefix The prefix to add to the path (e.g. 'Models').
131
     * @param string|null $classSuffix The suffix to add to the class name (e.g. 'Model').
132
     * @return string The fully suggested path including the filename and extension.
133
     */
134
    public function getSuggestedPath(string $className, ?string $pathPrefix = null, ?string $classSuffix = null): string
24✔
135
    {
136
        // Separate input path and classname
137
        $inputClassName = NamespaceHelper::toClassName($className);
24✔
138
        $inputPath = str(path($className))->replaceLast($inputClassName, '')->toString();
24✔
139
        $className = str($inputClassName)
24✔
140
            ->pascal()
24✔
141
            ->finish($classSuffix ?? '')
24✔
142
            ->toString();
24✔
143

144
        // Prepare the suggested path from the project namespace
145
        return str(path(
24✔
146
            $this->composer->mainNamespace->path,
24✔
147
            $pathPrefix ?? '',
24✔
148
            $inputPath,
24✔
149
        ))
24✔
150
            ->finish('/')
24✔
151
            ->append($className . '.php')
24✔
152
            ->toString();
24✔
153
    }
154

155
    /**
156
     * Prompt the user for the target path to save the generated file.
157
     * @param string $suggestedPath The suggested path to show to the user.
158
     * @return string The target path that the user has chosen.
159
     */
160
    public function promptTargetPath(string $suggestedPath): string
9✔
161
    {
162
        $className = NamespaceHelper::toClassName($suggestedPath);
9✔
163

164
        return $this->console->ask(
9✔
165
            question: sprintf('Where do you want to save the file "%s"?', $className),
9✔
166
            default: $suggestedPath,
9✔
167
            validation: [new NotEmpty(), new EndsWith('.php')],
9✔
168
        );
9✔
169
    }
170

171
    /**
172
     * Ask the user if they want to override the file if it already exists.
173
     * @param string $targetPath The target path to check for existence.
174
     * @return bool Whether the user wants to override the file.
175
     */
176
    public function askForOverride(string $targetPath): bool
12✔
177
    {
178
        if (! file_exists($targetPath)) {
12✔
179
            return true;
12✔
180
        }
181

182
        return $this->console->confirm(
×
183
            question: sprintf('The file "%s" already exists. Do you want to override it?', $targetPath),
×
UNCOV
184
        );
×
185
    }
186
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc