آموزش طراحی سایت خبری با PHP — رایگان و از صفر تا صد

۵۶۸ بازدید
آخرین به‌روزرسانی: ۰۸ مهر ۱۴۰۲
زمان مطالعه: ۳۹ دقیقه
آموزش طراحی سایت خبری با PHP — رایگان و از صفر تا صد

در نگاه نخست عنوان این مطلب به صورت آموزش طراحی سایت خبری با PHP ممکن است برای برنامه‌نویسان تازه‌کار این زبان، کمی ترسناک باشد. اما اجازه بدهید در همین ابتدای مطلب بیان کنیم که این تصور به هیچ وجه صحیح نیست. در این آموزش با روش ساخت برخی مقدماتی یک سیستم مدیریت محتوای خبری آشنا می‌شویم گه در عین سادگی همه کارکردهای ضروری چنین سیستمی را دارد و شما می‌توانید در طی مدت کوتاهی آن را بسازید.

فهرست مطالب این نوشته

در این مسیر با شیوه ساخت پایگاه‌های داده و جداول MySQL نیز آشنا خواهیم شد. همچنین طرز کار پروژه‌های PHP، ثابت‌ها، تعاریف include، سِشِن‌ها و دیگر قابلیت‌های این زبان برنامه‌نویسی را می‌آموزیم. در ادامه با شیوه جداسازی منطق کسب و کار از لایه ارائه آشنا می‌شویم تا کد PHP امن‌تر شود. اما این‌ها همه مواردی نیست که در این آموزش یاد می‌گیرید، بلکه چیزهای زیاد دیگری وجود دارند که پیشنهاد می‌کنیم با ادامه مطالعه این مطلب آن‌ها را شخصاً بررسی کنید.

برای این که مراحل این راهنمای طراحی سایت خبری با PHP را گام به گام طی کنید، باید وب‌سرور آپاچی و PHP روی سیستم شما نصب باشد. همچنین باید سرور پایگاه داده MySQL روی رایانه اجرا شده باشد. توضیح شیوه تنظیم کردن همه این موارد، فراتر از موضوع این مطلب است، اما اگر بخواهید همه این کارها را به روش آسان‌تری انجام دهید، می‌توانید XAMPP (+) را روی سیستم خود نصب کنید.

فهرست قابلیت‌های سایت خبری

نخستین کاری که برای طراحی سایت خبری با PHP یا هر سایت دیگری باید انجام دهیم، این است که فهرستی از کارهایی که وب‌سایت انجام خواهد داد تهیه کنیم. این CMS قابلیت‌های زیر را دارد:

فرانت‌اند

  • صفحه اصلی که شامل پنج مقاله جدید است.
  • صفحه فهرست مقالات که فهرستی از همه مقالات را نمایش می‌دهد.
  • صفحه «نمایش مقاله» (view article) که به بازدیدکننده‌های وب‌سایت امکان مطالعه یک مقاله منفرد را می‌دهد.

بک‌اند

  • صفحه ورود/خروج ادمین
  • لیست کردن همه مقالات
  • افزودن یک مقاله جدید
  • ویرایش یک مقاله موجود
  • حذف یک مقاله موجود
  • هر مقاله یک عنوان، خلاصه مقاله و تاریخ انتشار دارد.

گام اول: ساخت دیتابیس

طراحی سایت خبری با PHP

دومین کاری که برای طراحی سایت خبری با PHP باید انجام دهیم، ساخت یک پایگاه داده MySQL برای ذخیره‌سازی محتوا است. این کار به روش زیر انجام می‌شود.

اجرای برنامه کلاینت mysql

یک پنجره ترمینال را باز کرده و دستور زیر را در آن وارد نمایید:

mysql -u username -p

در ادامه آموزش طراحی سایت خبری با PHP زمانی که از شما خواسته می‌شود، رمز عبور دیتابیس را وارد کنید. توجه کنید که باید کاربری باشد که مجوز ساخت دیتابیس را داشته باشد. اگر روی یک سرور توسعه مثلاً روی رایانه شخصی خود کار می‌کنید، می‌توانید از کاربر root به این منظور استفاده کنید تا دیگر مجبور به ساخت یک کاربر جدید نباشید.

ساخت پایگاه داده

در اعلان mysql>‎ دستور زیر را وارد کنید:

create database cms;

سپس اینتر را بزنید.

خروج از برنامه mysql

برای خروج از برنامه کلاینت باید دستور زیر را در اعلان mysql>‎ وارد کنید.

exit

و اینتر را بزنید. اکنون شما یک دیتابیس جدید و خالی ایجاد کرده‌اید که جداول و محتوای سایت خبری درون آن قرار خواهد گرفت.

برخی وب‌سرورها امکان ساخت پایگاه داده را از طریق ابزارهای مبتنی بر وب مانند cPanel یا Plesk فراهم ساخته‌اند. برخی اوقات این تنها روش ممکن برای ساخت دیتابیس است. اگر در این خصوص مطمئن نیستید، از تیم پشتیبانی هاستینگ خود بپرسید.

  • پیش از ادامه مطلب لازم است اشاره کنیم که مطالعه این مطلب از مجله فرادرس می‌تواند به شما درک بهتری از نحوه اتصال پایگاه داده MySQL و PHP بدهد: اتصال PHP به MySQL — به زبان ساده

گام دوم: ساخت جدول articles در پایگاه داده

در گام دوم طراحی سایت خبری با PHP باید اشاره کنیم سایت خبری که می‌خواهیم طراحی کنیم، تنها یک جدول به نام articles دارد. این جدول چنان که از نامش پیدا است همه مقالات را در سیستم نگه‌داری می‌کند. در ادامه باید یک اسکیما برای این جدول ایجاد کنیم. اسکیمای جدول دیتابیس نوع داده‌هایی که در آن جدول ذخیره می‌شوند و همچنین برخی اطلاعات دیگر در مورد جدول را مشخص می‌کند.

یک فایل متنی به نام tables.sql جایی روی هارد درایو ایجاد کنید و کد زیر را در آن ذخیره کنید:

1DROP TABLE IF EXISTS articles;
2CREATE TABLE articles
3(
4  id              smallint unsigned NOT NULL auto_increment,
5  publicationDate date NOT NULL,                              # When the article was published
6  title           varchar(255) NOT NULL,                      # Full title of the article
7  summary         text NOT NULL,                              # A short summary of the article
8  content         mediumtext NOT NULL,                        # The HTML content of the article
9
10  PRIMARY KEY     (id)
11);

کد فوق اسکیمای جدول articles را تعریف می‌کند. این کد به زبان SQL نوشته شده که برای ساخت و دستکاری پایگاه‌های داده در MySQL و بسیاری دیگر از سیستم‌های پایگاه داده استفاده می‌شود. در ادامه بخش‌های مختلف کد اسکیمای جدول مقالات را به تفصیل توضیح می‌دهیم.

ساخت جدول articles

دستور DROP TABLE IF EXISTS articles در صورت وجود جدول articles از قبل آن را حذف می‌کند. این دستور داده‌های موجود قبلی را نیز حذف می‌کند و از این رو باید هوشیار باشید. دلیل این امر آن است که امکان تعریف یک جدول با نام یکسان در یک جدول موجود وجود ندارد.

دستور () CREATE TABLE articles جدول جدید articles را می‌سازد. موارد درون پرانتز ساختار داده‌های درون جدول را توصیف می‌کنند که در ادامه توضیح خواهیم داد.

اختصاص یک شناسه یکتا به هر مقاله

در این مرحله از طراحی سایت خبری با PHP آماده تعریف ساختار جدول هستیم. هر جدول دیتابیس شامل تعدادی فیلد (که ستون نیز نامیده می‌شوند) است. هر فیلد نوع خاصی از اطلاعات را در مورد مقالات نگهداری می‌کند.

ابتدا فیلد id را ایجاد می‌کنیم. این فیلد دارای نوع داده smallint unsigned است، یعنی اعدادی بین 0 تا 65536 را نگهداری می‌کند. به این ترتیب سایت خبری ما می‌تواند 65535 مقاله را در خود جای دهد. همچنین خصوصیت NOT NULL تعیین شده که موجب می‌شود این فیلد نتواند مقدار خالی (تهی) داشته باشد. به این ترتیب از بروز بسیاری مشکل‌ها جلوگیری می‌کنیم. همچنین خصوصیت auto_increment را به این فیلد شناسه داده‌ایم که به MySQL اعلام می‌کند در زمان ایجاد یک رکورد مقاله جدید یک شناسه تازه و منحصر به فرد به آن اختصاص دهد. بنابراین نخستین مقاله ما دارای شناسه 1 خواهد بود، دومی دارای شناسه 2 و همین طور تا آخر به ترتیب شناسه مقالات افزایش می‌یابد. ما از این مقدار منحصر به فرد به عنوان یک دستگیره برای اشاره به مقاله‌ای استفاده می‌کنیم که می‌خواهیم در CMS نمایش دهیم یا آن را ویرایش یا حذف کنیم.

افزودن فیلد publicationDate

در خط بعدی فایل فوق فیلد publicationDate را تعریف می‌کنیم که تاریخ انتشار مقاله را ذخیره می‌کند. این فیلد نوع داده data دارد، یعنی مقادیر تاریخ را در خود ذخیره می‌کند.

افزودن فیلد title

در ادامه فیلد title را ایجاد می‌کنیم که عنوان مقالات را در خود جای می‌دهد. نوع داده این فیلد varchar(255) است یعنی می‌تواند رشته‌ای به طور 255 کاراکتر را در خود ذخیره سازد.

افزودن فیلد‌های summary و content

دو فیلد آخر که در اسکیمای جدول اشاره شده، فیلد‌های summary و content هستند که به ترتیب خلاصه کوتاهی از مقاله و محتوای اصلی HTML آن را ذخیره می‌کنند. فیلد summary دارای نوع داده text است که می‌تواند 65535 کاراکتر متنی را در خود جای دهد و فیلد content دارای نوع داده mediumtext است که تا حداکثر 16777215 کاراکتر را در خود ذخیره می‌سازد.

افزودن کلید اصلی

در آخرین خط اسکیمای فوق گزاره CREATE TABLE را می‌بینیم که یک کلید برای مقاله تعریف می‌کند. کلید (key) که اندیس (index) نیز نامیده می‌شود، موجب می‌شود که داده‌ها در جدول سریع‌تر پیدا شوند. این کار به قیمت افزایش اندکی در فضای ذخیره‌سازی جدول انجام می‌یابد.

ما فیلد id جدول را به صورت «کلید اصلی» (PRIMARY KEY) تعریف می‌کنیم. هر جدول تنها یک کلید اصلی می‌تواند داشته باشد. این همان کلیدی است که رکورد را به طور منحصر به فرد در جدول تعریف می‌کند. به علاوه با افزودن این کلید، MySQL می‌تواند یک مقاله را بر اساس ID آن به طور یکتا بازیابی نماید.

اکنون که اسکیمای جدول را ایجاد کردیم، باید آن را در MySQL بارگذاری کنیم تا جدول عملاً ایجاد شود. آسان‌ترین روش برای انجام این کار آن است که پنجره ترمینال را باز کنیم و به پوشه شامل فایل tables.sql رفته و دستور زیر را اجرا نماییم:

mysql -u username -p cms < tables.sql

در دستور فوق username نام کاربری دیتابیس مای‌اس‌کیوال است. Cms نام دیتابیس است که در گام اول قبلی ایجاد کردیم. در ادامه از شما رمز عبور خواسته می‌شود که باید وارد کنید. در نهایت MySQL این کد را بارگذاری و اجرا می‌کند و جدول articles درون پایگاه داده cms ایجاد می‌شود.

توجه کنید که امکان ساخت جدول با استفاده از ابزارهای مبتنی بر وب مانند phpMyAdmin نیز وجود دارد. phpMyAdmin به طور پیش‌فرض روی اغلب اکانت‌های میزبانی وب نصب می‌شود و فرایند کار مشابه است.

گام سوم: ساخت فایل پیکربندی

طراحی سایت خبری با PHP

در گام سوم طراحی سایت خبری با PHP حالا که پایگاه داده وب‌سایت خبری خود را ساخته‌ایم، آماده نوشتن کد PHP هستیم. کار خود را با ایجاد فایل پیکربندی برای ذخیره تنظیمات مختلف مفید CMS آغاز می‌کنیم. این فایل از سوی همه فایل‌های اسکریپت در CMS مورد استفاده قرار می‌گیرد.

ابتدا یک پوشه به نام cms‌جایی روی وب‌سایت لوکال روی سیستم ایجاد می‌کنیم تا همه فایل‌های مرتبط با CMS را در آن قرار دهیم. اگر از XAMPP استفاده می‌کنید، وب‌سایت لوکال در پوشه htdocs درون پوشه XAMPP قرار دارد. اما اگر دوست دارید می‌توانید یک وب‌سایت کاملاً جدید صرفاً برای CMS‌خود بسازید و همه فایل‌های مرتبط را در پوشه روت سند وب‌سایت بگذارید.

درون پوشه cms یک فایل به نام config.php ساخته و کد زیر را در آن ذخیره سازید:

1<?php
2ini_set( "display_errors", true );
3date_default_timezone_set( "Australia/Sydney" );  // http://www.php.net/manual/en/timezones.php
4define( "DB_DSN", "mysql:host=localhost;dbname=cms" );
5define( "DB_USERNAME", "username" );
6define( "DB_PASSWORD", "password" );
7define( "CLASS_PATH", "classes" );
8define( "TEMPLATE_PATH", "templates" );
9define( "HOMEPAGE_NUM_ARTICLES", 5 );
10define( "ADMIN_USERNAME", "admin" );
11define( "ADMIN_PASSWORD", "mypass" );
12require( CLASS_PATH . "/Article.php" );
13
14function handleException( $exception ) {
15  echo "Sorry, a problem occurred. Please try later.";
16  error_log( $exception->getMessage() );
17}
18
19set_exception_handler( 'handleException' );
20?>

در ادامه آموزش طراحی سایت خبری با PHP موارد داخل این فایل را به تفصیل توضیح می‌دهیم.

نمایش خطا در مرورگر

خط ()ini_set موجب می‌شود که پیام خطایی در مرورگر نمایش یابد. این امر به منظور دیباگ کردن مفید است، اما روی سایت‌های فعال باید به صورت false تنظیم شود چون می‌تواند موجب بروز ریسک‌های امنیتی شود.

تنظیم منطقه زمانی

از آنجا که CMS ما از تابع ()date در PHP بهره می‌گیرد، باید به منطقه زمانی سرور خود را به آن اعلام کنیم.

تنظیم جزییات دسترسی به پایگاه داده

در ادامه یک ثابت به نام DB_DSN تعریف می‌کنیم که محل یافتن دیتابیس MySQL را به PHP اعلام می‌کند. مطمئن شوید که پارامتر dbname با نام پایگاه داده CMS تطبیق دارد. همچنین نام کاربری و رمز عبور مای‌اس‌کیو‌ال را که برای دسترسی به دیتابیس استفاده می‌شود، به ترتیب در ثوابت DB_USERNAME و DB_PASSWORD مشخص می‌سازیم.

تعیین مسیرها

ما دو نام مسیر در فایل پیکربندی خود داریم. یکی مسیر CLASS_PATH است که مسیر فایل‌های کلاس را نشان می‌دهد و دیگری TEMPLATE_PATH است که مسیری را نشان می‌دهد که اسکریپت باید به دنبال فایل‌های قالب تمپلیت بگردد. هر دوی این مسیرها نسبت به پوشه سطح بالای cms تعریف می‌شوند.

تعیین تعداد مقالاتی که باید در صفحه اصلی نمایش یابد

ثابت HOMEPAGE_NUM_ARTICLES تعداد بیشینه عناوین مقالاتی که باید در صفحه اصلی سایت نمایش یابد را مشخص می‌سازد. ما در ابتدا این مقدار را روی عدد 5 تنظیم کرده‌ایم. اگر شما می‌خواهید آن را تغییر دهید، کافی است عدد را کاهش یا افزایش دهید.

تعیین نام کاربری و رمز عبور ادمین

ثابت‌های ADMIN_USERNAME و ADMIN_PASSWORD شامل جزییات لاگین برای کاربر مدیر CMS هستند. در این مورد نیز می‌توانید مقادیر مورد نظر خود را جایگزین کنید.

گنجایش کلاس Article

از آنجا که فایل کلاس Article (که در ادامه ایجاد خواهیم کرد) توسط همه اسکریپت‌ها در اپلیکیشن مورد نیاز است، ‌آن را در اینجا include می‌کنیم.

ساخت دستگیره استثنا

در نهایت تابع ()handleException را تعریف می‌کنیم که هر استثنای PHP که ممکن است در کد ما رخ دهد را مدیریت می‌کند. این تابع یک پیام خطای کلی نمایش داده و پیامک استثنای واقعی را در لاگ خطاهای وب‌سرور ثبت می‌کند. به طور خاص این تابع با مدیریت استثناهای PDO موجب افزایش امنیت می‌شود، چون این استثناها اگر مدیریت نشوند ممکن است نام کاربری و رمز عبور دیتابیس را روی صفحه نمایش دهند. زمانی که تابع ()handleException را تعریف کردیم، آن را با فراخوانی تابع ()set_exception_handler در PHP تنظیم می‌کنیم.

توجه کنید که دستگیره استثنا یک میانبر سریع و نامناسب برای ساده نگه داشتن این راهنما است. روش صحیح برای مدیریت استثنا این است که همه فراخوانی‌های PDO را درون بلوک‌های try…catch در Article.php قرار دهیم.

نکته امنیتی

در یک محیط سرور زنده بهتر است فایل config.php را جایی خارج از ریشه سند وب‌سایت قرار دهیم چون شامل نام کاربری و رمز عبور است. با این که به طور معمول امکان خواندن سورس کد یک فایل PHP از طریق مرورگر وجود ندارد، اما در صورتی که وب‌‌سرور به درستی پیکربندی نشده باشد، ممکن است چنین کاری ممکن شود.

همچنین بهتر است از تابع ()hash برای هش کردن رمز عبور ادمین استفاده کنید و هش رمز را به جای متن ساده در فایل config.php ذخیره کنید. در این حالت در زمان لاگین می‌توانید رمز عبور دریافتی را هش کرده و آن را با هش موجود در این فایل تطبیق دهید.

گام چهارم: ساخت کلاس Article

در گام چهارم آموزش طراحی سایت خبری با PHP شما اکنون آماده ساخت کلاس PHP به نام Article هستید. این تنها کلاس پروژه ما است و وظیفه مرتب‌سازی مقالات موجود و همچنین بازیابی آن‌ها از دیتابیس را بر عهده دارد. زمانی که این کلاس ساخته شد، اسکریپت‌های دیگر CMS می‌توانند به آسانی اقدام به ساخت، به‌روزرسانی، بازیابی و حذف مقالات بکنند.

درون پوشه cms به نام classes ایجاد کنید. درون این پوشه classes یک فایل به نام Article.php بسازید و کد زیر را در آن قرار دهید:

1<?php
2
3/**
4 * Class to handle articles
5 */
6
7class Article
8{
9
10  // Properties
11
12  /**
13  * @var int The article ID from the database
14  */
15  public $id = null;
16
17  /**
18  * @var int When the article was published
19  */
20  public $publicationDate = null;
21
22  /**
23  * @var string Full title of the article
24  */
25  public $title = null;
26
27  /**
28  * @var string A short summary of the article
29  */
30  public $summary = null;
31
32  /**
33  * @var string The HTML content of the article
34  */
35  public $content = null;
36
37
38  /**
39  * Sets the object's properties using the values in the supplied array
40  *
41  * @param assoc The property values
42  */
43
44  public function __construct( $data=array() ) {
45    if ( isset( $data['id'] ) ) $this->id = (int) $data['id'];
46    if ( isset( $data['publicationDate'] ) ) $this->publicationDate = (int) $data['publicationDate'];
47    if ( isset( $data['title'] ) ) $this->title = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data['title'] );
48    if ( isset( $data['summary'] ) ) $this->summary = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data['summary'] );
49    if ( isset( $data['content'] ) ) $this->content = $data['content'];
50  }
51
52
53  /**
54  * Sets the object's properties using the edit form post values in the supplied array
55  *
56  * @param assoc The form post values
57  */
58
59  public function storeFormValues ( $params ) {
60
61    // Store all the parameters
62    $this->__construct( $params );
63
64    // Parse and store the publication date
65    if ( isset($params['publicationDate']) ) {
66      $publicationDate = explode ( '-', $params['publicationDate'] );
67
68      if ( count($publicationDate) == 3 ) {
69        list ( $y, $m, $d ) = $publicationDate;
70        $this->publicationDate = mktime ( 0, 0, 0, $m, $d, $y );
71      }
72    }
73  }
74
75
76  /**
77  * Returns an Article object matching the given article ID
78  *
79  * @param int The article ID
80  * @return Article|false The article object, or false if the record was not found or there was a problem
81  */
82
83  public static function getById( $id ) {
84    $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
85    $sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles WHERE id = :id";
86    $st = $conn->prepare( $sql );
87    $st->bindValue( ":id", $id, PDO::PARAM_INT );
88    $st->execute();
89    $row = $st->fetch();
90    $conn = null;
91    if ( $row ) return new Article( $row );
92  }
93
94
95  /**
96  * Returns all (or a range of) Article objects in the DB
97  *
98  * @param int Optional The number of rows to return (default=all)
99  * @return Array|false A two-element array : results => array, a list of Article objects; totalRows => Total number of articles
100  */
101
102  public static function getList( $numRows=1000000 ) {
103    $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
104    $sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles
105            ORDER BY publicationDate DESC LIMIT :numRows";
106
107    $st = $conn->prepare( $sql );
108    $st->bindValue( ":numRows", $numRows, PDO::PARAM_INT );
109    $st->execute();
110    $list = array();
111
112    while ( $row = $st->fetch() ) {
113      $article = new Article( $row );
114      $list[] = $article;
115    }
116
117    // Now get the total number of articles that matched the criteria
118    $sql = "SELECT FOUND_ROWS() AS totalRows";
119    $totalRows = $conn->query( $sql )->fetch();
120    $conn = null;
121    return ( array ( "results" => $list, "totalRows" => $totalRows[0] ) );
122  }
123
124
125  /**
126  * Inserts the current Article object into the database, and sets its ID property.
127  */
128
129  public function insert() {
130
131    // Does the Article object already have an ID?
132    if ( !is_null( $this->id ) ) trigger_error ( "Article::insert(): Attempt to insert an Article object that already has its ID property set (to $this->id).", E_USER_ERROR );
133
134    // Insert the Article
135    $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
136    $sql = "INSERT INTO articles ( publicationDate, title, summary, content ) VALUES ( FROM_UNIXTIME(:publicationDate), :title, :summary, :content )";
137    $st = $conn->prepare ( $sql );
138    $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT );
139    $st->bindValue( ":title", $this->title, PDO::PARAM_STR );
140    $st->bindValue( ":summary", $this->summary, PDO::PARAM_STR );
141    $st->bindValue( ":content", $this->content, PDO::PARAM_STR );
142    $st->execute();
143    $this->id = $conn->lastInsertId();
144    $conn = null;
145  }
146
147
148  /**
149  * Updates the current Article object in the database.
150  */
151
152  public function update() {
153
154    // Does the Article object have an ID?
155    if ( is_null( $this->id ) ) trigger_error ( "Article::update(): Attempt to update an Article object that does not have its ID property set.", E_USER_ERROR );
156   
157    // Update the Article
158    $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
159    $sql = "UPDATE articles SET publicationDate=FROM_UNIXTIME(:publicationDate), title=:title, summary=:summary, content=:content WHERE id = :id";
160    $st = $conn->prepare ( $sql );
161    $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT );
162    $st->bindValue( ":title", $this->title, PDO::PARAM_STR );
163    $st->bindValue( ":summary", $this->summary, PDO::PARAM_STR );
164    $st->bindValue( ":content", $this->content, PDO::PARAM_STR );
165    $st->bindValue( ":id", $this->id, PDO::PARAM_INT );
166    $st->execute();
167    $conn = null;
168  }
169
170
171  /**
172  * Deletes the current Article object from the database.
173  */
174
175  public function delete() {
176
177    // Does the Article object have an ID?
178    if ( is_null( $this->id ) ) trigger_error ( "Article::delete(): Attempt to delete an Article object that does not have its ID property set.", E_USER_ERROR );
179
180    // Delete the Article
181    $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
182    $st = $conn->prepare ( "DELETE FROM articles WHERE id = :id LIMIT 1" );
183    $st->bindValue( ":id", $this->id, PDO::PARAM_INT );
184    $st->execute();
185    $conn = null;
186  }
187
188}
189
190?>

این یک فایل طولانی است، اما زمانی که در ادامه آموزش طراحی سایت خبری با PHP به توضیح جزییات آن بپردازیم، می‌بینید که کاملاً ساده است.

تعریف کلاس و مشخصه‌ها

ابتدا به تعریف کلاس Article با کد زیر می‌پردازیم.

1class Article
2{

هر چیزی پس از این خطوط کد تا زمان بسته شدن آکولاد موجب ساخت کلاس Article می‌شود. پس از شروع تعریف کلاس، مشخصه‌های کلاس یعنی id، $publicationDate$ و غیره را اعلان می‌کنیم. هر شیء Article که ایجاد کنیم داده‌های مقاله را در این مشخصه‌ها ذخیره خواهد کرد. می‌توان دید که نام‌های مشخصه نشانگر نام فیلدها در جدول دیتابیس articles هستند.

از نظر فنی، نوع کلاس که شامل مشخصه‌هایی است که مستقیماً با فیلدهای دیتابیس متناظر هستند و همچنین متدهایی برای ذخیره‌سازی یا بازیابی از دیتابیس را شامل می‌شود، از یک الگوی طراحی شیء‌گرایی پیگیری می‌کند.

سازنده

در ادامه متدهای کلاس را ایجاد می‌کنیم. متدها به توابعی گفته می‌شوند که به کلاس مرتبط هستند و همچنین اشیایی هستند که از روی کلاس ساخته می‌شوند. کد اصلی ما می‌تواند این متدها را برای دستگیری داده‌ها در شی‌ءهای Article فراخوانی کند.

متد نخست یعنی ()construct__ سازنده است. سازنده یک متد خاص است که هر زمان یک شیء جدید Article ایجاد می‌شود، به طور خودکار توسط موتور PHP فراخوانی می‌شود. سازنده ما یک آرایه اختیاری data$ می‌گیرد که شامل داده‌هایی است که باید در مشخصه‌هایشی قرار گیرند. در ادامه این مشخصه‌ها را درون بدنه سازنده مقداردهی می‌کنیم. به این ترتیب روش مفیدی برای ایجاد و مقداردهی یک شیء در یک حرکت خواهیم داشت.

توجه کنید که دستور this->propertyName$ به این معنی است که این شیء دارای مشخصه‌ای به نام propertyName$ است. همچنین توجه داشته باشید که متد پیش از آن که داده‌ها را در مشخصه‌ها ذخیره کند، آن‌ها را فیلتر می‌کند. مشخصه‌های id و publicationDate با استفاده از (int) به نوع عدد صحیح تبدیل می‌شوند، زیرا این مقادیر باید همواره integer باشند. مشخصه‌های title و summary با استفاده از «عبارت‌های منظم» (regular expression) فیلتر می‌شوند تا تنها بازه مشخصی از کاراکترها را بتوان وارد کرد. فیلتر کردن داده‌های ورودی یک رویه امنیتی مناسب است زیرا تنها به داده‌هایی اجازه ورود به دیتابیس می‌دهد که مجاز باشند.

اما از سوی دیگر نمی‌توانیم مشخصه content را زیاد فیلتر کنیم، زیرا می‌خواهیم از بازه گسترده‌ای از کاراکترها و احتمالاً از مارکاپ HTML در محتوای مقاله بهره بگیریم. اگر محدوده کاراکترهای مجاز را در این مشخصه کوچک کنیم، میزان مفید بودن CMS کاهش می‌یابد.

به طور معمول این نقیصه که اشاره کردیم، می‌تواند یک نقصی امنیتی باشد، زیرا یک کاربر می‌تواند کدهای مخرب جاوا اسکریپت و موارد دیگر را در محتوا قرار دهد. با این حال از آنجا که به مدیر وب‌سایت خبری که تنها فرد مجاز به درج محتوا در آن است اعتماد داریم، در این مورد خاص می‌توانیم این ریسک را قبول کنیم. اگر قصد دارید از محتواهای تولید شده کاربران مانند نظرات یا فرم‌ها بهره بگیرید، باید احتیاط بیشتری به خرج داده و تنها به کدهای HTML امن امکان ارسال بدهید. یک ابزار بسیار مناسب به این منظور وب‌سایت HTML Purifier (+) است که ورودی HTML را به طور کامل تحلیل کرده و همه کدهای مخرب را حذف می‌کند.

توجه کنید که امنیت PHP مبحث گسترده‌ای است و فراتر از موضوع این نوشتار با موضوع طراحی سایت خبری با PHP می‌رود، بنابراین به همین مقدار بسنده می‌کنیم.

متد ()storeFormValues

متد بعدی یعنی ()storeFormValues مشابه سازنده است چون یک آرایه از داده‌های ارائه شده را در مشخصه‌های شیء ذخیره می‌کند. تفاوت اصلی در این است که متد ()storeFormValues می‌تواند داده‌ها را در فرمتی مدیریت کند که از طریق فرم‌های New Article و Edit Article عرضه شده است. این موارد را در ادامه بیشتر توضیح می‌دهیم. به طور خاص این متد می‌تواند تاریخ‌های انتشار را در قالب YYYY-MM-DD مدیریت کند و این تاریخ را به صورت فرمت UNIX timestamp درآورد که برای ذخیره‌سازی در شیء مناسب است.

توجه کنید که فرمت UNIX timestamp به صورت مقادیر صحیحی است که تعداد ثانیه‌های بین نیمه شب اول ژانویه 1970 تا لحظه کنونی را نشان می‌دهد. در PHP بهتر است که از UNIX timestamp برای کار با تاریخ و زمان استفاده کنیم، چون ذخیره‌سازی و دستکاری آن آسان است.

هدف این متد صرفاً آن است که اسکریپت‌های مدیریتی به طرز آسانی بتوانند داده‌های ارائه شده از سوی فرم‌ها را ذخیره سازند. در این حالت آن‌ها صرفاً باید متد ()storeFormValues را فراخوانی کنند و آرایه‌ای از داده‌های فرم را به آن بفرستند.

همه اعضای کلاس Article که شامل مشخصه‌ها و متدها می‌شود، دارای یک کلیدواژه public قبل از خود هستند. این بدان معنی است که این کدها در دسترس بخش‌های خارج از کلاس نیز قرار دارند. امکان ایجاد اعضای خصوصی نیز وجود دارد که تنها از داخل کلاس قابل دسترسی باشند. همچنین می‌توانید از مشخصه‌ها و متدهای protected استفاده کنید که صرفاً از داخل کلاس و زیرکلاس‌های آن قابل دسترسی هستند.

متد ()getById

اکنون در این بخش از آموزش طراحی سایت خبری با PHP نوبت به متدهایی رسیده است که در عمل به پایگاه داده دسترسی پیدا می‌کنند. نخستین متد ()getById نام دارد که آرگومانی که شکل شناسه مقاله می‌گیرد (id$) و سپس رکورد مقاله را از روی ID مربوطه از جدول articles دیتابیس بازیابی و در شیء جدیدی از کلاس Article ذخیره می‌کند.

به طور معمول، هنگامی که یک متد را فراخوانی می‌کنید، ابتدا یک شیء را ایجاد یا بازیابی می‌کنید، سپس متد را روی آن شیء فرا می‌خوانید. با این حال، از آنجا که این متد یک شیء Article جدید بازگشت می‌دهد، بهتر است که آن را به وسیله کد فراخوانی کننده خودمان و نه از طریق یک شیء موجود فراخوانی کنیم. در غیر این صورت باید هر بار ابتدا یک شیء ساختگی جدید ایجاد کرده و متد را از طریق آن برای بازیابی یک مقاله از دیتابیس فراخوانی کنیم.

برای این که به متد خود امکان فراخوانی شدن بدون نیاز به ایجاد یک شیء بدهیم، کلیدواژه static را به تعریف متد اضافه می‌کنیم. به این ترتیب متد می‌تواند مستقیماً و بدون نیاز به تعیین یک شیء فراخوانی شود.

1public static function getById($id) {

خود متد از PDO برای اتصال به پایگاه داده، بازیابی رکورد ماله با استفاده از گزاره اس‌کیوال SELECT و ذخیره داده‌ها در شیء جدید Article استفاده می‌کند که در ادامه به کد فراخوانی کننده بازگشت می‌یابد.

PDO اختصاری برای عبارت «اشیای داده‌ای پی‌اچ‌پی» (PHP Data Objects) و یک کتابخانه شیء‌گرای است که بر مبنای PHP ساخته شده تا به آسانی بتوان اسکریپت‌های برای تعامل با پایگاه‌های داده ایجاد کرد.

در ادامه آموزش طراحی سایت خبری با PHP اجزای این متد را توضیح می‌دهیم.

اتصال به پایگاه داده

1$conn = new PDO(DB_DSN, DB_USERNAME, DB_PASSWORD);

دستور فوق با بهره‌گیری از جزییات لاگین که در فایل config.php قرار دارد، یک اتصال به دیتابیس MySQL ایجاد و دستگیره اتصال حاصل را در متغیر conn$ ذخیره می‌کند. این دستگیره از سوی باقی بخش‌های کد درون متد برای تعامل با پایگاه داده مورد استفاده قرار می‌گیرد.

بازیابی رکورد مقاله

1 $sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles WHERE id = :id";
2 $st = $conn->prepare( $sql );
3 $st->bindValue( ":id", $id, PDO::PARAM_INT );
4 $st->execute();
5 $row = $st->fetch();

گزاره SELECT ما همه فیلدها (*) را از رکوردی که در جدول articles با این شناسه تطبیق یابد، بازیابی می‌کند. همچنین فیلد publicationDate را که دارای فرمت UNIX timestamp به جای فرمت تاریخ پیش‌فرض MySQL است بازیابی می‌کند و از این رو می‌توانیم آن را به سادگی در شیء خود ذخیره سازیم.

به جای قرار این که پارامتر id$ را مستقیماً درون رشته SELECT قرار دهیم که یک ریسک امنیتی محسوب می‌شود، به جای آن از:id استفاده می‌کنیم. این مورد به نام placeholder خوانده می‌شود. در ادامه یک متد PDO برای اتصال مقدار id$ به این placeholder فرا می‌خوانیم.

هنگامی که گزاره SELECT را در یک رشته ذخیره کردیم، گزاره را با فراخوانی کردن ()conn->prepare$ آماده‌سازی می‌کنیم و دستگیره گزاره حاصل را در یک متغیر به نام ()conn->prepare$ قرار می‌دهیم.

گزاره‌های آماده‌سازی شده یکی از قابلیت‌های اغلب پایگاه‌های داده هستند که به دیتابیس امکان می‌دهند تا به روشی سریع‌تر و مطمئن‌تر فراخوانی شود.

اکنون مقدار متغیر id$ خود را اتصال می‌دهیم یعنی ID مقاله‌ای که می‌خواهیم بازیابی کنیم را با فراخوانی متد ()bindValue به id: وصل می‌کنیم. نام placeholder را به این متد ارسال می‌کنیم تا مقدار آن و نوع داده مقدار وصل شود تا PDO بداند که دقیقاً چطور می‌تواند مقدار را escape بکند.

در نهایت متد ()execute را برای اجرای کوئری فرا می‌خوانیم، در ادامه از متد ()fetch برای بازیابی رکورد حاصل به صورت یک آرایه انجمنی از نام‌های فیلد و مقادیر متناظرشان استفاده می‌کنیم که کدر متغیر row$ ذخیره می‌شود.

بستن اتصال

در انتهای این بخش از آموزش طراحی سایت خبری با PHP با دستور زیر اقدام به بستن اتصال دیتابیس می‌کنیم.

1$conn = null;

از آنجا که دیگر نیازی به اتصال نداریم، آن را با انتساب مقدار null به متغیر conn$ می‌بندیم. بستن اتصال دیتابیس به محض پایان یافتن کار ایده خوبی است، زیرا به آزاد شدن حافظه سرور کمک می‌کند.

بازگشت شیء جدید Article

1if ($row) return new Article($row);
2}

آخرین کاری که متدهای ما باید انجام دهند ایجاد یک شیء جدید Article است که رکورد بازگشتی از دیتابیس در آن ذخیره شود و این شیء به کد فراخوانی کننده بازگشت یابد. ابتدا بررسی می‌کنیم که مقدار بازگشت از فراخوانی ()fetch یعنی در عمل حاوی داده‌ای باشد. اگر چنین باشد، در ادامه یک شیء جدید Article ایجاد کرده و به row$ ارسال می‌کنیم. به خاطر داشته باشید که این متد سازنده ما را فراخوانی می‌کند که قبلاً ایجاد کردیم و شیء را با داده‌های موجود در آرایه row$ مقداردهی می‌کند. در ادامه راهنمای طراحی سایت خبری با PHP این شیء جدید را بازگشت می‌دهیم تا کارمان در این مرحله پایان یابد.

متد ()getList

متد بعدی که دراین مطلب طراحی سایت خبری با PHP بررسی می‌کنیم ()getList نام دارد که از جهات مختلف شبیه متد قبلی ()getById است. تفاوت عمده، چنان که شاید حدس بزنید، این است که این متد اخیر می‌تواند به جای یک مقاله، هر بار مقالات زیادی را بازگشت دهد. هر زمان که نیاز به نمایش فهرستی از مقالات به کاربر یا مدیر سایت داشته باشیم، می‌توانیم از این متد بهره بگیریم.

متد ()getList برخی آرگومان‌های اختیاری دارد که به شرح زیر هستند.

numRows

این آرگومان بیشینه تعداد مقالاتی که می‌تواند بازگشت یابد را مشخص می‌سازد. مقدار پیش‌فرض این آرگومان 1،000،000 است که عملاً به معنای همه مقالات وب‌سایت است. این پارامتر به ما امکان می‌دهد که برای نمونه تنها پنج مقاله آخر را در صفحه نخست سایت نمایش دهیم.

اغلب بخش‌های کد این متد مشابه است. با این حال در ادامه مقاله طراحی سایت خبری با PHP بخش‌های ابتدایی آن را بررسی می‌کنیم.

1$sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles
2            ORDER BY publicationDate DESC LIMIT :numRows"

اکنون کوئری ما کمی پیچیده‌تر از کوئری قبلی است. در وهله نخست متوجه می‌شویم که هیچ گزاره وجود ندارد، زیرا می‌خواهیم همه مقالات و نه یک مقاله که دارای شناسه خاصی است، بازیابی شوند.

همچنین یک بند LIMIT نیز اضافه شده که در پارامتر numRows$ ارسال می‌شود و به این ترتیب می‌توانیم به طور اختیاری تعداد رکوردهایی که بازگشت خواهد یافت را محدود سازیم.

در نهایت مقدار خاص SQL_CALC_FOUND_ROWS به MySQL اعلام می‌کند که تعداد واقعی رکوردهای بازگشتی را نیز نشان دهد. این اطلاعات برای نمایش به کاربر و همچنین کارهایی مانند صفحه‌بندی مقالات بازگشتی مفید است.

1    $list = array();
2
3    while ( $row = $st->fetch() ) {
4      $article = new Article( $row );
5      $list[] = $article;
6    }

از آنجا که چندین ردیف را بازگشت می‌دهیم، یک آرایه به نام list$ ایجاد می‌کنیم تا اشیای متناظر Article را نگه‌داری کند. در ادامه از حلقه while برای بازیابی ردیف بعدی از طریق ()fetch استفاده می‌کنیم، شیء جدید Article را ایجاد کرده، مقادیر ردیف را در آن ذخیره ساخته و شیء را به آرایه list$ اضافه می‌کنیم. زمانی که هیچ ردیفی وجود نداشته باشد، متد ()fetch مقدار false بازگشت داده و از حلقه خارج می‌شود.

1    // Now get the total number of articles that matched the criteria
2    $sql = "SELECT FOUND_ROWS() AS totalRows";
3    $totalRows = $conn->query( $sql )->fetch();
4    $conn = null;
5    return ( array ( "results" => $list, "totalRows" => $totalRows[0] ) );

در نهایت کوئری دیگری را اجرا می‌کنیم که از تابع ()FOUND_ROWS در MySQL بهره می‌گیرد تا تعداد ردیف‌های بازگشتی را که توسط دستور قبلی SQL_CALC_FOUND_ROWS بازگشت دهد. این بار از متد ()query در PDO استفاده می‌کنیم که به ما امکان می‌دهد در صورت عدم وجود اتصال placeholders یک کوئری را به روشی سریع اجرا کنیم. ما متد ()fetch را روی دستگیره گزاره حاصل فراخوانی می‌کنیم تا ردیف نتیجه را بازیابی نماییم و در ادامه هم لیست اشیای Article یعنی list$ و هم تعداد کل ردیف‌های یک آرایه انجمنی را بازگشت می‌دهیم.

متد ()insert

متدهای دیگر کلاس Article به کارهایی مانند افزودن، تغییر دادن و حذف رکوردهای مقالات در پایگاه داده مرتبط هستند.

  • متد ()insert یک رکورد جدید مقاله به جدول articles اضافه می‌کند که مقادیر آن در شیء کنونی Article ذخیره شده‌اند.
  • این متد ابتدا مطمئن می‌شود که شیء از قبل دارای مشخصه تعیین شده id$ نباشد. اگر چنین باشد و یک ID موجود باشد، احتمالاً مقاله از قبل در دیتابیس وجود دارد و از این رو نباید آن را مجدداً درج کنیم.
  • در ادامه متد ()insert یک کوئری اس‌کیوال به نام برای درج رکورد در جدول articles با استفاده از placeholder-ها برای ارسال مقادیر مشخصه به دیتابیس استفاده می‌کند. توجه کنید که استفاده از تابع ()FROM_UNIXTIME در اس‌کیو‌ال برای تبدیل تاریخ انتشار از فرمت UNIX timestamp به فرمت MySQL است.
  • پس از اجرای کوئری، این متد ID رکورد مقاله جدید را با استفاده از تابع PDO به نام ()lastInsertId از دیتابیس بازگشت می‌دهد و در مشخصه id$ شیء برای ارجاع بعدی ذخیره می‌سازد. به خاطر داشته باشید که ما فیلد id جدول articles را به صورت یک فیلد «خود-افزایشی» (auto_increment) تنظیم کرده‌ایم و از این رو MySQL هر بار که رکورد جدیدی ایجاد شود یک شناسه یکتای جدید برای آن خواهد ساخت.

توجه کنید که ما از PDO::PARAM_INT در زمان اتصال مقادیر صحیح به placeholder-ها استفاده می‌کنیم و زمانی که مقادیر رشته‌ای اتصال می‌یابند از PDO::PARAM_STR استفاده می‌شود. این کار به آن جهت صورت می‌گیرد که PDO بتواند مقادیر را به طرز صحیحی escape کند.

متد ()update

این متد مشابه متد ()insert به جز این که به جای ایجاد یک رکورد جدید، رکورد یک مقاله موجود در دیتابیس را به‌روزرسانی می‌کند. ابتدا بررسی می‌کند که آیا شیء دارای ID است یا نه. سپس از گزاره اس‌کیوال UPDATE برای به‌روزرسانی فیلدهای رکورد مقاله استفاده می‌کند. توجه کنید که ما ID شیء را برای به‌روزرسانی به گزاره UPDATE ارسال می‌کنیم تا بداند که کدام رکورد را باید به‌روزرسانی کند.

متد ()delete

وظیفه متد ()delete کاملاً از روی نامش مشخص است. این متد از گزاره DELETE اس‌کیو‌ال برای حذف یک مقاله ذخیره شده در جدول articles استفاده می‌کند. به این منظور از مشخصه id$ شیء برای شناسایی رکورد در جدول استفاده می‌شود. به دلایل امنیتی ما LIMIT 1 را نیز به کوئری اضافه می‌کنیم تا مطمئن شویم که تنها 1 مقاله هر زمان می‌تواند حذف شود.

گام پنجم: نوشتن اسکریپت فرانت‌اند در index.php

طراحی سایت خبری با PHP

ما تا به اینجا در آموزش طراحی سایت خبری با PHP کلاس Article را ایجاد کرده‌ایم که بار عمده وظایف CMS را بر عهده دارد. اکنون که این کار تمام شده، بقیه بخش‌های وب‌سایت خبری ما آسان است.

ابتدا یک فایل به نام index.php ایجاد می‌کنیم که نمایش صفحه‌های فرانت‌اند سایت را کنترل می‌کند. این فایل را در پوشه cms که قبلاً در ابتدای گام چهارم راهنمای طراحی سایت خبری با PHP ایجاد کردیم ذخیره کنید.

1<?php
2
3require( "config.php" );
4$action = isset( $_GET['action'] ) ? $_GET['action'] : "";
5
6switch ( $action ) {
7  case 'archive':
8    archive();
9    break;
10  case 'viewArticle':
11    viewArticle();
12    break;
13  default:
14    homepage();
15}
16
17
18function archive() {
19  $results = array();
20  $data = Article::getList();
21  $results['articles'] = $data['results'];
22  $results['totalRows'] = $data['totalRows'];
23  $results['pageTitle'] = "Article Archive | Widget News";
24  require( TEMPLATE_PATH . "/archive.php" );
25}
26
27function viewArticle() {
28  if ( !isset($_GET["articleId"]) || !$_GET["articleId"] ) {
29    homepage();
30    return;
31  }
32
33  $results = array();
34  $results['article'] = Article::getById( (int)$_GET["articleId"] );
35  $results['pageTitle'] = $results['article']->title . " | Widget News";
36  require( TEMPLATE_PATH . "/viewArticle.php" );
37}
38
39function homepage() {
40  $results = array();
41  $data = Article::getList( HOMEPAGE_NUM_ARTICLES );
42  $results['articles'] = $data['results'];
43  $results['totalRows'] = $data['totalRows'];
44  $results['pageTitle'] = "Widget News";
45  require( TEMPLATE_PATH . "/homepage.php" );
46}
47
48?>

در ادامه بخش‌های مختلف فایل فوق را توضیح می‌دهیم.

گنجاندن فایل پیکربندی

ما در خط نخست کد، فایل config.php را که قبلاً ایجاد کردیم include می‌کنیم تا همه تنظیمات پیکربندی در اختیار اسکریپت قرار گیرند. به این منظور از گزاره ()require به جای ()include استفاده می‌کنیم زیرا ()require در صورت نبود فایل یک خطا ایجاد می‌کند.

به دست آوردن پارامتر action

در ادامه پارامتر GET['action']_$ را در متغیری به نام $action ذخیره می‌کنیم تا بتوانیم در ادامه راهنمای طراحی سایت خبری با PHP از این مقدار در اسکریپت بهره بگیریم. پیش از انجام این کار، با استفاده از متد ()isset بررسی می‌کنیم که مقدار GET['action']_$ موجود باشد. اگر چنین نباشد، متغیر action$ متناظر را به صورت یک رشته خالی تنظیم می‌کنیم.

نکته: بررسی مقادیر ارائه شده از سوی کاربر مانند پارامترهای رشته کوئری، مقادیر فرم‌ها و کوکی‌ها پیش از استفاده عملی از آن‌ها، همواره یک رویه مناسب در برنامه‌نویسی محسوب می‌شود. این کار نه تنها از بروز حفره‌های امنیتی جلوگیری می‌کند، بلکه با انجام این کار دیگر شاهد بروز خطاهای undefined index در زمان اجرای اسکریپت‌های PHP نیز نخواهید بود.

تصمیم‌گیری در مورد اکشنی که باید اجرا شود

بلوک switch به دنبال پارامتر action در URL می‌گردد تا تشخیص دهد کدام اکشن باید اجرا شود. اگر هیچ پارامتر action در URL موجود نباشد، در این صورت اسکریپت صفحه اصلی سایت را نمایش می‌دهد.

تابع ()archive

این تابع فهرستی از همه مقالاتی که در پایگاه داده وجود دارند نمایش می‌دهد. این کار با فراخوانی متد ()getList از کلاس Article انجام می‌یابد که قبلاً ایجاد کردیم. در ادامه تابع ()archive نتایج را همراه با عنوان صفحه در یک آرایه انجمنی به نام results$ ذخیره می‌کند تا تمپلیت بتواند آن‌ها را در صفحه نمایش دهد. در نهایت فایل تمپلیت برای نمایش صفحه include می‌شود. در ادامه این فایل را ایجاد خواهیم کرد.

تابع ()viewArticle

این تابع یک صفحه منفرد را نمایش می‌دهد. این تابع ID مقاله‌ای که باید نمایش یابد را از پارامتر articleId در URL بازیابی می‌کند و در ادامه متد ()getById کلاس Article را فرا می‌خواند تا شیء مقاله بازیابی شده و در ادامه آن را برای استفاده تمپلیت در آرایه results$ ذخیره می‌سازد.

توجه کنید که ما از (int) برای تبدیل پارامتری کوئری articleID به مقدار صحیح استفاده کردیم. این یک معیار امنیتی مناسب است چون از ارسال مقادیر غیر عدد صحیح به کدمان جلوگیری به عمل می‌آورد.

تابع ()homepage

آخرین تابع ما به نام ()homepage صفحه اصلی سایت را نمایش می‌دهد که شامل فهرستی از مقالات است. این تابع تا حدودی زیادی شبیه تابع ()archive است به جز این که HOMEPAGE_NUM_ARTICLES را به متد ()getList ارسال می‌کند تا تعداد مقالات بازگشتی را محدود سازد.

گام ششم: اسکریپت بک‌اند به نام admin.php

طراحی سایت خبری با PHP

اسکریپت مدیریتی ما پیچیده‌تر از index.php است، چون با کارکردهای مدیریتی CMS سروکار دارد. با این حال ساختار اساسی آن تا حدودی شبیه اسکریپت فرانت‌اند است. در این بخش از راهنمای طراحی سایت خبری با PHP کد زیر را در فایلی به نام admin.php در همان پوشه‌ای که اسکریپت index.php را ذخیره کر‌دید، قرار دهید.

1<?php
2
3require( "config.php" );
4session_start();
5$action = isset( $_GET['action'] ) ? $_GET['action'] : "";
6$username = isset( $_SESSION['username'] ) ? $_SESSION['username'] : "";
7
8if ( $action != "login" && $action != "logout" && !$username ) {
9  login();
10  exit;
11}
12
13switch ( $action ) {
14  case 'login':
15    login();
16    break;
17  case 'logout':
18    logout();
19    break;
20  case 'newArticle':
21    newArticle();
22    break;
23  case 'editArticle':
24    editArticle();
25    break;
26  case 'deleteArticle':
27    deleteArticle();
28    break;
29  default:
30    listArticles();
31}
32
33
34function login() {
35
36  $results = array();
37  $results['pageTitle'] = "Admin Login | Widget News";
38
39  if ( isset( $_POST['login'] ) ) {
40
41    // User has posted the login form: attempt to log the user in
42
43    if ( $_POST['username'] == ADMIN_USERNAME && $_POST['password'] == ADMIN_PASSWORD ) {
44
45      // Login successful: Create a session and redirect to the admin homepage
46      $_SESSION['username'] = ADMIN_USERNAME;
47      header( "Location: admin.php" );
48
49    } else {
50
51      // Login failed: display an error message to the user
52      $results['errorMessage'] = "Incorrect username or password. Please try again.";
53      require( TEMPLATE_PATH . "/admin/loginForm.php" );
54    }
55
56  } else {
57
58    // User has not posted the login form yet: display the form
59    require( TEMPLATE_PATH . "/admin/loginForm.php" );
60  }
61
62}
63
64
65function logout() {
66  unset( $_SESSION['username'] );
67  header( "Location: admin.php" );
68}
69
70
71function newArticle() {
72
73  $results = array();
74  $results['pageTitle'] = "New Article";
75  $results['formAction'] = "newArticle";
76
77  if ( isset( $_POST['saveChanges'] ) ) {
78
79    // User has posted the article edit form: save the new article
80    $article = new Article;
81    $article->storeFormValues( $_POST );
82    $article->insert();
83    header( "Location: admin.php?status=changesSaved" );
84
85  } elseif ( isset( $_POST['cancel'] ) ) {
86
87    // User has cancelled their edits: return to the article list
88    header( "Location: admin.php" );
89  } else {
90
91    // User has not posted the article edit form yet: display the form
92    $results['article'] = new Article;
93    require( TEMPLATE_PATH . "/admin/editArticle.php" );
94  }
95
96}
97
98
99function editArticle() {
100
101  $results = array();
102  $results['pageTitle'] = "Edit Article";
103  $results['formAction'] = "editArticle";
104
105  if ( isset( $_POST['saveChanges'] ) ) {
106
107    // User has posted the article edit form: save the article changes
108
109    if ( !$article = Article::getById( (int)$_POST['articleId'] ) ) {
110      header( "Location: admin.php?error=articleNotFound" );
111      return;
112    }
113
114    $article->storeFormValues( $_POST );
115    $article->update();
116    header( "Location: admin.php?status=changesSaved" );
117
118  } elseif ( isset( $_POST['cancel'] ) ) {
119
120    // User has cancelled their edits: return to the article list
121    header( "Location: admin.php" );
122  } else {
123
124    // User has not posted the article edit form yet: display the form
125    $results['article'] = Article::getById( (int)$_GET['articleId'] );
126    require( TEMPLATE_PATH . "/admin/editArticle.php" );
127  }
128
129}
130
131
132function deleteArticle() {
133
134  if ( !$article = Article::getById( (int)$_GET['articleId'] ) ) {
135    header( "Location: admin.php?error=articleNotFound" );
136    return;
137  }
138
139  $article->delete();
140  header( "Location: admin.php?status=articleDeleted" );
141}
142
143
144function listArticles() {
145  $results = array();
146  $data = Article::getList();
147  $results['articles'] = $data['results'];
148  $results['totalRows'] = $data['totalRows'];
149  $results['pageTitle'] = "All Articles";
150
151  if ( isset( $_GET['error'] ) ) {
152    if ( $_GET['error'] == "articleNotFound" ) $results['errorMessage'] = "Error: Article not found.";
153  }
154
155  if ( isset( $_GET['status'] ) ) {
156    if ( $_GET['status'] == "changesSaved" ) $results['statusMessage'] = "Your changes have been saved.";
157    if ( $_GET['status'] == "articleDeleted" ) $results['statusMessage'] = "Article deleted.";
158  }
159
160  require( TEMPLATE_PATH . "/admin/listArticles.php" );
161}
162
163?>

در ادامه آموزش طراحی سایت خبری با PHP به بررسی محتوای فایل فوق می‌پردازیم.

آغاز نشست کاربری

در بخش فوقانی فایل admin.php تابع ()session_start را فراخوانی می‌کنیم. این تابع PHP یک نشست جدید برای کاربر آغاز می‌کند که می‌توان از آن برای بررسی لاگین بودن کاربر استفاده کرد. از آنجا که «نشست‌ها» (sessions) برای کاربرد صحیح به کوکی نیاز دارند و کوکی‌ها نیز پیش از محتوا به مرورگر ارسال می‌شوند، باید ()session_start را در ابتدای اسکریپت و پیش از خروجی دادن هر نوع محتوایی قرار دهید.

به دست آوردن پارامتر اکشن و متغیر نشست username

در ادامه راهنمای طراحی سایت خبری با PHP پارامتر GET['action']_$ را در یک متغیر به نام action$ قرار می‌دهیم و متغیر نشست SESSION['username']_$ نیز در usernam$ قرار می‌گیرد تا بتوانیم از این مقادیر در ادامه اسکریپت استفاده کنیم. پیش از انجام این کار با بهره‌گیری از متد ()isset بررسی می‌کنیم که آیا این مقادیر قبلاً تعیین شده‌اند یا نه. اگر مقداری موجود نباشد، متغیر متناظر را روی یک رشته خالی تنظیم می‌کنیم.

بررسی لاگین کردن کاربر

کاربر تا پیش از آن که لاگین کند نباید مجاز به انجام هیچ کاری به عنوان یک مدیر باشد. بنابراین در گام بعدی به بررسی username$ می‌پردازیم تا مطمئن شویم که کاربر لاگین کرده است. اگر مقدار username$ خالی باش و کاربر قبلاً تلاشی برای ورود یا خروج نکرده باشد، در این صورت صفحه لاگین را نمایش داده و بی‌رنگ خارج می‌شویم.

تصمیم‌گیری در مورد اکشنی که باید اجرا شود

بلوک switch تا حدود زیادی شبیه به بلوک متناظر در فایل index.php عمل می‌کند. این بلوک تابع متناسب را بر اساس مقدار پارامتر action در URL فرا می‌خواند. اکشن پیش‌فرض نمایش فهرستی از مقالات در CMS است.

تابع ()login

این تابع زمانی که کاربر باید لاگین کند یا در فرایند لاگین است فراخوانی می‌شود. اگر کاربر فرم لاگین را تحویل داده باشد که بر اساس بررسی پارامتر login فرم مشخص می‌شود، این تابع به بررسی نام کاربری و رمز عبور وارد شده با مقادیر ADMIN_USERNAME و ADMIN_PASSWORD پیکربندی‌شده می‌پردازد. اگر این دو مقدار تطبیق پیدا کنند، کلید نشست username برای نام کاربری admin تنظیم می‌شود یعنی مدیر با موفقیت وارد سیستم شده است و در ادامه مرورگر به اسکریپت admin.php هدایت می‌شود که فهرستی از مقالات را نمایش خواهد داد. اگر نام کاربری و رمز عبور با هم تطبیق نیابند، در این صورت فرم لاگین با نشان دادن پیام خطا دوباره نمایش پیدا می‌کند. اگر کاربر فرم لاگین را هنوز تحویل نداده باشد، این تابع صرفاً فرم را نمایش می‌دهد. در ادامه راهنمای طراحی سایت خبری با PHP توابع دیگر بک‌اند را بررسی می‌کنیم.

تابع ()logout

این تابع زمانی فراخوانی می‌شود که کاربر بخواهد از سیستم خارج شود. تنها وظیفه این تابع آن است که کلید نشست username را حذف کرده و کاربر را به اسکریپت admin.php هدایت می‌نماید.

تابع ()newArticle

این تابع به کاربر امکان می‌دهد که مقاله جدیدی ایجاد کند. اگر کاربر فرم new article را تحویل داده باشد، در این صورت تابع یک شیء جدید Article را ایجاد کرده، داده‌های فرم را از طریق فراخوانی ()storeFormValues در آن ذخیره کرده و مقاله را با فراخوانی ()insert در دیتابیس درج می‌کند. سپس دوباره به فهرست مقالات بازمی‌گردد و پیام وضعیت Changes Saved نمایش می‌یابد.

اگر کاربر فرم new article را هنوز ارسال نکرده باشد، این تابع یک شیء جدید Article بدون مقدار ایجاد کرده و از تمپلیت editArticle.php برای نمایش فرم ویرایش مقاله با استفاده از شیء خالی Article استفاده می‌کند.

تابع ()editArticle

در این بخش از راهنمای طراحی سایت خبری با PHP یک تابع دیگر بک‌اند را معرفی می‌کنیم. این تابع مشابه تابع قبلی ()newArticle است و تنها تفاوتش در این است که امکان ویرایش یک مقاله موجود را به کاربر می‌دهد. هنگامی که کاربر تغییرات خود را ذخیره کند، این تابع مقاله موجود را با استفاده از ()getById بازیابی می‌کند و مقدار جدید را در شیء Article ذخیره کرده و شیء تغییر یافته را با فراخوانی متد ()update در دیتابیس به‌روزرسانی می‌کند. هنگامی که فرم ویرایش مقاله نمایش یابد، تابع دوباره از متد برای بارگذاری مقادیر فیلد مقاله کنونی در فرم ویرایش مقاله بهره می‌گیرد.

توجه داشته باشید که اسکریپت از تمپلیت یکسانی به نام editArticle.php برای ایجاد مقاله جدید و ویرایش مقالات موجود بهره می‌گیرد. این بدان معنی است که ما تنها باید یک فرم منفرد HTML ایجاد کنیم. پارامتر formAction برای تشخیص این که کاربر در حال نوشتن مقاله یا ویرایش کردن آن است مورد استفاده قرار می‌گیرد.

تابع ()deleteArticle

اگر کاربر بخواهد مقاله‌ای را حذف کند، این تابع ابتدا مقاله را از دیتابیس بازیابی کرده و سپس با فراخوانی متد ()delete از کلاس Article آن را حذف می‌کند. توجه کنید که اگر مقاله را نتواند در دیتابیس پیدا کند، یک خطا نمایش می‌دهد. در ادامه به صفحه فهرست مقالات ریدایرکت می‌شود که پیام وضعیت article deleted نمایش می‌یابد.

تابع ()listArticles

تابع آخری که در فایل admin.php مشاهده می‌کنیم، فهرستی از همه مقالات موجود در CMS را برای ویرایش کردن نمایش می‌دهد. این تابع از متد ()getList کلاس Article برای بازیابی همه مقالات استفاده می‌کند و سپس از تمپلیت listArticles.php برای نمایش فهرست بهره می‌گیرد. در این مسیر همچنین به بررسی پارامترهای error و status در کوئری URL می‌پردازد تا در صورت نیاز به نمایش یک خطا یا وضعیت روی صفحه از آن اطلاع پیدا کند. در این حالت پیام مورد نیاز ایجاد شده و برای نمایش به تمپلیت ارسال می‌شود.

گام هفتم: ساخت تمپلیت‌های فرانت‌اند

طراحی سایت خبری با PHP

ما تا به اینجای راهنمای طراحی سایت خبری با PHP همه کد‌های PHP لازم برای کارکرد CMS را ایجاد کرده‌ایم. در این گام باید تمپلیت‌های HTML را برای هر دو سمت فرانت‌اند و بک‌اند بسازیم. ابتدا کار را از تمپلیت‌های فرانت‌اند آغاز می‌کنیم.

فایل‌های include

یک پوشه به نام templates درون پوشه cms ایجاد کنید. سپس پوشه‌ای به نام include درون پوشه templates بسازید. در این پوشه قصد داریم مارکاپ هدر و فوتر وب‌سایت را که میان همه صفحه‌های وب‌سایت خبری ما مشترک است قرار دهیم تا از زحمت قرار دادن آن داخل تک‌تک فایل‌های تمپلیت معاف شویم.

در این بخش از آموزش طراحی سایت خبری با PHP یک فایل جدید به نام header.php ایجاد کرده و در پوشه include قرار دهید و کد زیر را در آن ذخیره کنید.

1<!DOCTYPE html>
2<html lang="en">
3  <head>
4    <title><?php echo htmlspecialchars( $results['pageTitle'] )?></title>
5    <link rel="stylesheet" type="text/css" href="style.css" />
6  </head>
7  <body>
8    <div id="container">
9
10      <a href="."><img id="logo" src="images/logo.jpg" alt="Widget News" /></a>

چنان که می‌بینید این کد صرفاً به نمایش مارکاپ آغازین صفحه HTML می‌پردازد. در این کد از متغیر results['pageTitle']$ که از اسکریپت اصلی یعنی index.php یا admin.php ارسال شده برای تنظیم عنصر title استفاده می‌کنیم و همچنین در ادامه آموزش طراحی سایت خبری با PHP فایل استایل‌شیت را به نام style.css لینک می‌کنیم.

توجه کنید که مقدار results['pageTitle']$ را از طریق تابع PHP به نام ()htmlspecialchars ارسال می‌کنیم. این تابع همه کاراکترهای خاص HTML مانند <, > و & را درون معادل‌های نهاد HTML آن یعنی &lt;, &gt; و &amp; قرار می‌دهد. به این ترتیب همراه با فیلتر کردن ورودی که در زمان نوشتن سازنده Article در گام چهارم انجام دادیم، خروجی را نیز انکد می‌کنیم که یک رویه امنیتی خوب محسوب می‌شود. ما غالب داده‌های موجود در تمپلیت‌ها را به همین شیوه کدگذاری می‌کنیم.

در ادامه مراحل طراحی سایت خبری با PHP فایلی به نام footer.php در همان پوشه ایجاد کرده و کد زیر را در آن ذخیره می‌کنیم.

1      <div id="footer">
2        Widget News © 2011. All rights reserved. <a href="admin.php">Site Admin</a>
3      </div>
4
5    </div>
6  </body>
7</html>

به این ترتیب مارکاپ انتهایی همه صفحه‌های HTML ما انجام می‌یابد.

فایل homepage.php

اینک دوباره به سراغ پوشه templates می‌رویم و این بار فایلی به نام تمپلیت homepage.php ایجاد کرده و کد زیر را در آن قرار می‌دهیم.

1<?php include "templates/include/header.php" ?>
2
3      <ul id="headlines">
4
5<?php foreach ( $results['articles'] as $article ) { ?>
6
7        <li>
8          <h2>
9            <span class="pubDate"><?php echo date('j F', $article->publicationDate)?></span><a href=".?action=viewArticle&articleId=<?php echo $article->id?>"><?php echo htmlspecialchars( $article->title )?></a>
10          </h2>
11          <p class="summary"><?php echo htmlspecialchars( $article->summary )?></p>
12        </li>
13
14<?php } ?>
15
16      </ul>
17
18      <p><a href="./?action=archive">Article Archive</a></p>
19
20<?php include "templates/include/footer.php" ?>

این تمپلیت عناوین مقاله را روی صفحه اصلی به صورت یک لیست نامرتب نمایش می‌دهد. این تمپلیت روی آرایه اشیای Article که در results['articles']$ ذخیره‌سازی شده‌اند می‌چرخد و تاریخ انتشار، عنوان و خلاصه‌ای از هر مقاله را نمایش می‌‌دهد. این عنوان به '.' پیوند دارد که اکشن action=viewArticle را ارسال می‌کند و همچنین ID مقاله را در URL نمایش می‌دهد. به این ترتیب بازدیدکننده می‌تواند یک مقاله را با کلیک کردن روی عنوانش بخواند.

تمپلیت شامل یک لینک به آرشیو مقاله است. توجه کنید که این تمپلیت و همچنین تمپلیت‌های بعدی از گزاره include در PHP برای گنجاندن فایل‌های هدر و فوتر در صفحه استفاده می‌کنند.

فایل archive.php

اکنون فایل تمپلیت archive.php را در پوشه templates ایجاد و کد زیر را در آن وارد می‌کنیم.

1<?php include "templates/include/header.php" ?>
2
3      <h1>Article Archive</h1>
4
5      <ul id="headlines" class="archive">
6
7<?php foreach ( $results['articles'] as $article ) { ?>
8
9        <li>
10          <h2>
11            <span class="pubDate"><?php echo date('j F Y', $article->publicationDate)?></span><a href=".?action=viewArticle&articleId=<?php echo $article->id?>"><?php echo htmlspecialchars( $article->title )?></a>
12          </h2>
13          <p class="summary"><?php echo htmlspecialchars( $article->summary )?></p>
14        </li>
15
16<?php } ?>
17
18      </ul>
19
20      <p><?php echo $results['totalRows']?> article<?php echo ( $results['totalRows'] != 1 ) ? 's' : '' ?> in total.</p>
21
22      <p><a href="./">Return to Homepage</a></p>
23
24<?php include "templates/include/footer.php" ?>

این تمپلیت بایگانی همه مقالات را در CMS نمایش می‌دهد. چنان که می‌بینید تقریباً مشابه فایل homepage.php است. این تمپلیت یک کلاس CSS به نام archive به لیست نامرتب اضافه می‌کند تا بتوانیم آیتم‌های لیست را کمی متفاوت از صفحه اصلی استایل‌بندی کنیم. همچنین سال را به تاریخ انتشار اضافه می‌کند.

این صفحه همچنین تعداد کل مقالات را در دیتابیس نمایش می‌دهد که از طریق results['totalRows']$ بازیابی شده‌اند. در نهایت به جای لینک آرشیو در انتهای صفحه، شامل لینکی برای «بازگشت به صفحه اصلی» (Return to Homepage) است.

فایل viewArticle.php

آخرین تمپلیت فرانت‌اند در این راهنمای طراحی سایت خبری با PHP برای نمایش یک مقاله به کاربر استفاده می‌شود. یک فایل به نام viewArticle.php در پوشه templates ایجاد کرده و کد زیر را در آن ذخیره کنید.

1<?php include "templates/include/header.php" ?>
2
3      <h1 style="width: 75%;"><?php echo htmlspecialchars( $results['article']->title )?></h1>
4      <div style="width: 75%; font-style: italic;"><?php echo htmlspecialchars( $results['article']->summary )?></div>
5      <div style="width: 75%;"><?php echo $results['article']->content?></div>
6      <p class="pubDate">Published on <?php echo date('j F Y', $results['article']->publicationDate)?></p>
7
8      <p><a href="./">Return to Homepage</a></p>
9
10<?php include "templates/include/footer.php" ?>

این تمپلیت طراحی سایت خبری با PHP بسیار سرراست است و عنوان مقاله، خلاصه، محتوا و همچنین تاریخ انتشار مقاله منتخب را به همراه لینکی برای بازگشت به صفحه اصلی نمایش می‌دهد.

شاید متوجه شده باشید که ما results['article']->content$ را از طریق ()htmlspecialchars ارسال نکردیم. چنان که در زمان ایجاد سازنده Article در گام چهارم اشاره کردیم، سازنده احتمالاً می‌خواهد از مارکاپ HTML مانند تگ‌های <p> در محتوای مقاله استفاده کند. اگر محتوا را انکد کنیم، در این صورت تگ‌های <p> روی صفحه به جای ایجاد پاراگراف به همان صورت متن ساده یعنی <p> نمایش می‌یابند.

گام هشتم: ساخت تمپلیت‌های بک‌اند

طراحی سایت خبری با PHP

اکنون و در این گام از راهنمای طراحی سایت خبری با PHP که تمپلیت‌های فرانت‌اند را ایجاد کردیم، نوبت آن رسیده که سه تمپلیت بخش مدیریتی وب‌سایت خبری خودمان را نیز ایجاد کنیم.

تمپلیت loginForm.php

ابتدا پوشه دیگری به نام admin درون پوشه templates ایجاد می‌کنیم. درون پوشه admin اولین تمپلیت را به نام loginForm.php ایجاد کرده و کد زیر را در آن ذخیره می‌کنیم.

1<?php include "templates/include/header.php" ?>
2
3      <form action="admin.php?action=login" method="post" style="width: 50%;">
4        <input type="hidden" name="login" value="true" />
5
6<?php if ( isset( $results['errorMessage'] ) ) { ?>
7        <div class="errorMessage"><?php echo $results['errorMessage'] ?></div>
8<?php } ?>
9
10        <ul>
11
12          <li>
13            <label for="username">Username</label>
14            <input type="text" name="username" id="username" placeholder="Your admin username" required autofocus maxlength="20" />
15          </li>
16
17          <li>
18            <label for="password">Password</label>
19            <input type="password" name="password" id="password" placeholder="Your admin password" required maxlength="20" />
20          </li>
21
22        </ul>
23
24        <div class="buttons">
25          <input type="submit" name="login" value="Login" />
26        </div>
27
28      </form>
29
30<?php include "templates/include/footer.php" ?>

این صفحه شامل فرم لاگین ادمین است که به admin.php?action=login ارسال می‌شود. این صفحه شامل فیلد پنهان login است که تابع ()login ما در گام ششم راهنمای طراحی سایت خبری با PHP از آن برای بررسی پست شدن فرم لاگین استفاده می‌کند. همچنین این فرم شامل بخشی است که پیام‌های خطا مانند نادرستی نام کاربری رمز عبور را نمایش می‌دهد و همچنین شامل فیلد‌های username و password و دکمه login است.

ما در این تمپلیت از برخی قابلیت‌های HTML5 مانند placeholder, required, autofocus و date در فرم‌های مدیریتی استفاده کرده‌ایم. این کار موجب می‌شود که فرم استفاده بهتری داشته باشد و همچنین ما را از زحمت بررسی فیلد‌های الزامی در کد PHP معاف سازد. از آنجا که همه مرورگرها در حال حاضر از این قابلیت‌های فرم‌های HTML5 بهره نمی‌گیرند، احتمالاً بهتر است از fallbacks-های JavaScript و/یا PHP برای بررسی فیلد‌های الزامی در سیستم‌های پروداکشن خود استفاده کنید.

تمپلیت listArticles.php

در این بخش از آموزش طراحی سایت خبری با PHP تمپلیت دوم مدیریتی را در پوشه admin می‌سازیم. به این منظور فایلی به نام listArticles.php ایجاد کرده و کد زیر را در آن ذخیره می‌کنیم.

1<?php include "templates/include/header.php" ?>
2
3      <div id="adminHeader">
4        <h2>Widget News Admin</h2>
5        <p>You are logged in as <b><?php echo htmlspecialchars( $_SESSION['username']) ?></b>. <a href="admin.php?action=logout"?>Log out</a></p>
6      </div>
7
8      <h1>All Articles</h1>
9
10<?php if ( isset( $results['errorMessage'] ) ) { ?>
11        <div class="errorMessage"><?php echo $results['errorMessage'] ?></div>
12<?php } ?>
13
14
15<?php if ( isset( $results['statusMessage'] ) ) { ?>
16        <div class="statusMessage"><?php echo $results['statusMessage'] ?></div>
17<?php } ?>
18
19      <table>
20        <tr>
21          <th>Publication Date</th>
22          <th>Article</th>
23        </tr>
24
25<?php foreach ( $results['articles'] as $article ) { ?>
26
27        <tr onclick="location='admin.php?action=editArticle&articleId=<?php echo $article->id?>'">
28          <td><?php echo date('j M Y', $article->publicationDate)?></td>
29          <td>
30            <?php echo $article->title?>
31          </td>
32        </tr>
33
34<?php } ?>
35
36      </table>
37
38      <p><?php echo $results['totalRows']?> article<?php echo ( $results['totalRows'] != 1 ) ? 's' : '' ?> in total.</p>
39
40      <p><a href="admin.php?action=newArticle">Add a New Article</a></p>
41
42<?php include "templates/include/footer.php" ?>

این تمپلیت همه فهرست مقالات را برای مدیر نمایش می‌دهد تا آن‌ها را در صورت نیاز ویرایش کند. پس از نمایش یافتن هر پیام خطا یا وضعیت، این تمپلیت روی آرایه اشیای Article که در متغیر results['articles']$ ذخیره شده می‌چرخد تا تاریخ انتشار هر مقاله و عنوان آن در ردیف جدول نمایش یابد. همچنین یک رویداد onclick جاوا اسکریپت به هر ردیف جدول مقالات اضافه می‌کند، به طوری که مدیر بتواند روی مقاله کلیک کرده و آن را ویرایش کند.

همچنین این تمپلیت تعداد کل مقالات را نیز همراه با لینکی نشان می‌دهد که به مدیر امکان می‌دهد تا یک مقاله جدید اضافه کند.

تمپلیت editArticle.php

در این بخش از راهنمای طراحی سایت خبری با PHP تمپلیت نهایی خود را به نام editArticle.php در پوشه admin ایجاد کرده و کد زیر را در آن ذخیره می‌کنیم.

1<?php include "templates/include/header.php" ?>
2
3      <div id="adminHeader">
4        <h2>Widget News Admin</h2>
5        <p>You are logged in as <b><?php echo htmlspecialchars( $_SESSION['username']) ?></b>. <a href="admin.php?action=logout"?>Log out</a></p>
6      </div>
7
8      <h1><?php echo $results['pageTitle']?></h1>
9
10      <form action="admin.php?action=<?php echo $results['formAction']?>" method="post">
11        <input type="hidden" name="articleId" value="<?php echo $results['article']->id ?>"/>
12
13<?php if ( isset( $results['errorMessage'] ) ) { ?>
14        <div class="errorMessage"><?php echo $results['errorMessage'] ?></div>
15<?php } ?>
16
17        <ul>
18
19          <li>
20            <label for="title">Article Title</label>
21            <input type="text" name="title" id="title" placeholder="Name of the article" required autofocus maxlength="255" value="<?php echo htmlspecialchars( $results['article']->title )?>" />
22          </li>
23
24          <li>
25            <label for="summary">Article Summary</label>
26            <textarea name="summary" id="summary" placeholder="Brief description of the article" required maxlength="1000" style="height: 5em;"><?php echo htmlspecialchars( $results['article']->summary )?></textarea>
27          </li>
28
29          <li>
30            <label for="content">Article Content</label>
31            <textarea name="content" id="content" placeholder="The HTML content of the article" required maxlength="100000" style="height: 30em;"><?php echo htmlspecialchars( $results['article']->content )?></textarea>
32          </li>
33
34          <li>
35            <label for="publicationDate">Publication Date</label>
36            <input type="date" name="publicationDate" id="publicationDate" placeholder="YYYY-MM-DD" required maxlength="10" value="<?php echo $results['article']->publicationDate ? date( "Y-m-d", $results['article']->publicationDate ) : "" ?>" />
37          </li>
38
39
40        </ul>
41
42        <div class="buttons">
43          <input type="submit" name="saveChanges" value="Save Changes" />
44          <input type="submit" formnovalidate name="cancel" value="Cancel" />
45        </div>
46
47      </form>
48
49<?php if ( $results['article']->id ) { ?>
50      <p><a href="admin.php?action=deleteArticle&articleId=<?php echo $results['article']->id ?>" onclick="return confirm('Delete This Article?')">Delete This Article</a></p>
51<?php } ?>
52
53<?php include "templates/include/footer.php" ?>

این فرم ویرایش هم برای ایجاد مقالات جدید و هم ویرایش مقالات موجود استفاده می‌شود. همچنین نتیجه را بسته به مقدار ارسالی در متغیر results['formAction']$ به یکی از موارد admin.php?action=newArticle یا admin.php?action=editArticle ارسال می‌کند. همچنین شامل یک فیلد پنهان به نام articleId است که ID مقاله‌ای که ویرایش می‌شود را بررسی می‌کند.

از سوی دیگر این فرم شامل بخشی است که پیام‌های خطا را نمایش می‌دهد و همچنین فیلد‌هایی برای عنوان مقاله، خلاصه، محتوا و تاریخ انتشار دارد. در نهایت دو دکمه برای ذخیره‌سازی و یا لغو تغییرات و یک لینک برای ایجاد امکان حذف مقاله در حال ویرایش ارائه شده است.

به طور معمول ما در زمان طراحی سایت خبری با PHP همه داده‌ها را پیش از خروجی دادن در مارکاپ از طریق ()htmlspecialchars ارسال می‌کنیم. این نه تنها یک رویه امنیتی خوب است، بلکه تضمین می‌کند که مقادیر فیلد فرم به درستی escpae شده‌اند. برای نمونه اگر مقدار فیلد title شامل یک گیومه دوبل باشد که escape نشده باشد، در این صورت ممکن است ردگیری نشود و از آنجا که گیومه‌های دوبل برای جداسازی مقدار فیلد در مارکاپ استفاده می‌شود.

توجه کنید که از خصوصیت formnovalidate در HTML5 برای دکمه Cancel استفاده شده است. این خصوصیت کارآمد به مرورگر اعلام می‌کند که در صورت فشردن دکمه کنسل اعتبارسنجی نکند.

گام نهم: ایجاد استایل‌شیت و تصویر لوگو

اینک به انتهای آموزش طراحی سایت خبری با PHP رسیده‌ایم و کار ساخت اپلیکیشن CMS ما اکنون به پایان رسیده است، اما برای این که آن را هم برای بازدیدکنندگان و هم مدیر سایت کمی زیباتر کنیم، یک فایل CSS برای کنترل ظاهر سایت می‌سازیم. کد زیر را در فایلی به نام style.css در پوشه cms ذخیره کنید.

1/* Style the body and outer container */
2
3body {
4  margin: 0;
5  color: #333;
6  background-color: #00a0b0;
7  font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
8  line-height: 1.5em;
9}
10
11#container {
12  width: 960px;
13  background: #fff;
14  margin: 20px auto;
15  padding: 20px;
16  -moz-border-radius: 5px;
17  -webkit-border-radius: 5px;
18  border-radius: 5px;
19}
20
21
22/* The logo and footer */
23
24#logo {
25  display: block;
26  width: 300px;
27  padding: 0 660px 20px 0;
28  border: none;
29  border-bottom: 1px solid #00a0b0;
30  margin-bottom: 40px;
31}
32
33#footer {
34  border-top: 1px solid #00a0b0;
35  margin-top: 40px;
36  padding: 20px 0 0 0;
37  font-size: .8em;
38}
39
40
41/* Headings */
42
43h1 {
44  color: #eb6841;
45  margin-bottom: 30px;
46  line-height: 1.2em;
47}
48
49h2, h2 a {
50  color: #edc951;
51}
52
53h2 a {
54  text-decoration: none;
55}
56
57
58/* Article headlines */
59
60#headlines {
61  list-style: none;
62  padding-left: 0;
63  width: 75%;
64}
65
66#headlines li {
67  margin-bottom: 2em;
68}
69
70.pubDate {
71  font-size: .8em;
72  color: #eb6841;
73  text-transform: uppercase;
74}
75
76#headlines .pubDate {
77  display: inline-block;
78  width: 100px;
79  font-size: .5em;
80  vertical-align: middle;
81}
82
83#headlines.archive .pubDate {
84  width: 130px;
85}
86
87.summary {
88  padding-left: 100px;
89}
90
91#headlines.archive .summary {
92  padding-left: 130px;
93}
94
95
96/* "You are logged in..." header on admin pages */
97
98#adminHeader {
99  width: 940px;
100  padding: 0 10px;
101  border-bottom: 1px solid #00a0b0;
102  margin: -30px 0 40px 0;
103  font-size: 0.8em;
104}
105
106
107/* Style the form with a coloured background, along with curved corners and a drop shadow */
108
109form {
110  margin: 20px auto;
111  padding: 40px 20px;
112  overflow: auto;
113  background: #fff4cf;
114  border: 1px solid #666;
115  -moz-border-radius: 5px;
116  -webkit-border-radius: 5px;  
117  border-radius: 5px;
118  -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
119  -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
120  box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
121}
122
123
124/* Give form elements consistent margin, padding and line height */
125
126form ul {
127  list-style: none;
128  margin: 0;
129  padding: 0;
130}
131
132form ul li {
133  margin: .9em 0 0 0;
134  padding: 0;
135}
136
137form * {
138  line-height: 1em;
139}
140
141
142/* The field labels */
143
144label {
145  display: block;
146  float: left;
147  clear: left;
148  text-align: right;
149  width: 15%;
150  padding: .4em 0 0 0;
151  margin: .15em .5em 0 0;
152}
153
154
155/* The fields */
156
157input, select, textarea {
158  display: block;
159  margin: 0;
160  padding: .4em;
161  width: 80%;
162}
163
164input, textarea, .date {
165  border: 2px solid #666;
166  -moz-border-radius: 5px;
167  -webkit-border-radius: 5px;    
168  border-radius: 5px;
169  background: #fff;
170}
171
172input {
173  font-size: .9em;
174}
175
176select {
177  padding: 0;
178  margin-bottom: 2.5em;
179  position: relative;
180  top: .7em;
181}
182
183textarea {
184  font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
185  font-size: .9em;
186  height: 5em;
187  line-height: 1.5em;
188}
189
190textarea#content {
191  font-family: "Courier New", courier, fixed;
192}
193  
194
195/* Place a border around focused fields */
196
197form *:focus {
198  border: 2px solid #7c412b;
199  outline: none;
200}
201
202
203/* Display correctly filled-in fields with a green background */
204
205input:valid, textarea:valid {
206  background: #efe;
207}
208
209
210/* Submit buttons */
211
212.buttons {
213  text-align: center;
214  margin: 40px 0 0 0;
215}
216
217input[type="submit"] {
218  display: inline;
219  margin: 0 20px;
220  width: 12em;
221  padding: 10px;
222  border: 2px solid #7c412b;
223  -moz-border-radius: 5px;
224  -webkit-border-radius: 5px;  
225  border-radius: 5px;
226  -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
227  -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
228  box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
229  color: #fff;
230  background: #ef7d50;
231  font-weight: bold;
232  -webkit-appearance: none;
233}
234
235input[type="submit"]:hover, input[type="submit"]:active {
236  cursor: pointer;
237  background: #fff;
238  color: #ef7d50;
239}
240
241input[type="submit"]:active {
242  background: #eee;
243  -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset;
244  -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset;
245  box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset;
246}
247
248
249/* Tables */
250
251table {
252  width: 100%;
253  border-collapse: collapse;
254}
255
256tr, th, td {
257  padding: 10px;
258  margin: 0;
259  text-align: left;
260}
261
262table, th {
263  border: 1px solid #00a0b0;
264}
265
266th {
267  border-left: none;
268  border-right: none;
269  background: #ef7d50;
270  color: #fff;
271  cursor: default;
272}
273
274tr:nth-child(odd) {
275  background: #fff4cf;
276}
277
278tr:nth-child(even) {
279  background: #fff;
280}
281
282tr:hover {
283  background: #ddd;
284  cursor: pointer;
285}
286
287
288/* Status and error boxes */
289
290.statusMessage, .errorMessage {
291  font-size: .8em;
292  padding: .5em;
293  margin: 2em 0;
294  -moz-border-radius: 5px;
295  -webkit-border-radius: 5px;
296  border-radius: 5px; 
297  -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
298  -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
299  -box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
300}
301
302.statusMessage {
303  background-color: #2b2;
304  border: 1px solid #080;
305  color: #fff;
306}
307
308.errorMessage {
309  background-color: #f22;
310  border: 1px solid #800;
311  color: #fff;
312}

ما قصد نداریم به بررسی جزییات این کد CSS بپردازیم چون بحثی طولانی و خارج از حیطه این مقاله آموزش ساخت وب‌سایت خبری با PHP و MySQL است. به طور خلاصه این فایل به استایل‌بندی مواردی مانند لی‌آوت صفحه، رنگ‌ها، فونت‌ها، فرم‌ها، جداول و موارد دیگر می‌پردازد. در نهایت در پایان راهنمای طراحی سایت خبری با PHP یک لوگو برای سایت خبری خود طراحی می‌کنیم که شکل آن به صورت زیر است.

طراحی سایت خبری با PHP

این لوگو را در پوشه images در داخل پوشه cms ذخیره کرده و نام آن را logo.jpg می‌گذاریم.

سخن پایانی: طراحی سایت خبری با PHP

به این ترتیب این آموزش سایت خبری با PHP به پایان می‌رسد. برای امتحان کردن این سایت کافی است مرورگر وب خود را باز کرده و به URL مبنای CMS مراجعه کنید که در این مورد http://localhost/cms است. روی گزینه Site Admin در فوتر کلیک کرده، لاگین کنید و برخی مقالات را به سایت اضافه نمایید. سپس روی لوگوی سایت کلیک کنید تا به صفحه اصلی بازگردید و آن‌ها را مشاهده کنید.

به این ترتیب ما در این راهنما یک سیستم مدیریت محتوای ساده را از صفر صد تا با استفاده از زبان برنامه‌نویسی PHP و سیستم مدیریت پایگاه داده MySQL ایجاد کردیم. با این که این CMS بسیار ابتدایی است، اما امیدواریم یک نقطه شروع برای ساخت وب‌سایت‌های مبتنی بر CMS از سوی شما باشد. در ادامه برخی از مواردی که می‌توانید روی اضافه کردنشان به این سایت خبری وقت بگذارید را لیست کرده‌ایم.

  • امکان صفحه‌بندی در صفحه بایگانی مقالات تا مدیریت صدها مقاله به آسانی صورت گیرد.
  • یک ادیتور WYSIWYG برای ویرایش بهتر و آسان‌تر مقالات
  • قابلیت آپلود تصاویر
  • قابلیت پیش‌نمایش مقاله تا مدیر سایت پیش از منتشر کردن مقاله بتواند آن را ببیند و نواقص احتمالی را برطرف کند.
  • دسته‌بندی و تگ‌گذاری برای مقالات
  • یکپارچه‌سازی با mod_rewrite وب‌سرور آپاچی برای ساخت permalink-های قابل خواندن از سوی انسان برای مقالات.
  • سیستم نظردهی کاربران

امیدواریم این آموزش مورد توجه شما قرار گرفته باشد. شما می‌توانید هر گونه سؤال یا دیدگاه و پیشنهاد خود را در بخش نظرات این نوشته با ما و دیگر خوانندگان مجله فرادرس در میان بگذارید.

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
elated
۱ دیدگاه برای «آموزش طراحی سایت خبری با PHP — رایگان و از صفر تا صد»

سلام خسته نباشید
یه مشکلی پیش اومده . تو قسمت 7 توی کد اول بخش include , کد اول که header هست رو کپی کردم و تو php قرار دادم , ابن ارور رو میده که result$ تعریف نشده است .
اگه ممکن راه حل این مشکل رو توضیح بدید .
ممنون

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *