From 00020fd74eabf32fb0ce32b9ebf7879007d0a114 Mon Sep 17 00:00:00 2001
From: Norbert Orzechowicz
Date: Sun, 2 Aug 2026 11:00:35 +0200
Subject: [PATCH 1/2] test: stop tests depending on host environment detail
- assert CLI table output independently of symfony/console border
rendering
- schedule PostgreSQL test notifications server side instead of spawning
a PHP subprocess
---
.../DatabaseTableListCommandTest.php | 18 ++-
.../Integration/Client/ListenNotifyTest.php | 6 +-
.../Tests/Integration/PostgreSqlContext.php | 123 +++++++-----------
3 files changed, 59 insertions(+), 88 deletions(-)
diff --git a/src/cli/tests/Flow/CLI/Tests/Integration/DatabaseTableListCommandTest.php b/src/cli/tests/Flow/CLI/Tests/Integration/DatabaseTableListCommandTest.php
index 08e7f19c0c..e9f56f5d19 100644
--- a/src/cli/tests/Flow/CLI/Tests/Integration/DatabaseTableListCommandTest.php
+++ b/src/cli/tests/Flow/CLI/Tests/Integration/DatabaseTableListCommandTest.php
@@ -61,23 +61,21 @@ public function test_run_db_table_list(): void
$tester->assertCommandIsSuccessful();
- static::assertSame(<<<'OUTPUT'
+ $display = $tester->getDisplay();
+
+ static::assertStringContainsString(<<<'OUTPUT'
┌──────────┬───────────┬─────────┐
│ Name │ Namespace │ Columns │
├──────────┼───────────┼─────────┤
│ table_01 │ public │ 3 │
│ table_02 │ public │ 3 │
└──────────┴───────────┴─────────┘
- ------------------ -----
- Summary
- ------------------ -----
- Total tables 2
- Total namespaces 1
- Total columns 6
- ------------------ -----
-
+ OUTPUT, $display);
- OUTPUT, $tester->getDisplay());
+ static::assertStringContainsString('Summary', $display);
+ static::assertStringContainsString('Total tables 2', $display);
+ static::assertStringContainsString('Total namespaces 1', $display);
+ static::assertStringContainsString('Total columns 6', $display);
}
protected function dbContext(): DatabaseContext
diff --git a/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/Client/ListenNotifyTest.php b/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/Client/ListenNotifyTest.php
index d87b68a62b..715ae0d60a 100644
--- a/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/Client/ListenNotifyTest.php
+++ b/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/Client/ListenNotifyTest.php
@@ -20,15 +20,15 @@ public function test_blocking_wait_unblocks_on_concurrent_notify(): void
$listener = $this->pgsqlContext()->client;
$listener->listen('flow_test_blocking');
- $this->pgsqlContext()->spawnBackgroundNotifier('flow_test_blocking', 'delivered', 200);
+ $this->pgsqlContext()->scheduleNotify('flow_test_blocking', 'delivered', 200);
$startNs = hrtime(true);
$notification = $listener->wait(3000);
$elapsedMs = (hrtime(true) - $startNs) / 1_000_000;
if ($notification === null) {
- $stderr = implode("\n---\n", $this->pgsqlContext()->backgroundStderrContents());
- static::fail('No notification received. Background sender stderr:' . "\n" . $stderr);
+ $errors = implode("\n---\n", $this->pgsqlContext()->backgroundNotifierErrors());
+ static::fail('No notification received. Scheduled notification errors:' . "\n" . $errors);
}
static::assertSame('flow_test_blocking', $notification->channel);
diff --git a/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/PostgreSqlContext.php b/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/PostgreSqlContext.php
index 779c5a8a83..3611e021e5 100644
--- a/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/PostgreSqlContext.php
+++ b/src/lib/postgresql/tests/Flow/PostgreSql/Tests/Integration/PostgreSqlContext.php
@@ -5,41 +5,37 @@
namespace Flow\PostgreSql\Tests\Integration;
use Flow\PostgreSql\Client\Client;
+use PgSql\Connection;
use RuntimeException;
-use function fclose;
-use function file_get_contents;
use function Flow\PostgreSql\DSL\and_;
use function Flow\PostgreSql\DSL\col;
use function Flow\PostgreSql\DSL\drop;
use function Flow\PostgreSql\DSL\eq;
use function Flow\PostgreSql\DSL\func;
use function Flow\PostgreSql\DSL\literal;
+use function Flow\PostgreSql\DSL\notify;
use function Flow\PostgreSql\DSL\pgsql_client;
use function Flow\PostgreSql\DSL\pgsql_connection_dsn;
use function Flow\PostgreSql\DSL\select;
use function Flow\PostgreSql\DSL\table;
-use function getcwd;
use function getenv;
-use function is_file;
-use function is_resource;
-use function proc_close;
-use function proc_open;
+use function pg_close;
+use function pg_connect;
+use function pg_get_result;
+use function pg_last_error;
+use function pg_result_error;
+use function pg_send_query;
use function sprintf;
-use function sys_get_temp_dir;
-use function uniqid;
-use function unlink;
-use function var_export;
+
+use const PGSQL_CONNECT_FORCE_NEW;
final class PostgreSqlContext
{
public readonly Client $client;
- /** @var list */
- private array $backgroundProcesses = [];
-
- /** @var list */
- private array $backgroundStderrLogs = [];
+ /** @var list */
+ private array $backgroundConnections = [];
private readonly string $dsn;
@@ -59,19 +55,26 @@ public function __construct()
}
/**
+ * Drains the results of every scheduled notification and returns the errors
+ * PostgreSQL reported for them. Empty when all of them succeeded.
+ *
* @return list
*/
- public function backgroundStderrContents(): array
+ public function backgroundNotifierErrors(): array
{
- $out = [];
+ $errors = [];
- foreach ($this->backgroundStderrLogs as $path) {
- if (is_file($path)) {
- $out[] = file_get_contents($path) ?: '';
+ foreach ($this->backgroundConnections as $connection) {
+ while (($result = pg_get_result($connection)) !== false) {
+ $error = pg_result_error($result);
+
+ if ($error !== false && $error !== '') {
+ $errors[] = $error;
+ }
}
}
- return $out;
+ return $errors;
}
public function client(): Client
@@ -81,17 +84,10 @@ public function client(): Client
public function close(): void
{
- foreach ($this->backgroundProcesses as $process) {
- proc_close($process);
- }
- $this->backgroundProcesses = [];
-
- foreach ($this->backgroundStderrLogs as $path) {
- if (is_file($path)) {
- @unlink($path);
- }
+ foreach ($this->backgroundConnections as $connection) {
+ pg_close($connection);
}
- $this->backgroundStderrLogs = [];
+ $this->backgroundConnections = [];
foreach ($this->secondaryClients as $client) {
$client->close();
@@ -190,56 +186,33 @@ public function newClient(): Client
}
/**
- * Spawns a detached child PHP process that sleeps for $delayMs and then
- * fires a NOTIFY on $channel with $payload using its own Client
- * connection. Returns the child process handle; the context closes it
- * during close(). Used for integration tests that need to exercise the
- * blocking wait path of waitForNotification().
+ * Fires a NOTIFY on $channel with $payload after $delayMs on a dedicated
+ * connection, without blocking the caller. The delay runs server side, so
+ * the notification arrives while the caller sits in a blocking wait on its
+ * own connection. Used by tests that exercise the blocking wait path of
+ * waitForNotification().
*/
- public function spawnBackgroundNotifier(string $channel, string $payload, int $delayMs): void
+ public function scheduleNotify(string $channel, string $payload, int $delayMs): void
{
- $cwd = getcwd();
+ $connection = pg_connect(pgsql_connection_dsn($this->dsn)->toString(), PGSQL_CONNECT_FORCE_NEW);
- if ($cwd === false) {
- throw new RuntimeException('Failed to determine current working directory');
+ if ($connection === false) {
+ throw new RuntimeException('Failed to open a connection for the scheduled notification');
}
- $autoload = $cwd . '/vendor/autoload.php';
+ $this->backgroundConnections[] = $connection;
- if (!is_file($autoload)) {
- throw new RuntimeException(sprintf('Project vendor/autoload.php not found at %s', $autoload));
- }
+ $sent = pg_send_query($connection, sprintf(
+ '%s; %s',
+ select(func('pg_sleep', [literal($delayMs / 1000)]))->toSql(),
+ notify($channel)->withPayload($payload)->toSql(),
+ ));
- $phpCode = sprintf(
- 'usleep(%d); require %s; try { $c = \Flow\PostgreSql\DSL\pgsql_client(\Flow\PostgreSql\DSL\pgsql_connection_dsn(%s)); $c->execute(\Flow\PostgreSql\DSL\notify(%s)->withPayload(%s)); $c->close(); } catch (\Throwable $e) { fwrite(STDERR, "child error: " . get_class($e) . ": " . $e->getMessage() . "\n"); exit(1); }',
- $delayMs * 1000,
- var_export($autoload, true),
- var_export($this->dsn, true),
- var_export($channel, true),
- var_export($payload, true),
- );
-
- $stderrLog = sys_get_temp_dir() . '/flow-bg-notifier-' . uniqid('', true) . '.log';
- $descriptors = [
- 0 => ['pipe', 'r'],
- 1 => ['file', '/dev/null', 'w'],
- 2 => ['file', $stderrLog, 'w'],
- ];
-
- $pipes = [];
- $process = proc_open(['php', '-r', $phpCode], $descriptors, $pipes);
-
- if (!is_resource($process)) {
- throw new RuntimeException('Failed to spawn background notifier process');
+ if ($sent === false) {
+ throw new RuntimeException(sprintf(
+ 'Failed to send the scheduled notification: %s',
+ pg_last_error($connection),
+ ));
}
-
- if (!is_resource($pipes[0] ?? null)) {
- throw new RuntimeException('Failed to open stdin pipe to background notifier process');
- }
-
- fclose($pipes[0]);
-
- $this->backgroundProcesses[] = $process;
- $this->backgroundStderrLogs[] = $stderrLog;
}
}
From 32c435cb86ce97df8e6becaf9b2211d34db50cf8 Mon Sep 17 00:00:00 2001
From: Norbert Orzechowicz
Date: Sun, 2 Aug 2026 12:05:14 +0200
Subject: [PATCH 2/2] refactor(flow-php/web): rename shop to work-shop to match
the URLs
- move the inter-page legal links from markdown into the templates
- lint web/landing markdown on pull requests
---
.github/workflows/docs.yml | 1 +
...er.js => work_shop_carousel_controller.js} | 0
...er.js => work_shop_checkout_controller.js} | 0
...er.js => work_shop_confetti_controller.js} | 0
...troller.js => work_shop_nav_controller.js} | 0
...oller.js => work_shop_order_controller.js} | 0
.../{shop => work-shop}/consulting-2.jpg | Bin
.../images/{shop => work-shop}/consulting.jpg | Bin
.../symfony-backoffice-api.png | Bin
.../symfony-backoffice-app.png | Bin
.../symfony-backoffice-import.png | Bin
.../symfony-backoffice-profiler.png | Bin
.../symfony-backoffice-telemetry.png | Bin
web/landing/assets/styles/app.css | 2 +-
.../{shop-legal.css => work-shop-legal.css} | 26 +++---
web/landing/config/services.yaml | 6 +-
.../{shop => work-shop}/privacy-policy.md | 1 -
.../{shop => work-shop}/terms-of-sales.md | 2 +-
.../Website/Controller/ShopController.php | 76 ----------------
.../Website/Controller/WorkShopController.php | 82 ++++++++++++++++++
web/landing/templates/main/sponsor.html.twig | 2 +-
.../{shop => work-shop}/_listing.html.twig | 24 ++---
.../blueprints/how-it-works/index.html.twig | 8 +-
.../symfony-backoffice/index.html.twig | 14 +--
.../consulting/index.html.twig | 6 +-
.../{shop => work-shop}/index.html.twig | 12 +--
.../privacy-policy/index.html.twig | 10 ++-
.../success/index.html.twig | 10 +--
.../terms-of-sales/index.html.twig | 10 ++-
...lerTest.php => WorkShopControllerTest.php} | 44 ++++++----
30 files changed, 182 insertions(+), 154 deletions(-)
rename web/landing/assets/controllers/{shop_carousel_controller.js => work_shop_carousel_controller.js} (100%)
rename web/landing/assets/controllers/{shop_checkout_controller.js => work_shop_checkout_controller.js} (100%)
rename web/landing/assets/controllers/{shop_confetti_controller.js => work_shop_confetti_controller.js} (100%)
rename web/landing/assets/controllers/{shop_nav_controller.js => work_shop_nav_controller.js} (100%)
rename web/landing/assets/controllers/{shop_order_controller.js => work_shop_order_controller.js} (100%)
rename web/landing/assets/images/{shop => work-shop}/consulting-2.jpg (100%)
rename web/landing/assets/images/{shop => work-shop}/consulting.jpg (100%)
rename web/landing/assets/images/{shop => work-shop}/symfony-backoffice-api.png (100%)
rename web/landing/assets/images/{shop => work-shop}/symfony-backoffice-app.png (100%)
rename web/landing/assets/images/{shop => work-shop}/symfony-backoffice-import.png (100%)
rename web/landing/assets/images/{shop => work-shop}/symfony-backoffice-profiler.png (100%)
rename web/landing/assets/images/{shop => work-shop}/symfony-backoffice-telemetry.png (100%)
rename web/landing/assets/styles/{shop-legal.css => work-shop-legal.css} (70%)
rename web/landing/content/{shop => work-shop}/privacy-policy.md (98%)
rename web/landing/content/{shop => work-shop}/terms-of-sales.md (99%)
delete mode 100644 web/landing/src/Flow/Website/Controller/ShopController.php
create mode 100644 web/landing/src/Flow/Website/Controller/WorkShopController.php
rename web/landing/templates/{shop => work-shop}/_listing.html.twig (85%)
rename web/landing/templates/{shop => work-shop}/blueprints/how-it-works/index.html.twig (96%)
rename web/landing/templates/{shop => work-shop}/blueprints/symfony-backoffice/index.html.twig (96%)
rename web/landing/templates/{shop => work-shop}/consulting/index.html.twig (97%)
rename web/landing/templates/{shop => work-shop}/index.html.twig (95%)
rename web/landing/templates/{shop => work-shop}/privacy-policy/index.html.twig (63%)
rename web/landing/templates/{shop => work-shop}/success/index.html.twig (96%)
rename web/landing/templates/{shop => work-shop}/terms-of-sales/index.html.twig (61%)
rename web/landing/tests/Flow/Website/Tests/Functional/{ShopControllerTest.php => WorkShopControllerTest.php} (82%)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index ddb068a0b0..ddf9ecbb09 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -4,6 +4,7 @@ on:
- 'src/**'
- 'docs/**'
- 'examples/**'
+ - 'web/landing/**'
- 'README.md'
push:
branches:
diff --git a/web/landing/assets/controllers/shop_carousel_controller.js b/web/landing/assets/controllers/work_shop_carousel_controller.js
similarity index 100%
rename from web/landing/assets/controllers/shop_carousel_controller.js
rename to web/landing/assets/controllers/work_shop_carousel_controller.js
diff --git a/web/landing/assets/controllers/shop_checkout_controller.js b/web/landing/assets/controllers/work_shop_checkout_controller.js
similarity index 100%
rename from web/landing/assets/controllers/shop_checkout_controller.js
rename to web/landing/assets/controllers/work_shop_checkout_controller.js
diff --git a/web/landing/assets/controllers/shop_confetti_controller.js b/web/landing/assets/controllers/work_shop_confetti_controller.js
similarity index 100%
rename from web/landing/assets/controllers/shop_confetti_controller.js
rename to web/landing/assets/controllers/work_shop_confetti_controller.js
diff --git a/web/landing/assets/controllers/shop_nav_controller.js b/web/landing/assets/controllers/work_shop_nav_controller.js
similarity index 100%
rename from web/landing/assets/controllers/shop_nav_controller.js
rename to web/landing/assets/controllers/work_shop_nav_controller.js
diff --git a/web/landing/assets/controllers/shop_order_controller.js b/web/landing/assets/controllers/work_shop_order_controller.js
similarity index 100%
rename from web/landing/assets/controllers/shop_order_controller.js
rename to web/landing/assets/controllers/work_shop_order_controller.js
diff --git a/web/landing/assets/images/shop/consulting-2.jpg b/web/landing/assets/images/work-shop/consulting-2.jpg
similarity index 100%
rename from web/landing/assets/images/shop/consulting-2.jpg
rename to web/landing/assets/images/work-shop/consulting-2.jpg
diff --git a/web/landing/assets/images/shop/consulting.jpg b/web/landing/assets/images/work-shop/consulting.jpg
similarity index 100%
rename from web/landing/assets/images/shop/consulting.jpg
rename to web/landing/assets/images/work-shop/consulting.jpg
diff --git a/web/landing/assets/images/shop/symfony-backoffice-api.png b/web/landing/assets/images/work-shop/symfony-backoffice-api.png
similarity index 100%
rename from web/landing/assets/images/shop/symfony-backoffice-api.png
rename to web/landing/assets/images/work-shop/symfony-backoffice-api.png
diff --git a/web/landing/assets/images/shop/symfony-backoffice-app.png b/web/landing/assets/images/work-shop/symfony-backoffice-app.png
similarity index 100%
rename from web/landing/assets/images/shop/symfony-backoffice-app.png
rename to web/landing/assets/images/work-shop/symfony-backoffice-app.png
diff --git a/web/landing/assets/images/shop/symfony-backoffice-import.png b/web/landing/assets/images/work-shop/symfony-backoffice-import.png
similarity index 100%
rename from web/landing/assets/images/shop/symfony-backoffice-import.png
rename to web/landing/assets/images/work-shop/symfony-backoffice-import.png
diff --git a/web/landing/assets/images/shop/symfony-backoffice-profiler.png b/web/landing/assets/images/work-shop/symfony-backoffice-profiler.png
similarity index 100%
rename from web/landing/assets/images/shop/symfony-backoffice-profiler.png
rename to web/landing/assets/images/work-shop/symfony-backoffice-profiler.png
diff --git a/web/landing/assets/images/shop/symfony-backoffice-telemetry.png b/web/landing/assets/images/work-shop/symfony-backoffice-telemetry.png
similarity index 100%
rename from web/landing/assets/images/shop/symfony-backoffice-telemetry.png
rename to web/landing/assets/images/work-shop/symfony-backoffice-telemetry.png
diff --git a/web/landing/assets/styles/app.css b/web/landing/assets/styles/app.css
index 5028570499..a814245e93 100644
--- a/web/landing/assets/styles/app.css
+++ b/web/landing/assets/styles/app.css
@@ -1,5 +1,5 @@
@import './changelog.css';
-@import './shop-legal.css';
+@import './work-shop-legal.css';
@tailwind base;
@tailwind components;
diff --git a/web/landing/assets/styles/shop-legal.css b/web/landing/assets/styles/work-shop-legal.css
similarity index 70%
rename from web/landing/assets/styles/shop-legal.css
rename to web/landing/assets/styles/work-shop-legal.css
index c3f8d82a85..fecb74e734 100644
--- a/web/landing/assets/styles/shop-legal.css
+++ b/web/landing/assets/styles/work-shop-legal.css
@@ -1,53 +1,53 @@
-section.shop-legal {
+section.work-shop-legal {
@apply text-slate-700 leading-relaxed dark:text-white/85;
}
-section.shop-legal h1 {
+section.work-shop-legal h1 {
@apply font-bold text-3xl sm:text-4xl tracking-tight text-slate-900 mt-2 mb-6 dark:text-white;
}
-section.shop-legal h2 {
+section.work-shop-legal h2 {
@apply font-bold text-2xl tracking-tight text-slate-900 mt-10 mb-3 dark:text-white;
}
-section.shop-legal h3 {
+section.work-shop-legal h3 {
@apply font-semibold text-xl text-slate-900 mt-7 mb-2 dark:text-white;
}
-section.shop-legal p {
+section.work-shop-legal p {
@apply mb-4;
}
-section.shop-legal a {
+section.work-shop-legal a {
@apply text-blue-300 underline-offset-2 hover:text-orange-300 transition-colors
dark:text-blue-100 dark:hover:text-orange-100;
}
-section.shop-legal ul {
+section.work-shop-legal ul {
@apply list-disc pl-6 mb-4 space-y-1.5;
}
-section.shop-legal ol {
+section.work-shop-legal ol {
@apply list-decimal pl-6 mb-4 space-y-1.5;
}
-section.shop-legal strong {
+section.work-shop-legal strong {
@apply font-semibold text-slate-900 dark:text-white;
}
-section.shop-legal blockquote {
+section.work-shop-legal blockquote {
@apply my-6 rounded-r-lg border-l-4 border-orange-100/70 bg-orange-100/10 px-4 py-3 text-sm muted;
}
-section.shop-legal blockquote p {
+section.work-shop-legal blockquote p {
@apply mb-0;
}
-section.shop-legal hr {
+section.work-shop-legal hr {
@apply my-8 border-0 h-px bg-slate-200 dark:bg-white/10;
}
-section.shop-legal :not(pre) > code {
+section.work-shop-legal :not(pre) > code {
@apply font-mono text-[0.9em] px-1.5 py-0.5 rounded bg-violet-50 text-violet-700
dark:bg-white/[0.06] dark:text-[#d2a8ff];
}
diff --git a/web/landing/config/services.yaml b/web/landing/config/services.yaml
index 936c384b93..9d6e8d2b31 100644
--- a/web/landing/config/services.yaml
+++ b/web/landing/config/services.yaml
@@ -11,7 +11,7 @@ parameters:
router.request_context.scheme: '%env(SCHEME)%'
flow_version: '%env(FLOW_VERSION)%'
turnstile_appearance_default: 'interaction-only'
- shop_checkout_links:
+ work_shop_checkout_links:
symfony_backoffice: 'https://buy.polar.sh/polar_cl_KAb6EWLVtewQhsDZV2bGeqsxSxTWKOOvl1KAq4GbDlx'
services:
@@ -27,9 +27,9 @@ services:
Flow\Website\Service\:
resource: '../src/Flow/Website/Service/'
- Flow\Website\Controller\ShopController:
+ Flow\Website\Controller\WorkShopController:
arguments:
- $checkoutLinks: '%shop_checkout_links%'
+ $checkoutLinks: '%work_shop_checkout_links%'
Flow\Website\Factory\Github\ContributorsRequestFactory:
arguments:
diff --git a/web/landing/content/shop/privacy-policy.md b/web/landing/content/work-shop/privacy-policy.md
similarity index 98%
rename from web/landing/content/shop/privacy-policy.md
rename to web/landing/content/work-shop/privacy-policy.md
index c325e35931..fba5c83ec5 100644
--- a/web/landing/content/shop/privacy-policy.md
+++ b/web/landing/content/work-shop/privacy-policy.md
@@ -155,4 +155,3 @@ address, and material changes are reflected by updating the date above.
## 13. Contact
For any privacy question or request, contact us at support@flow-php.com.
-Our Terms of Sale are available at [Terms of Sale](/work-shop/terms-of-sales).
diff --git a/web/landing/content/shop/terms-of-sales.md b/web/landing/content/work-shop/terms-of-sales.md
similarity index 99%
rename from web/landing/content/shop/terms-of-sales.md
rename to web/landing/content/work-shop/terms-of-sales.md
index 9ee04d8c30..8a15dc2df6 100644
--- a/web/landing/content/shop/terms-of-sales.md
+++ b/web/landing/content/work-shop/terms-of-sales.md
@@ -161,7 +161,7 @@ arrangements and invoicing apply.
We process personal data needed to deliver access and provide support (such as
your email and GitHub account). Polar processes data needed for payment as
Merchant of Record. For details on how we handle personal data, see our
-[Privacy Policy](/work-shop/privacy-policy). Where we list purchasers as project
+Privacy Policy. Where we list purchasers as project
sponsors, we do so only for those who explicitly opt in, and only the GitHub
profile they choose to display.
diff --git a/web/landing/src/Flow/Website/Controller/ShopController.php b/web/landing/src/Flow/Website/Controller/ShopController.php
deleted file mode 100644
index c3290f5958..0000000000
--- a/web/landing/src/Flow/Website/Controller/ShopController.php
+++ /dev/null
@@ -1,76 +0,0 @@
- $checkoutLinks
- */
- public function __construct(
- private readonly array $checkoutLinks,
- ) {}
-
- #[Route('/work-shop', name: 'shop', options: ['sitemap' => false])]
- public function index(): Response
- {
- return $this->render('shop/index.html.twig');
- }
-
- #[Route('/work-shop/blueprints/symfony-backoffice', name: 'shop_blueprint_symfony', options: ['sitemap' => false])]
- public function blueprintSymfony(): Response
- {
- return $this->render('shop/blueprints/symfony-backoffice/index.html.twig', [
- 'listing_checkout_url' => $this->checkoutLinks['symfony_backoffice'] ?? null,
- ]);
- }
-
- #[Route('/work-shop/blueprints/how-it-works', name: 'shop_blueprint_how_it_works', options: ['sitemap' => false])]
- public function blueprintHowItWorks(): Response
- {
- return $this->render('shop/blueprints/how-it-works/index.html.twig', [
- 'listing_checkout_url' => $this->checkoutLinks['symfony_backoffice'] ?? null,
- ]);
- }
-
- #[Route('/work-shop/success', name: 'shop_success', options: ['sitemap' => false])]
- public function success(): Response
- {
- return $this->render('shop/success/index.html.twig');
- }
-
- #[Route('/work-shop/consulting', name: 'shop_consulting', options: ['sitemap' => false])]
- public function consulting(): Response
- {
- return $this->render('shop/consulting/index.html.twig');
- }
-
- #[Route('/work-shop/terms-of-sales', name: 'shop_terms_of_sales', options: ['sitemap' => false])]
- public function termsOfSales(): Response
- {
- return $this->render('shop/terms-of-sales/index.html.twig', [
- 'terms_markdown' => file_get_contents(
- type_string()->assert($this->getParameter('kernel.project_dir')) . '/content/shop/terms-of-sales.md',
- ),
- ]);
- }
-
- #[Route('/work-shop/privacy-policy', name: 'shop_privacy_policy', options: ['sitemap' => false])]
- public function privacyPolicy(): Response
- {
- return $this->render('shop/privacy-policy/index.html.twig', [
- 'privacy_markdown' => file_get_contents(
- type_string()->assert($this->getParameter('kernel.project_dir')) . '/content/shop/privacy-policy.md',
- ),
- ]);
- }
-}
diff --git a/web/landing/src/Flow/Website/Controller/WorkShopController.php b/web/landing/src/Flow/Website/Controller/WorkShopController.php
new file mode 100644
index 0000000000..867414a1f6
--- /dev/null
+++ b/web/landing/src/Flow/Website/Controller/WorkShopController.php
@@ -0,0 +1,82 @@
+ $checkoutLinks
+ */
+ public function __construct(
+ private readonly array $checkoutLinks,
+ ) {}
+
+ #[Route('/work-shop', name: 'work_shop', options: ['sitemap' => false])]
+ public function index(): Response
+ {
+ return $this->render('work-shop/index.html.twig');
+ }
+
+ #[Route('/work-shop/blueprints/symfony-backoffice', name: 'work_shop_blueprint_symfony', options: [
+ 'sitemap' => false,
+ ])]
+ public function blueprintSymfony(): Response
+ {
+ return $this->render('work-shop/blueprints/symfony-backoffice/index.html.twig', [
+ 'listing_checkout_url' => $this->checkoutLinks['symfony_backoffice'] ?? null,
+ ]);
+ }
+
+ #[Route('/work-shop/blueprints/how-it-works', name: 'work_shop_blueprint_how_it_works', options: [
+ 'sitemap' => false,
+ ])]
+ public function blueprintHowItWorks(): Response
+ {
+ return $this->render('work-shop/blueprints/how-it-works/index.html.twig', [
+ 'listing_checkout_url' => $this->checkoutLinks['symfony_backoffice'] ?? null,
+ ]);
+ }
+
+ #[Route('/work-shop/success', name: 'work_shop_success', options: ['sitemap' => false])]
+ public function success(): Response
+ {
+ return $this->render('work-shop/success/index.html.twig');
+ }
+
+ #[Route('/work-shop/consulting', name: 'work_shop_consulting', options: ['sitemap' => false])]
+ public function consulting(): Response
+ {
+ return $this->render('work-shop/consulting/index.html.twig');
+ }
+
+ #[Route('/work-shop/terms-of-sales', name: 'work_shop_terms_of_sales', options: ['sitemap' => false])]
+ public function termsOfSales(): Response
+ {
+ return $this->render('work-shop/terms-of-sales/index.html.twig', [
+ 'terms_markdown' => file_get_contents(
+ type_string()->assert($this->getParameter('kernel.project_dir'))
+ . '/content/work-shop/terms-of-sales.md',
+ ),
+ ]);
+ }
+
+ #[Route('/work-shop/privacy-policy', name: 'work_shop_privacy_policy', options: ['sitemap' => false])]
+ public function privacyPolicy(): Response
+ {
+ return $this->render('work-shop/privacy-policy/index.html.twig', [
+ 'privacy_markdown' => file_get_contents(
+ type_string()->assert($this->getParameter('kernel.project_dir'))
+ . '/content/work-shop/privacy-policy.md',
+ ),
+ ]);
+ }
+}
diff --git a/web/landing/templates/main/sponsor.html.twig b/web/landing/templates/main/sponsor.html.twig
index 809618e90a..557293bcfb 100644
--- a/web/landing/templates/main/sponsor.html.twig
+++ b/web/landing/templates/main/sponsor.html.twig
@@ -54,7 +54,7 @@
Hire me for consulting, workshops, or implementation support.
Engagements of 20+ hours include a sponsor logo on this page.
-
Learn more →
diff --git a/web/landing/templates/shop/_listing.html.twig b/web/landing/templates/work-shop/_listing.html.twig
similarity index 85%
rename from web/landing/templates/shop/_listing.html.twig
rename to web/landing/templates/work-shop/_listing.html.twig
index 535ea68cd1..58d127784c 100644
--- a/web/landing/templates/shop/_listing.html.twig
+++ b/web/landing/templates/work-shop/_listing.html.twig
@@ -33,7 +33,7 @@
{% set listing_images = listing_images ?? [] %}
{% set listing_image_aspect = listing_image_aspect ?? '16 / 9' %}