Pers.narod.ru. PHP. Статьи. Пишем класс на PHP. Класс календаря на PHP

В отношении классов PHP вполне оправдывает свою репутацию языка с низким порогом входимости. С одной стороны, это не позволит Вам сполна наслаждаться C++-подобными заумностями вроде "деструктор абстрактного класса должен быть всегда виртуальным", с другой стороны, классы на PHP просто удобны и хорошо решают свои задачи:

По-моему, хороший класс должен хорошо описывать моделируемый объект, содержать достаточно понятных настроек и быть удобным в обращении, только и всего.

Возьмём для примера что-нибудь понятное и часто нужное, например, обычный григорианский календарь, и напишем для него класс.

Синтаксис класса на PHP может предельно упрощён. Все свойства и методы класса будут внутри его операторных скобок:

<?php
class calendar {
 //...
}
?>

Свойства (переменные) класса можно перечислить в начале, сразу же задавая им значения по умолчанию. По сути, все эти переменные содержат те или иные настройки календаря, так что сделаем им тип доступа public, то есть, разрешим менять их значения извне. А вот значения текущих дня, месяца и года менять извне не стоит, эти переменные будут иметь тип доступа private (доступ только для функций нашего класса). Есть ещё protected, но он нужен только тогда, когда Вы собираетесь писать классы-наследники, имеющие доступ к свойствам класса-родителя.

private $nday,$nmon,$nyear; //Текущие день, месяц и год - менять извне нельзя!
 public $dayslinecolor='#333333', //Цвет названия дней
  $holidaycolor='red', //Цвет выходных и названий выходных
  $weekdaycolor='#000066', //Цвет обычных дней
  $todaycolor='green', //Цвет сегодняшнего дня
  $maintabcolumns=4, //Сколько столбцов в таблице месяцев (1-12)
  $weekhorizontalorientation=false, //Горизонтальное положение недель (true-вкл.)
  $showform=false, //Показывать ли форму выбора года (true-вкл.)
  $year1=1970, //Год начала списка лет в форме
  $year2=2035, //Год окончания списка лет в форме
  $selectedyear, //Год, выбранный в форме как текущий
  $month1=1, //Первый месяц календаря
  $month2=12, //Последний месяц календаря
  $text = ''; //Будем код календаря писать в эту переменную, так легче потом интегрировать
 public $months=array("Январь","Февраль","Март","Апрель","Май","Июнь",
  "Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"); //Названия месяцев
 public $days=array("Пн","Вт","Ср","Чт","Пт","Сб","Вс"); //Названия дней недели
 public $dayscount=array("31","28","31","30","31","30", 
  "31","31","30","31","30","31"); //Число дней в месяцах
 public $maincellspacing=5, //Расстояние между ячейками месяцев в главной таблице
  $maincellpadding=5, //Отступ от края ячейки месяца в главной таблице
  $monthsize=2, //Размер названий месяцев $months (1-7)
  $daysize=1; //Размер чисел в таблице месяца (1-7)
 public $links=array(); //Список ссылок для вывода в календаре
 public $defaultlinktarget='_self'; //Целевое окно ссылок календаря по умолчанию
  // '_self' - текущее окно, '_blank' - новое окно

Для создания конструктора на PHP нам достаточно включить в класс функцию со специальным именем __construct - она будет вызываться каждый раз, когда мы пишем в коде что-то вроде

$cal = new calendar();

В отличие от других языков программирования, у класса PHP может быть только один конструктор. В чём-то это даже удобно.

Явный конструктор классу на PHP имеет смысл задавать, когда мы собираемся выставить в нём значения приватных свойств или же значения общедоступных свойств, которые не являются константами.

Соответственно, в нашем классе это переменные $nday,$nmon,$nyear и значение $selectedyear, показывающее, какой год выбран в форме лет (по умолчанию - текущий). Обратите внимание, как осуществляется доступ к переменным (да и функциям) класса: $ИмяКласса->ИмяПеременной, именно так, без символа "$", который обычно указывается в начале имени любой переменной, в том числе и переменной класса, при её описании.

function __construct () { //Конструктор
  list($this->nyear,$this->nmon,$this->nday) = explode(".",date('Y.m.d')); 
  $this->selectedyear=$this->nyear;
 }

Служебная функция leapyear ($y) определит, является ли год $y високосным, а функция с заголовком putform ($y1=1970,$y2=2035) сформирует и вернёт строкой форму для выбора года календаря с кнопкой "ОК".

Здесь после "=" в заголовке функции указаны значения параметров по умолчанию, так что putform (2011) выдаст годы от 2011 до 2035, putform (2000,2040) - от 2000 до 2040, а просто putform () - от 1970 до 2035.

function leapyear ($y) { //true, если год високосный
  return ($y%4==0) and ($y%100!=0) or ($y%400==0); 
 }

 function putform ($y1=1970,$y2=2035) { //Вывод формы выбора года
  $text='';
  if (empty($_REQUEST['selectedyear'])) $this->selectedyear = $this->nyear; 
  else $this->selectedyear = intval(strip_tags(trim($_REQUEST['selectedyear'])));
  $text.='<div align="center"><form action="'.$_SERVER['PHP_SELF'].
   '" method="POST"><select name="selectedyear" size="1">';
  for ($year=$y1;$year<=$y2;$year++) {
   if ($this->selectedyear == $year) $sel = ' selected'; else $sel = '';
   $text.="<option value=$year $sel>$year";
  }
  $text.='</select> <input type="submit" value="OK"></center></form></div>';
  return $text;
 }

Наконец, напишем функции, непосредственно формирующие календарь. Существуют два основных способа отобразить календарь на месяц - недели выводятся вертикально:

Пример календаря на месяц с вертикальным выводом недели

(примем это по умолчанию) или горизонтально:

Пример календаря на месяц с горизонтальным выводом недели

Переключать способ вывода будет свойство класса $weekhorizontalorientation.

Соответственно, нам понадобятся 2 функции с одинаковыми параметрами -

Функции назовём drawverticalmonth и drawhorizontalmonth, обе они будут просто возвращать строкой сформированный ими HTML-код календаря. Так же поступит и фунцкия putcalendar($m1,$m2), которая сформирует календарь для указанного диапазона месяцев [$m1,$m2]. Если задать только $m1, получится календарь от текущего месяца до $m1 (при необходимости они поменяются местами), если в параметрах не задать ничего - выведется календарь на текущий месяц. В детали реализации вникать не будем, возможно, код неоптимален, так как писался очень быстро.

function drawhorizontalmonth ($firstday,$mon,$year,$daysamount) {
  //Рисует горизонтальный календарь на месяц
  $text= "\n".'<table border="0" align="center"><caption><font size="'.$this->monthsize.'">'.
   $this->months[$mon-1].'</font></caption><tr>';
  for ($i=0;$i<7;$i++) {
   $day=$this->days[$i];
   $text.= '<td align="center"><font size="'.$this->daysize.'" color="'.
    ($i>4?$this->holidaycolor:$this->dayslinecolor).'">'.ucfirst($day).'</font></td>'; 
  }
  $text.= '</tr><tr>';
  for($i=0; $i<$firstday; $i++) $text.= '<td><font size="'.$this->daysize.'">&nbsp;</font></td>';
  for ($q=1; $q<$daysamount; $q++) {
   if ($q==$this->nday and $mon==$this->nmon and $year==$this->nyear) $color=$this->todaycolor;
   else if ($firstday<5) $color=$this->weekdaycolor; 
   else $color=$this->holidaycolor;
   $firstday++;
  if ($firstday==7) $firstday=0;
   $text.= '<td align="right"><font size="'.$this->daysize.'" color="'.$color.'">'.
    $this->getdaylink($q,$mon,$year).'</font></td>'; 
   if(!strstr(''.($i+$q)/7,'.')) $text.= '</tr><tr>';
  }
  return $text.'</tr></table>';
 }

 function drawverticalmonth ($firstday,$mon,$year,$daysamount) {
  //Рисует вертикальный календарь на месяц
  $wd1=$firstday+1;
  $pn1=($wd1==1? 1 : 0);
  $wd=$wd1;
  $cols=1;
  $maxDay=$daysamount-1;
  $m=$mon;
  for ($i=1; $i<=$maxDay; $i++) {
   $wd++;
   if ($wd>7) {
    if ($pn1==0) $pn1=$i+1;
    $wd=1;
    if ($i<$maxDay) $cols++;
   }
  }
  $text = "\n".'<table border="0" align="center"><caption><font size="'.$this->monthsize.'">'.
   $this->months[$mon-1].'</font></caption>';
  for ($r=1; $r<8; $r++) {
   $wd=($pn1>1 ? $pn1+$r-1 : 7+$r );
   $text.='<tr><td align="center"><font size="'.$this->daysize.'" color="'.
    ($r>5?$this->holidaycolor:$this->dayslinecolor).'">'.ucfirst($this->days[$r-1]).'</font></td>';
   $rr=$r-($wd1-1);
   if ($rr==$this->nday and $m==$this->nmon and $year==$this->nyear) $color=$this->todaycolor;
   else if ($r>5) $color=$this->holidaycolor;
   else $color=$this->weekdaycolor;
   $text.='<td><font size="'.$this->daysize.'" color="'.$color.'">'.
    ($r<$wd1 ? '&nbsp;' : $this->getdaylink($rr,$mon,$year) ).'</font></td>';
   for ($c=1; $c<$cols; $c++) { 
    if ($wd==$this->nday and $m==$this->nmon and $year==$this->nyear) 
     $color=$this->todaycolor;
    else if ($r>5) $color=$this->holidaycolor;
    else $color=$this->weekdaycolor;
    $text.='<td align="right"><font size="'.$this->daysize.'" color="'.$color.'">'.
     ($wd>$maxDay ? '&nbsp;' : $this->getdaylink($wd,$mon,$year)).'</font></td>';
    $wd+=7;
   }
   $text.='</tr>';
  }
  return $text.'</table>';
 }

 function putcalendar ($m1=0,$m2=0) {
  //Возвращает календарь на месяцы [$m1,$m2] года $selectedyear
  if ($m1==0) $m1=$this->nmon;
  if ($m2==0) $m2=$this->nmon;
  if ($m1>$m2) { $a=$m1; $m1=$m2; $m2=$a; }
  $tabcol=$this->maintabcolumns;
  if ($m1==$m2) $tabcol=1;
  $text="\n".'<div align="center"><table border="0" cellspacing="'.$this->maincellspacing.
   '" cellpadding="'.$this->maincellpadding.'"><tr>';
  $rows=0; $cols=0; 
  for ($z=$m1;$z<=$m2;$z++) {
   $mon = $z;
   //Если форма не выводилась - selectedyear ставили в конструкторе
   $year = $this->selectedyear;
   $daysamount=($this->dayscount[$z-1])+1;
   if ($this->leapyear($year) and $z==2) $daysamount++;
   $firstday=date('w',mktime(0,0,0,$mon,0,(int)$year)); //0-6
   if ($mon>$m1 and $cols%$tabcol==0) { 
    $text.= '</tr><tr>'."\n"; 
    $rows++; $cols=0; 
   }
   $text.= '<td align="center" valign="top">';
   $cols++;
   if ($this->weekhorizontalorientation) { 
    //Рисуем месяц, когда недели по горизонтали
    $text.=$this->drawhorizontalmonth ($firstday,$mon,$year,$daysamount);
   }
   else {  //Рисуем месяц, когда недели по вертикали
    $text.=$this->drawverticalmonth ($firstday,$mon,$year,$daysamount);
   }
   $text.= '</td>';
  }
  if ($rows>0 and $cols<$tabcol) { //Дорисовать недостающие ячейки строки
   $voids = $tabcol - $cols;
   for ($i=0; $i<$voids; $i++) 
    $text.='<td><font size="'.$this->daysize.'">&nbsp;</font></td>';
  }
  return $text.'</tr></table></div>';  
 }

Я не предполагаю здесь такой экзотики, как печать месяцев в календаре "по столбцам" или начало недели с воскресенья, как у американцев. При желании всё это, конечно, можно дописать, но это сделает код заметно более громоздким.

А вот сделать, чтобы номера дней можно было выводить с гиперссылками, было бы неплохо, например, это может понадобиться, если будем прикручивать календарь к какой-нибудь ленте новостей.

Здесь напрашиваются 2 метода - addlink будет ставить "ссылку дня", а getdaylink получать её. Конечно, можно предусмотреть ещё методы savelinks и loadlinks, которые будут сохранять и читать все ссылки из файла или базы данных, напишите их сами, если хочется.

function getdaylink ($day,$mon,$year,$target=0) {
  //Получить ранее сохранённую ссылку дня. $target может задавать имя окна (HTML).
  $t = mktime (0,0,0,$mon,$day,(int)$year);
  $tt=' target="';
  if ($target) $tt.=$target;
  else $tt.=$this->defaultlinktarget;
  $tt.='"';
  if (array_key_exists((int)$t,$this->links)) return '<a href="'.$this->links[$t].'"'.$tt.'>'.$day.'</a>';
  else return ''.$day;
 }

 function addlink ($day,$mon,$year,$url) {
  //Сохранить ссылку $url для указанной даты
  $t = mktime (0,0,0,$mon,$day,(int)$year);
  $this->links[(int)$t] = $url;
 }

Наконец, метод show просто покажет форму (при включённом свойстве $showform) и календарь. При вызове метода с параметром false он будет возвращать строку календаря, но не выводить её.

function show ($show=true) { //Показать форму и календарь
  //Если нужно только вернуть всё строкой - вызвать метод с параметром false
  if ($this->showform) {
   $this->text.=$this->putform ($this->year1,$this->year2);
  }
  $this->text.=$this->putcalendar ($this->month1,$this->month2);
  if ($show) echo $this->text;
  return $this->text;
 }

Полную проверку корректности данных класс не делает, будем исходить из того, что использующий его понимает, что в году 12 месяцев :) Приведём пример использования класса:

<?php
 require_once ("calendar.php");
 $cal = new calendar();

 $cal->showform=true;
 list($nyear,$nmon,$nday) = explode(".",date('Y.m.d')); 
 $cal->addlink (1,$nmon,$nyear,'http://www.ya.ru');
 $cal->defaultlinktarget='_blank';
 //$cal->maintabcolumns=1;
 $cal->show();
?>

Здесь включается показ формы, добавляется ссылка на http://www.ya.ru для первого дня текущего месяца, целевым окном ссылок назначено новое окно.

Раскомментарьте строку

//$cal->maintabcolumns=1;

- и получите календарь в столбец, поставьте там значение 12 - в строку и т.д.

А вот так можно вызывать методы календаря "порознь" (при таком вызове выведется текущий месяц):

echo $cal->putform ();
 echo $cal->putcalendar ();

 Этот пример в работе (прикручен файл form.php для выбора параметров)

 Скачать код этого примера в архиве ZIP (могут быть мелкие изменения в коде) (5 Кб)

Рейтинг@Mail.ru

вверх гостевая; E-mail
Hosted by uCoz