Skip to content
Open
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
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ CHANGELOG
* Deprecate `Symfony\Bundle\FrameworkBundle\Console\Application::add()` in favor of `Symfony\Bundle\FrameworkBundle\Console\Application::addCommand()`
* Add `assertEmailAddressNotContains()` to the `MailerAssertionsTrait`
* Add `framework.type_info.aliases` option
* Add `static-site-generation:generate` command

7.3
---
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Attribute\Option;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpKernel\Exception\RuntimeException;
use Symfony\Component\HttpKernel\StaticSiteGeneration\StaticPageDumperInterface;
use Symfony\Component\HttpKernel\StaticSiteGeneration\StaticPagesGenerator;
use Symfony\Component\Routing\StaticSiteGeneration\StaticPageUrisProviderInterface;

/**
* @author Thomas Bibaut <[email protected]>
*/
#[AsCommand(name: 'static-site-generation:generate', description: 'Generates static pages')]
final class StaticSiteGenerationGenerateCommand extends Command
{
public function __construct(
private readonly StaticPagesGenerator $staticPagesGenerator,
private readonly StaticPageDumperInterface $staticPageDumper,
private readonly StaticPageUrisProviderInterface $staticPageUrisProvider,
) {
parent::__construct();
}

public function __invoke(
SymfonyStyle $io,
#[Option(description: 'A pattern to filter the pages to generate')] ?string $filterPattern = null,
#[Option(description: 'Do not dump pages')] bool $dryRun = false,
): int {
$successful = true;

foreach ($this->staticPageUrisProvider->provide() as $uri) {
if (null !== $filterPattern && !preg_match($filterPattern, $uri)) {
continue;
}

try {
['content' => $content, 'format' => $format] = $this->staticPagesGenerator->generate($uri);

if (false === $dryRun) {
$this->staticPageDumper->dump($uri, $content, $format);
}

$io->info(\sprintf('Generated static page for URI "%s"', $uri));
} catch (RuntimeException $exception) {
$io->error(\sprintf('Generating page for URI "%s" failed : %s', $uri, $exception->getMessage()));
$successful = false;
}
}

return $successful ? Command::SUCCESS : Command::FAILURE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class UnusedTagsPass implements CompilerPassInterface
'routing.expression_language_provider',
'routing.loader',
'routing.route_loader',
'routing.static_site.params_provider',
'scheduler.schedule_provider',
'scheduler.task',
'security.access_token_handler.oidc.encryption_algorithm',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Psr\Log\LoggerAwareInterface;
use Symfony\Bridge\Monolog\Processor\DebugProcessor;
use Symfony\Bridge\Twig\Extension\CsrfExtension;
use Symfony\Bundle\FrameworkBundle\Command\StaticSiteGenerationGenerateCommand;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface;
use Symfony\Bundle\FullStack;
Expand Down Expand Up @@ -173,6 +174,7 @@
use Symfony\Component\RemoteEvent\RemoteEvent;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Loader\AttributeServicesLoader;
use Symfony\Component\Routing\StaticSiteGeneration\ParamsProviderInterface;
use Symfony\Component\Scheduler\Attribute\AsCronTask;
use Symfony\Component\Scheduler\Attribute\AsPeriodicTask;
use Symfony\Component\Scheduler\Attribute\AsSchedule;
Expand Down Expand Up @@ -436,6 +438,8 @@ public function load(array $configs, ContainerBuilder $container): void
$this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader);
$this->registerSecretsConfiguration($config['secrets'], $container, $loader, $config['secret'] ?? null);

$loader->load('http_kernel.php');

$exceptionListener = $container->getDefinition('exception_listener');

$loggers = [];
Expand Down Expand Up @@ -1342,6 +1346,19 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co
$container->getDefinition('router.request_context')
->replaceArgument(0, $config['default_uri']);
}

$container->registerForAutoconfiguration(ParamsProviderInterface::class)
->addTag('routing.static_site.params_provider');

if ($this->hasConsole()) {
$container->register('console.command.static_site_generation_generate', StaticSiteGenerationGenerateCommand::class)
->setArguments([
new Reference('http_kernel.static_site.pages_generator'),
new Reference('http_kernel.static_site.page_dumper'),
new Reference('routing.static_site.pages_uri_provider'),
])
->addTag('console.command');
}
}

private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Component\HttpKernel\StaticSiteGeneration\FilesystemStaticPageDumper;
use Symfony\Component\HttpKernel\StaticSiteGeneration\StaticPageDumperInterface;
use Symfony\Component\HttpKernel\StaticSiteGeneration\StaticPagesGenerator;

return static function (ContainerConfigurator $container) {
$container->services()
->set('http_kernel.static_site.pages_generator', StaticPagesGenerator::class)
->args([
service('http_kernel'),
service('routing.static_site.pages_uri_provider'),
])

->set('http_kernel.static_site.page_dumper.filesystem', FilesystemStaticPageDumper::class)
->args([
param('kernel.project_dir'),
])
->alias('http_kernel.static_site.page_dumper', 'http_kernel.static_site.page_dumper.filesystem')
->alias(StaticPageDumperInterface::class, 'http_kernel.static_site.page_dumper')
;
};
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RequestContextAwareInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\StaticSiteGeneration\StaticPageUrisProvider;
use Symfony\Component\Routing\StaticSiteGeneration\StaticPageUrisProviderInterface;

return static function (ContainerConfigurator $container) {
$container->parameters()
Expand Down Expand Up @@ -215,5 +217,12 @@
service('twig')->ignoreOnInvalid(),
])
->public()

->set('routing.static_site.pages_uri_provider', StaticPageUrisProvider::class)
->args([
service('router'),
tagged_locator('routing.static_site.params_provider'),
])
->alias(StaticPageUrisProviderInterface::class, 'routing.static_site.pages_uri_provider')
;
};
2 changes: 2 additions & 0 deletions src/Symfony/Component/HttpKernel/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ CHANGELOG
* Deprecate implementing `__sleep/wakeup()` on kernels; use `__(un)serialize()` instead
* Deprecate implementing `__sleep/wakeup()` on data collectors; use `__(un)serialize()` instead
* Make `Profile` final and `Profiler::__sleep()` internal
* Add `StaticPageGenerator` to generate static page content
* Add `StaticPageDumperInterface` to dump a static page content into a storage

7.3
---
Expand Down
16 changes: 16 additions & 0 deletions src/Symfony/Component/HttpKernel/Exception/RuntimeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\Exception;

class RuntimeException extends \RuntimeException
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\StaticSiteGeneration;

use Symfony\Component\Filesystem\Filesystem;

/**
* @author Thomas Bibaut <[email protected]>
*/
final class FilesystemStaticPageDumper implements StaticPageDumperInterface
{
private ?Filesystem $filesystem = null;

public function __construct(
private string $projectDir,
) {
}

public function dump(string $uri, string $content, ?string $format = null): void
{
$fileName = '/' === $uri ? 'index.html' : $uri;

if ($format && !str_ends_with($uri, '.'.$format)) {
$fileName = \sprintf('%s.%s', $uri, $format);
}

$staticPagesDirectory = \sprintf('%s/%s/static-pages', $this->projectDir, $this->getPublicDirectory());

$this->filesystem ??= new Filesystem();
$this->filesystem->dumpFile(\sprintf('%s/%s', $staticPagesDirectory, $fileName), $content);
}

private function getPublicDirectory(): string
{
$defaultPublicDir = 'public';
$composerFilePath = \sprintf('%s/composer.json', $this->projectDir);

if (!file_exists($composerFilePath)) {
return $defaultPublicDir;
}

$this->filesystem ??= new Filesystem();
$composerConfig = json_decode($this->filesystem->readFile($composerFilePath), true, flags: \JSON_THROW_ON_ERROR);

return $composerConfig['extra']['public-dir'] ?? $defaultPublicDir;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\StaticSiteGeneration;

/**
* Dump a static page content into a storage.
*
* @author Thomas Bibaut <[email protected]>
*/
interface StaticPageDumperInterface
{
public function dump(string $uri, string $content, ?string $format): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\StaticSiteGeneration;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\RuntimeException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;

/**
* @author Thomas Bibaut <[email protected]>
*/
final readonly class StaticPagesGenerator
{
public function __construct(
private HttpKernelInterface $kernel,
) {
}

/**
* Generate page content for URI.
*
* @return array{content: string, format: ?string}
*
* @throws RuntimeException
*/
public function generate(string $uri): array
{
$request = Request::create($uri);
try {
$response = $this->kernel->handle($request, HttpKernelInterface::MAIN_REQUEST);

if ($this->kernel instanceof TerminableInterface) {
$this->kernel->terminate($request, $response);
}
} catch (\Exception $e) {
throw new RuntimeException(\sprintf('Cannot generate page for URI "%s".', $uri), $e->getCode(), $e);
}

if (Response::HTTP_OK !== $response->getStatusCode()) {
throw new RuntimeException(\sprintf('Expected URI "%s" to return status code 200, got %d.', $uri, $response->getStatusCode()));
}

return [
'content' => $response->getContent(),
'format' => $request->getFormat($response->headers->get('Content-Type')),
];
}
}
Loading
Loading