Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
PositionManager
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 2
72
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 renderPosition
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3declare(strict_types=1);
4
5namespace App\Core\Layout;
6
7use PDO;
8use Psr\Container\ContainerInterface;
9use Throwable;
10
11/**
12 * Layout Position and Block Manager.
13 *
14 * Fetches assigned blocks for a specific theme position from database
15 * and securely renders their concatenated HTML output.
16 *
17 * @package App\Core\Layout
18 */
19final readonly class PositionManager
20{
21    /**
22     * PositionManager constructor.
23     *
24     * @param PDO $pdo SQL Database Connection PDO instance.
25     * @param ContainerInterface $container PSR-11 DI Container.
26     * @param string $tablePrefix Database table prefix (e.g. 'a_').
27     */
28    public function __construct(
29        private PDO $pdo,
30        private ContainerInterface $container,
31        private string $tablePrefix = 'a_'
32    ) {
33    }
34
35    /**
36     * Renders all active blocks assigned to a position within a theme.
37     *
38     * @param string $positionCode Position code (e.g. 'sidebar-left', 'header-top').
39     * @param string $themeName Theme name (e.g. 'guest', 'authenticated').
40     * @return string Accumulated rendered HTML string of all blocks.
41     */
42    public function renderPosition(string $positionCode, string $themeName): string
43    {
44        $positionsTable = $this->tablePrefix . 'core_layout_records';
45        $assignmentsTable = $this->tablePrefix . 'core_layout_assignments';
46
47        $sql = "SELECT a.block_class 
48                FROM {$assignmentsTable} a
49                JOIN {$positionsTable} p ON a.position_id = p.id
50                WHERE p.position_code = :posCode 
51                  AND p.theme_name = :themeName 
52                  AND a.is_active = 1
53                ORDER BY a.sort_order ASC";
54
55        $stmt = $this->pdo->prepare($sql);
56        $stmt->execute([
57            ':posCode' => $positionCode,
58            ':themeName' => $themeName,
59        ]);
60
61        $blocks = $stmt->fetchAll(PDO::FETCH_COLUMN);
62        if (empty($blocks)) {
63            return '';
64        }
65
66        $html = '';
67        foreach ($blocks as $blockClass) {
68            if (!is_string($blockClass) || !class_exists($blockClass)) {
69                continue;
70            }
71
72            try {
73                /** @var mixed $block */
74                $block = $this->container->get($blockClass);
75                if ($block instanceof BlockInterface) {
76                    $html .= $block->render();
77                }
78            } catch (Throwable) {
79                // Silently skip broken blocks to prevent page crash
80                continue;
81            }
82        }
83
84        return $html;
85    }
86}