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

valksor / php-dev-build / 19202038340

09 Nov 2025 02:25AM UTC coverage: 17.283% (-0.9%) from 18.191%
19202038340

push

github

k0d3r1s
update documentation

383 of 2216 relevant lines covered (17.28%)

0.92 hits per line

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

9.43
/Command/AbstractCommand.php
1
<?php declare(strict_types = 1);
2

3
/*
4
 * This file is part of the Valksor package.
5
 *
6
 * (c) Davis Zalitis (k0d3r1s)
7
 * (c) SIA Valksor <packages@valksor.com>
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12

13
namespace ValksorDev\Build\Command;
14

15
use Exception;
16
use RuntimeException;
17
use Symfony\Component\Console\Command\Command;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Style\SymfonyStyle;
20
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
21
use Symfony\Component\Process\Process;
22
use Valksor\Bundle\Command\AbstractCommand as BundleAbstractCommand;
23
use Valksor\Component\Sse\Helper;
24
use ValksorDev\Build\Provider\IoAwareInterface;
25
use ValksorDev\Build\Provider\ProviderRegistry;
26
use ValksorDev\Build\Util\ConsoleCommandBuilder;
27

28
use function method_exists;
29
use function usleep;
30

31
/**
32
 * Abstract base class for Valksor build system commands.
33
 *
34
 * This class provides common patterns, utilities, and configuration handling
35
 * for all build commands in the Valksor development system. It establishes
36
 * consistent behavior across build commands and reduces code duplication.
37
 *
38
 * Command Patterns Provided:
39
 * - Watch mode handling with automatic cleanup
40
 * - Non-interactive mode support for CI/automation
41
 * - Initialization phase execution (binary downloads, dependency setup)
42
 * - SSE server startup and management
43
 * - Minification control based on environment
44
 * - Provider registry integration for service coordination
45
 *
46
 * Key Features:
47
 * - Extends BundleAbstractCommand for core Valksor functionality
48
 * - Provides utility methods for common command operations
49
 * - Handles service lifecycle management in watch mode
50
 * - Integrates with the provider registry for service discovery
51
 * - Supports both development and production environments
52
 *
53
 * Design Principles:
54
 * - Consistent command interface across all build tools
55
 * - Graceful degradation in different environments
56
 * - Proper resource cleanup and shutdown handling
57
 * - Flexible configuration through command options
58
 */
59
abstract class AbstractCommand extends BundleAbstractCommand
60
{
61
    use Helper;
62

63
    /**
64
     * Initialize the abstract command with required dependencies.
65
     *
66
     * The constructor receives core dependencies needed by all build commands:
67
     * - Parameter bag for accessing application configuration and build settings
68
     * - Provider registry for service discovery and coordination
69
     *
70
     * The provider registry is marked as protected readonly to allow extending
71
     * commands to access service providers while preventing modification.
72
     *
73
     * @param ParameterBagInterface $parameterBag     Application configuration and build parameters
74
     * @param ProviderRegistry      $providerRegistry Registry of available service providers
75
     */
76
    public function __construct(
77
        ParameterBagInterface $parameterBag,
78
        protected readonly ProviderRegistry $providerRegistry,
79
        protected readonly ?ConsoleCommandBuilder $commandBuilder = null,
80
    ) {
81
        parent::__construct($parameterBag);
43✔
82
    }
83

84
    /**
85
     * Handle watch mode setup and cleanup for long-running services.
86
     *
87
     * This method implements the watch mode pattern used throughout the build system.
88
     * It provides automatic cleanup and resource management for services that run
89
     * indefinitely in watch mode, ensuring proper shutdown and preventing resource leaks.
90
     *
91
     * Watch Mode Pattern:
92
     * - Returns a cleanup function that can be called during shutdown
93
     * - Handles service lifecycle management (start/stop)
94
     * - Manages PID file cleanup for background processes
95
     * - Provides no-op cleanup for non-watch mode execution
96
     *
97
     * Usage Pattern:
98
     * ```php
99
     * $cleanup = $this->handleWatchMode($service, $input, 'tailwind');
100
     * try {
101
     *     // Run service logic here
102
     * } finally {
103
     *     $cleanup(); // Always cleanup, even on exceptions
104
     * }
105
     * ```
106
     *
107
     * This approach ensures that long-running services (file watchers, compilers)
108
     * are properly cleaned up when the command terminates or receives signals.
109
     *
110
     * @param object         $service     The service instance to manage
111
     * @param InputInterface $input       Command input to determine watch mode
112
     * @param string         $serviceName Service name for logging/debugging
113
     *
114
     * @return callable Cleanup function that should be called when the command finishes
115
     */
116
    protected function handleWatchMode(
117
        object $service,
118
        InputInterface $input,
119
        string $serviceName,
120
    ): callable {
121
        if (!$this->isWatchMode($input)) {
1✔
122
            // Return a no-op cleanup function for non-watch mode
123
            // This allows consistent cleanup calling without conditional logic
124
            return function (): void {
1✔
125
                // No cleanup needed for one-time execution
126
            };
1✔
127
        }
128

129
        // For watch mode, create a cleanup function that handles graceful shutdown
130
        // This ensures resources are properly released when the command exits
131
        return static function () use ($service): void {
×
132
            // Stop the service if it supports lifecycle management
133
            // Most long-running services implement a stop() method for graceful shutdown
134
            if (method_exists($service, 'stop')) {
×
135
                $service->stop();
×
136
            }
137

138
            // Clean up PID files if the service supports process tracking
139
            // This prevents stale PID files from interfering with future runs
140
            if (method_exists($service, 'removePidFile')) {
×
141
                $service->removePidFile();
×
142
            }
143
        };
×
144
    }
145

146
    protected function isNonInteractive(
147
        InputInterface $input,
148
    ): bool {
149
        return (bool) $input->getOption('non-interactive');
×
150
    }
151

152
    protected function isWatchMode(
153
        InputInterface $input,
154
    ): bool {
155
        return (bool) $input->getOption('watch');
1✔
156
    }
157

158
    /**
159
     * Run init phase - always runs first for all commands.
160
     */
161
    protected function runInit(
162
        SymfonyStyle $io,
163
    ): void {
164
        $servicesConfig = $this->parameterBag->get('valksor.build.services');
×
165
        $initProviders = $this->providerRegistry->getProvidersByFlag($servicesConfig, 'init');
×
166

167
        if (empty($initProviders)) {
×
168
            return;
×
169
        }
170

171
        $io->section('Running initialization tasks...');
×
172

173
        // Binaries always run first
174
        if (isset($initProviders['binaries'])) {
×
175
            $io->text('Ensuring binaries are available...');
×
176
            $this->runProvider('binaries', $initProviders['binaries'], []);
×
177
            unset($initProviders['binaries']);
×
178
        }
179

180
        // Run remaining init providers
181
        foreach ($initProviders as $name => $provider) {
×
182
            $config = $servicesConfig[$name] ?? [];
×
183
            $options = $config['options'] ?? [];
×
184
            $this->runProvider($name, $provider, $options);
×
185
        }
186

187
        $io->success('Initialization completed');
×
188
    }
189

190
    /**
191
     * Run a single provider with error handling.
192
     */
193
    protected function runProvider(
194
        string $name,
195
        $provider,
196
        array $options,
197
    ): void {
198
        try {
199
            $provider->init($options);
×
200
        } catch (Exception $e) {
×
201
            // In development, warn but continue; in production, fail
202
            if ($this->isProductionEnvironment()) {
×
203
                throw new RuntimeException("Provider '$name' failed: " . $e->getMessage(), 0, $e);
×
204
            }
205
            // Warning - continue but this could be problematic in non-interactive mode
206
        }
207
    }
208

209
    /**
210
     * Get SSE command for integration.
211
     */
212
    protected function runSseCommand(): int
213
    {
214
        // Use ConsoleCommandBuilder if available, otherwise fall back to manual construction
215
        if ($this->commandBuilder) {
×
216
            $process = $this->commandBuilder->build('valksor:sse');
×
217
        } else {
218
            $process = new Process(['php', 'bin/console', 'valksor:sse']);
×
219
        }
220

221
        // Start SSE server in background (non-blocking)
222
        $process->start();
×
223

224
        // Give SSE server more time to start and bind to port
225
        $maxWaitTime = 3; // 3 seconds max wait time
×
226
        $waitInterval = 250000; // 250ms intervals
×
227
        $elapsedTime = 0;
×
228

229
        while ($elapsedTime < $maxWaitTime) {
×
230
            usleep($waitInterval);
×
231
            $elapsedTime += ($waitInterval / 1000000);
×
232

233
            // Check if process is still running and hasn't failed
234
            if (!$process->isRunning()) {
×
235
                // Process stopped - check if it was successful
236
                return $process->isSuccessful() ? Command::SUCCESS : Command::FAILURE;
×
237
            }
238

239
            // After 1 second, check if we can verify the server is actually stable
240
            if ($elapsedTime >= 1.0) {
×
241
                // The SSE server should be stable by now, proceed
242
                break;
×
243
            }
244
        }
245

246
        // Final check - is the process still running successfully?
247
        return $process->isRunning() ? Command::SUCCESS : Command::FAILURE;
×
248
    }
249

250
    /**
251
     * Set SymfonyStyle on provider objects that support it.
252
     */
253
    protected function setProviderIo(
254
        object $provider,
255
        SymfonyStyle $io,
256
    ): void {
257
        if ($provider instanceof IoAwareInterface) {
×
258
            $provider->setIo($io);
×
259
        }
260
    }
261

262
    /**
263
     * Determine whether build output should be minified based on configuration.
264
     *
265
     * This method implements the minification decision pattern used across build commands.
266
     * It provides a hierarchical approach to determining minification settings:
267
     *
268
     * Decision Priority (highest to lowest):
269
     * 1. Command-line --no-minify flag (explicitly disable minification)
270
     * 2. Command-line --minify flag (explicitly enable minification)
271
     * 3. Environment-based setting (production = minify, development = unminified)
272
     *
273
     * This approach allows developers to override default behavior while maintaining
274
     * sensible defaults for different environments.
275
     *
276
     * Use Cases:
277
     * - Development: Debuggable, unminified output for easier troubleshooting
278
     * - Production: Optimized, minified output for better performance
279
     * - CI/CD: Explicit control via command-line flags
280
     * - Testing: Disable minification for better assertion debugging
281
     *
282
     * @param InputInterface $input Command input containing minification options
283
     *
284
     * @return bool True if output should be minified, false otherwise
285
     */
286
    protected function shouldMinify(
287
        InputInterface $input,
288
    ): bool {
289
        // Priority 1: Explicit --no-minify flag overrides all other settings
290
        // This allows developers to force unminified output even in production
291
        if ($input->hasOption('no-minify') && $input->getOption('no-minify')) {
×
292
            return false;
×
293
        }
294

295
        // Priority 2: Explicit --minify flag enables minification
296
        // Useful for testing production builds in development
297
        if ($input->hasOption('minify') && $input->getOption('minify')) {
×
298
            return true;
×
299
        }
300

301
        // Priority 3: Default behavior based on environment
302
        // Production environments typically want minified assets for performance
303
        return $this->isProductionEnvironment();
×
304
    }
305

306
    protected function shouldShowRealTimeOutput(
307
        InputInterface $input,
308
    ): bool {
309
        return !$this->isNonInteractive($input);
×
310
    }
311
}
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