Ves al contingut principal

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

<?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

<?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

  1. Fiabilitat de Dates: L'ús de DateTimeImmutable (en lloc de constants com date("n") o date("Y") i funcions com strtotime()) elimina l'ambigüitat en els càlculs i evita errors per manipulació del mateix objecte (immutabilitat).

  2. 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.

  3. 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.

  4. Separació de Responsabilitats: El fitxer index.php només s'encarrega de crear l'objecte i mostrar el seu resultat (generaTaulaHTML()), separant la Lògica (la classe) de la Presentació (el HTML).