Exemple Calendari. OOP + classe calendari
Un calendari, aplicant l'Orientació a Objectes (OOP) a PHP, utilitzant la classe DateTimeImmutable per a una manipulació de dates més fiable i encapsulant la lògica de generació del calendari en una classe Calendari. Aquesta solució separa la lògica (Calendari.php) de la presentació (index.php) i utilitza iteradors per generar les cel·les sense lògica complexa d'indexació.
Implementació Orientada a Objectes (Calendari.php)
Crearem la classe Calendari que s'encarregarà de tots els càlculs de dates i de generar el HTML del calendari.
<?php
class Calendari
{
/** @var DateTimeImmutable Data del mes que es vol mostrar */
private DateTimeImmutable $dataMes;
/** @var DateTimeImmutable Data del primer dia a iterar (el primer dilluns visible) */
private DateTimeImmutable $dataIniciIteracio;
/** @var DateTimeImmutable Data de l'últim dia a iterar (l'últim diumenge visible) */
private DateTimeImmutable $dataFiIteracio;
/**
* Constructor
* @param DateTimeImmutable|null $dataMes Si és null, s'utilitza la data actual.
*/
public function __construct(?DateTimeImmutable $dataMes = null)
{
// Posicionem la data al primer dia del mes seleccionat (o el mes actual)
$any = $dataMes?->format('Y') ?? date('Y');
$mes = $dataMes?->format('m') ?? date('m');
// Utilitzem createFromFormat per garantir el primer dia
$this->dataMes = DateTimeImmutable::createFromFormat('Y-m-d', "$any-$mes-01");
$this->calcularDatesIteracio();
}
/**
* Calcula la data d'inici (primer dilluns) i fi (últim diumenge)
* de la graella, ajustant-se exactament al mes.
*/
private function calcularDatesIteracio(): void
{
$primerDiaMes = $this->dataMes;
$darrerDiaMes = $this->dataMes->modify('last day of this month');
// 1. Càlcul de la data d'inici de la iteració (el primer dilluns visible)
$diaSetmanaPrimerDia = (int)$primerDiaMes->format('N'); // 1 (Dl) a 7 (Dg)
$diesARestar = $diaSetmanaPrimerDia - 1;
$this->dataIniciIteracio = $primerDiaMes->modify("-$diesARestar days");
// 2. Càlcul de la data de finalització de la iteració (l'últim diumenge visible)
// Busquem el dia de la setmana del darrer dia del mes
$diaSetmanaDarrerDia = (int)$darrerDiaMes->format('N'); // 1 (Dl) a 7 (Dg)
// Necessitem avançar fins al diumenge (dia 7)
// Si el mes acaba un diumenge (7), sumem 0 dies.
// Si el mes acaba un dissabte (6), sumem 1 dia.
$diesASumar = 7 - $diaSetmanaDarrerDia;
// L'últim dia real de la graella és el diumenge de l'última setmana
$this->dataFiIteracio = $darrerDiaMes->modify("+$diesASumar days");
}
/**
* Retorna el títol del calendari (Mes i Any)
*/
public function getTitol(): string
{
// Exemple de formatació: Setembre - 2025
return $this->dataMes->format('F') . ' - ' . $this->dataMes->format('Y');
}
/**
* Mètode principal per generar el HTML del calendari
*/
public function generaTaulaHTML(): string
{
$html = '<table class="calendari-taula" border="1" cellspacing="0" cellpadding="0">';
// Capçalera de dies de la setmana
$html .= '<tr>';
$diesSetmana = ['Dl', 'Dm', 'Dc', 'Dj', 'Dv', 'Ds', 'Dg'];
foreach ($diesSetmana as $dia) {
$html .= "<th>$dia</th>";
}
$html .= '</tr>';
// Cos del calendari
$dataActual = $this->dataIniciIteracio;
$avui = new DateTimeImmutable('today');
$intervalDia = new DateInterval('P1D');
while ($dataActual < $this->dataFiIteracio) {
// Nova fila (<tr>) cada dilluns (N=1)
if ($dataActual->format('N') == 1) {
$html .= '<tr>';
}
// Determinació d'estils
$esDelMesActual = $dataActual->format('m') === $this->dataMes->format('m');
$esAvui = $dataActual->format('Y-m-d') === $avui->format('Y-m-d');
$classe = $esAvui ? 'avui' : ($esDelMesActual ? 'mes-actual' : 'altre-mes');
$html .= "<td class='$classe'>";
$html .= $dataActual->format('j');
$html .= '</td>';
// Tancament de fila (</tr>) cada diumenge (N=7)
if ($dataActual->format('N') == 7) {
$html .= '</tr>';
}
// Avançar al dia següent amb l'interval
$dataActual = $dataActual->add($intervalDia);
}
$html .= '</table>';
return $html;
}
}
Ús de la Classe (index.php)
El fitxer principal es simplifica molt, ja que tota la lògica es troba a la classe Calendari.
<?php
// index.php
require_once 'Calendari.php';
// Opcional: Establir el fus horari per consistència
date_default_timezone_set('Europe/Madrid');
// Per a provar un mes amb menys setmanes (Octubre 2025 comença Dimecres)
// $calendari = new Calendari(new DateTimeImmutable('2025-10-01'));
// Crear l'objecte Calendari per al mes actual (Setembre 2025)
$calendari = new Calendari();
$titol = $calendari->getTitol();
$taula_html = $calendari->generaTaulaHTML();
?>
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<title>Calendari Ajustat: <?php echo $titol; ?></title>
<style>
.calendari-taula {
width: 100%;
max-width: 500px;
border-collapse: collapse;
text-align: center;
}
.calendari-taula th, .calendari-taula td {
padding: 8px;
border: 1px solid #ccc;
}
.calendari-taula .avui {
background-color: #ffe0b2;
font-weight: bold;
}
.calendari-taula .altre-mes {
color: #aaa;
background-color: #f7f7f7;
}
</style>
</head>
<body>
<h3> Calendari Ajustat: <?php echo $titol; ?></h3>
<?php echo $taula_html; ?>
</body>
</html>
Avantatges de l'Orientació a Objectes
-
Fiabilitat de Dates: L'ús de
DateTimeImmutable(en lloc de constants comdate("n")odate("Y")i funcions comstrtotime()) elimina l'ambigüitat en els càlculs i evita errors per manipulació del mateix objecte (immutabilitat). -
Encapsulament: Tota la lògica de càlculs (com saber el primer dilluns, els dies a restar, etc.) està encapsulada dins la classe
Calendari, fent que el codi sigui molt més net i fàcil de mantenir. -
Flexibilitat: El constructor ara pot acceptar qualsevol data, permetent generar calendaris per a qualsevol mes sense haver de modificar la lògica interna del codi.
-
Separació de Responsabilitats: El fitxer
index.phpnomés s'encarrega de crear l'objecte i mostrar el seu resultat (generaTaulaHTML()), separant la Lògica (la classe) de la Presentació (el HTML).