طراحی سایت 20 بازدید

ساخت یک سیستم مدیریت محتوا (CMS) برای توسعه‌دهندگان PHP مبتدی می‌تواند کار سختی به نظر برسد. با این حال، می‌توان این کار را به شکل ساده‌تر هم انجام داد. در این مطلب آموزشی از آکادمی کاپریلا، به شما نشان خواهیم داد که چگونه می‌توانید یک CMS (Content Management System) اساسی اما کاملاً کاربردی را در چند ساعت بسازید. برخلاف آنچه تصور می‌کنید، این موضوع به سادگی امکانپذیر است. همچنین در این مقاله، نحوه ایجاد پایگاه داده‌ها و جداول MySQL شامل نحوه کار با موضوعات PHP، ثابت‌ها (Constant)، Includes ،Sessions و ویژگی های دیگر را آموزش خواهیم داد. به‌علاوه می‌بینیم که چگونه منطق کسب و کار را از ارائه جدا کرده و چگونه کد PHP خود را ایمن‌تر کنیم. موضوعات ارائه شده در این مقاله به همین‌جا ختم نشده و بسیاری موارد دیگر را می‌توانید در آن یاد بگیرید. برای ساخت CMS با PHP ادامه این مطلب را از دست ندهید.

آنچه می خوانید: پنهان کردن فهرست

مراحل ساخت CMS با PHP

برای ساخت CMS، باید مراحل زیر را به درستی دنبال کنید. این مراحل شامل موارد زیر است که ادامه هر کدام را به طور کامل بررسی خواهیم کرد:

  • ایجاد پایگاه داده (Database)
  • ساخت جدول پایگاه داده مقالات (Articles database table)
  • ایجاد یک فایل پیکربندی
  • ساخت Article class
  • نوشتن فرانت اند اسکریپت php
  • نوشتن بک اند اسکریپت php
  • ایجاد قالب‌های فرانت-‌اند
  • ایجاد قالب‌های بک-اند
  • ایجاد Stylesheet و تصویر لوگو

همچنین آنچه خواهید ساخت، یک CMS با قابلیت‌های زیر است:

Front end:

  • صفحه خانه، لیست 5 مقاله آخر
  • صفحه لیست مقالات
  • صفحه نمایش مقاله

Back end:

  • بخش ورود/خروج ادمین
  • لیست تمام مقالات
  • اضافه کردن مقاله جدید
  • ویرایش مقالات موجود
  • حذف مقالات موجود

هر مقاله شامل یک عنوان، خلاصه مطلب و تاریخ انتشار هم خواهد بود.

مرحله اول: ایجاد پایگاه داده (Database)

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

1. برنامه “MySQL Client” را اجرا کنید.

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

mysql -u username -p

پس از درخواست، رمز عبور MySQL خود را وارد کنید.

2. پایگاه داده را ایجاد کنید.

در mysql و  قسمت “Prompt”، عبارت “create database cms” را تایپ کرده و سپس دکمه “Enter” را فشار دهید.

3. از برنامه mysql client خارج شوید.

در mysql و  قسمت “Prompt”، عبارت “exit” را تایپ کرده و سپس “Enter” را فشار دهید.

اکنون یک پایگاه داده جدید و خالی ایجاد کرده‌اید که می‌توانید جداول و محتوای پایگاه داده خود را در آن قرار دهید.

پیشنهاد آموزشی مرتبط:

مرحله دوم: ساخت جدول پایگاه داده مقالات (Articles database table)

سیستم CMS ساده ما فقط یک جدول پایگاه داده دارد و آن متعلق به “Articles” یا مقالات است. همانطور که از نام این بخش مشخص است، همه مقالات سیستم را درون خود نگه می‌دارد. بیایید برای جدول، یک طرح ایجاد کنیم. این طرح جدول، انواع داده‌هایی را که در آن وجود دارد و همچنین سایر اطلاعات مربوط به آن را توصیف می‌کند. یک فایل متنی به نام “tables.sql” ایجاد کرده و کد زیر را به فایل اضافه کنید:

DROP TABLE IF EXISTS articles;
CREATE TABLE articles
(
  id              smallint unsigned NOT NULL auto_increment,
  publicationDate date NOT NULL,                              # When the article was published
  title           varchar(255) NOT NULL,                      # Full title of the article
  summary         text NOT NULL,                              # A short summary of the article
  content         mediumtext NOT NULL,                        # The HTML content of the article

  PRIMARY KEY     (id)
);

کد بالا، طرحی را برای جدول “Articles” تعریف می‌کند. این کد به زبان SQL نوشته شده است که برای ایجاد و دست‌کاری پایگاه‌های داده در MySQL (و اکثر سیستم‌های پایگاه داده دیگر) استفاده می‌شود. بیایید کد بالا را کمی تجزیه کنیم:

ساخت جدول “Articles”

عبارت “DROP TABLE IF EXISTS articles”، هر جدول article (و یا داده) موجود را حذف می‌کند. انجام این کار به این دلیل است که نمی‌توانیم جدولی را با همان نام جدول موجود تعریف کنیم. عبارت “CREATE TABLE articles ( )”، جدول article جدید را ایجاد می‌کند. موارد داخل پرانتز، ساختار داده‌ها را در جدول تعریف می‌کند که در زیر توضیح داده شده است.

اختصاص ID انحصاری به هر مقاله (article)

بعد از ایجاد جدول، اکنون آماده تعریف ساختار آن هستیم. یک جدول، از تعدادی فیلد (که ستون نیز نامیده می‌شود) تشکیل شده است. هر فیلد دارای نوع خاصی از اطلاعات در مورد هر مقاله است. ابتدا یک فیلد “id” ایجاد می‌کنیم. این فیلد حاوی عبارت “smallint unsigned” است؛ به این معنی که می‌تواند اعداد کامل از 0 تا 65,535 را شامل شود. این مورد به CMS ما اجازه می‌دهد تا 65,535 مقاله را در خود جای دهد. همچنین ویژگی تحت عنوان “NOT NULL” را مشخص می‌کنیم، به این معنی که فیلد نمی‌تواند خالی باشد. همچنین ویژگی دیگری را با عبارت “auto_incremen” را اضافه می‌کنیم که به MySQL می‌گوید هنگام ایجاد رکورد مقاله، یک مقدار جدید و منحصربه‌فرد به فیلد id مقاله اختصاص دهد. این مقادیر برای ارجاع به مقاله‌ای که قصد ویرایش یا نمایش آن را داشته باشیم، کاربرد دارد.

افزودن فیلد تاریخ انتشار (PublicationDate)

خط بعدی فیلد “publicationDate” را ایجاد می‌کند که تاریخ انتشار هر مقاله را در خود دارد.

افزودن فیلد عنوان (Title)

در مرحله بعد، فیلد عنوان یا “Title” را برای افزودن عنوان هر مقاله ایجاد می‌کنیم. عبارت “varchar(255)” در این بخش‌ به این معنی است که عنوان شما می‌تواند عبارتی با حداکثر 255 کاراکتر را ذخیره کند.

افزودن فیلدهای خلاصه (Summary) و محتوا (Content)

دو قسمت آخر “Summary” و “Content” به ترتیب خلاصه‌ای از مقاله و محتوای HTML مقاله را در خود جای می‌دهند. بخش Summary یک نوع داده متنی است (که می‌تواند تا 65,535 کاراکتر را در خود جای دهد) و محتوا دارای یک نوع داده “Mediumtext” ​​است (که می‌تواند تا 16,777,215 کاراکتر را در خود جای دهد).

افزودن فیلد “Primary Key”

آخرین خط داخل دستور CREATE TABLE، یک کلید برای جدول تعریف می‌کند. این کلید (Key) همچنین تحت عنوان “index” نیز نامیده شده و به عبارت ساده، یافتن داده‌ها را در جدول سریع‌تر می‌کند. ما فیلد “ID” را به یک “PRIMARY KEY” تبدیل می‌کنیم. هر جدول فقط می‌تواند یک کلید اصلی داشته باشد؛ این کلیدی است که به طور انحصاری هر رکورد را در جدول شناسایی می‌کند. علاوه بر این، MySQL با افزودن این کلید می‌تواند یک مقاله را بر اساس شناسه آن خیلی سریع بازیابی کند.

حالا که طرح جدول خود را ایجاد کردیم، باید آن را در MySQL بارگذاری کنیم تا خود جدول ایجاد شود. ساده‌ترین راه برای انجام این کار این است که یک پنجره ترمینال را باز کرده و به پوشه حاوی فایل “tables.sql” تغییر دهید. سپس این دستور را اجرا کنید:

mysql -u username -p cms < tables.sql

در این عبارت، “Username” همان نام کاربری شما در MySQL بوده و “cms” نام پایگاه داده‌‌‌ای است که در مرحله اول ایجاد کردید. سپس باید رمز عبور خود را وارد کنید. سپس MySQL کد را در فایل tables.sql بارگیری و اجرا کرده و جدول مقالات را در پایگاه داده cms ایجاد می‌کند.

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

اکنون که پایگاه داده خود را ایجاد کرده‌ایم، آماده شروع نوشتن کد PHP هستیم. این کار را با ایجاد یک فایل پیکربندی برای ذخیره تنظیمات کاربردی مختلف برای CMS خود شروع می‌کنیم. این فایل توسط تمام فایل‌های اسکریپت موجود در CMS ما استفاده خواهد شد. ابتدا یک پوشه cms در جایی در وب‌سایت رایانه خود ایجاد کنید تا تمام فایل‌های مربوط به CMS را در خود نگه دارد. اگر “XAMPP” را اجرا می‌کنید، وب‌سایت محلی در یک پوشه “htdocs” در داخل پوشه “XAMPP” شما قرار می‌گیرد. در غیر این صورت، می‌توانید یک وب‌سایت کاملاً جدید فقط برای CMS خود ایجاد کرده و همه فایل‌ها را در پوشه اصلی سند آن وب سایت جدید قرار دهید. داخل پوشه cms، فایلی به نام “config.php” با کد زیر ایجاد کنید:

<?php
ini_set( "display_errors", true );
date_default_timezone_set( "Australia/Sydney" );  // http://www.php.net/manual/en/timezones.php
define( "DB_DSN", "mysql:host=localhost;dbname=cms" );
define( "DB_USERNAME", "username" );
define( "DB_PASSWORD", "password" );
define( "CLASS_PATH", "classes" );
define( "TEMPLATE_PATH", "templates" );
define( "HOMEPAGE_NUM_ARTICLES", 5 );
define( "ADMIN_USERNAME", "admin" );
define( "ADMIN_PASSWORD", "mypass" );
require( CLASS_PATH . "/Article.php" );

function handleException( $exception ) {
  echo "Sorry, a problem occurred. Please try later.";
  error_log( $exception->getMessage() );
}

set_exception_handler( 'handleException' );
?>

در ادامه، اجزای این کد دستوری را بررسی می‌کنیم:

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

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

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

از آنجایی که CMS ما از تابع date () PHP استفاده می‌کند، باید منطقه زمانی سرور خود را به PHP بگوییم (در غیر این صورت PHP یک پیام هشدار را نمایش می‌دهد). به عنوان مثال، در کد دستوری بالا، منطقه زمانی روی “Australia/Sydney” تنظیم شده است و شما می‌توانید آن را بر اساس منطقه خود تغییر دهید.

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

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

تعیین مسیر (Path)ها

در کد دستوری بالا، ما دو نام مختلف را در فایل پیکربندی قرار دادیم. “CLASS_PATH”، که مسیر فایل‌های کلاس است، و “TEMPLATE_PATH” جایی که اسکریپت ما باید به دنبال فایل‌های قالب HTML بگردد.

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

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

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

عبارت‌های “ADMIN_USERNAME” و “ADMIN_PASSWORD” حاوی جزئیات ورود به سیستم کاربر ادمین CMS هستند. شما می‌توانید این بخش‌ها را نیز مطابق با نام کاربری و رمز عبور خود تغییر دهید.

افزودن کلاس مقاله (Article class)

از آنجایی که فایل کلاس Article که در ادامه ایجاد خواهیم کرد، برای همه اسکریپت‌های ما مورد نیاز است، آن را در اینجا اضافه می‌کنیم.

ایجاد یک کنترل‌کننده استثنا

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

مرحله چهارم: ساخت Article class

با پشت سر گذاشتن مراحل بالا، اکنون آماده ساختن کلاس Article PHP هستید. این تنها کلاس در CMS ما است و سختی‌های ذخیره‌سازی مقالات در پایگاه داده و همچنین بازیابی مقالات از پایگاه داده را مدیریت می‌کند. هنگامی که این کلاس را ساختیم، ایجاد، به‌روزرسانی، بازیابی و حذف مقالات برای دیگر اسکریپت‌های CMS ما بسیار آسان خواهد بود. داخل پوشه cms خود، یک پوشه classes ایجاد کنید. داخل این پوشه جدید، یک فایل جدید به نام Article.php ایجاد کرده و کد زیر را در آن قرار دهید:

<?php

/**
 * Class to handle articles
 */

class Article
{

  // Properties

  /**
  * @var int The article ID from the database
  */
  public $id = null;

  /**
  * @var int When the article was published
  */
  public $publicationDate = null;

  /**
  * @var string Full title of the article
  */
  public $title = null;

  /**
  * @var string A short summary of the article
  */
  public $summary = null;

  /**
  * @var string The HTML content of the article
  */
  public $content = null;


  /**
  * Sets the object's properties using the values in the supplied array
  *
  * @param assoc The property values
  */

  public function __construct( $data=array() ) {
    if ( isset( $data['id'] ) ) $this->id = (int) $data['id'];
    if ( isset( $data['publicationDate'] ) ) $this->publicationDate = (int) $data['publicationDate'];
    if ( isset( $data['title'] ) ) $this->title = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data['title'] );
    if ( isset( $data['summary'] ) ) $this->summary = preg_replace ( "/[^\.\,\-\_\'\"\@\?\!\:\$ a-zA-Z0-9()]/", "", $data['summary'] );
    if ( isset( $data['content'] ) ) $this->content = $data['content'];
  }


  /**
  * Sets the object's properties using the edit form post values in the supplied array
  *
  * @param assoc The form post values
  */

  public function storeFormValues ( $params ) {

    // Store all the parameters
    $this->__construct( $params );

    // Parse and store the publication date
    if ( isset($params['publicationDate']) ) {
      $publicationDate = explode ( '-', $params['publicationDate'] );

      if ( count($publicationDate) == 3 ) {
        list ( $y, $m, $d ) = $publicationDate;
        $this->publicationDate = mktime ( 0, 0, 0, $m, $d, $y );
      }
    }
  }


  /**
  * Returns an Article object matching the given article ID
  *
  * @param int The article ID
  * @return Article|false The article object, or false if the record was not found or there was a problem
  */

  public static function getById( $id ) {
    $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
    $sql = "SELECT *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles WHERE id = :id";
    $st = $conn->prepare( $sql );
    $st->bindValue( ":id", $id, PDO::PARAM_INT );
    $st->execute();
    $row = $st->fetch();
    $conn = null;
    if ( $row ) return new Article( $row );
  }


  /**
  * Returns all (or a range of) Article objects in the DB
  *
  * @param int Optional The number of rows to return (default=all)
  * @return Array|false A two-element array : results => array, a list of Article objects; totalRows => Total number of articles
  */

  public static function getList( $numRows=1000000 ) {
    $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
    $sql = "SELECT SQL_CALC_FOUND_ROWS *, UNIX_TIMESTAMP(publicationDate) AS publicationDate FROM articles
            ORDER BY publicationDate DESC LIMIT :numRows";

    $st = $conn->prepare( $sql );
    $st->bindValue( ":numRows", $numRows, PDO::PARAM_INT );
    $st->execute();
    $list = array();

    while ( $row = $st->fetch() ) {
      $article = new Article( $row );
      $list[] = $article;
    }

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


  /**
  * Inserts the current Article object into the database, and sets its ID property.
  */

  public function insert() {

    // Does the Article object already have an ID?
    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 );

    // Insert the Article
    $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
    $sql = "INSERT INTO articles ( publicationDate, title, summary, content ) VALUES ( FROM_UNIXTIME(:publicationDate), :title, :summary, :content )";
    $st = $conn->prepare ( $sql );
    $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT );
    $st->bindValue( ":title", $this->title, PDO::PARAM_STR );
    $st->bindValue( ":summary", $this->summary, PDO::PARAM_STR );
    $st->bindValue( ":content", $this->content, PDO::PARAM_STR );
    $st->execute();
    $this->id = $conn->lastInsertId();
    $conn = null;
  }


  /**
  * Updates the current Article object in the database.
  */

  public function update() {

    // Does the Article object have an ID?
    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 );
   
    // Update the Article
    $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
    $sql = "UPDATE articles SET publicationDate=FROM_UNIXTIME(:publicationDate), title=:title, summary=:summary, content=:content WHERE id = :id";
    $st = $conn->prepare ( $sql );
    $st->bindValue( ":publicationDate", $this->publicationDate, PDO::PARAM_INT );
    $st->bindValue( ":title", $this->title, PDO::PARAM_STR );
    $st->bindValue( ":summary", $this->summary, PDO::PARAM_STR );
    $st->bindValue( ":content", $this->content, PDO::PARAM_STR );
    $st->bindValue( ":id", $this->id, PDO::PARAM_INT );
    $st->execute();
    $conn = null;
  }


  /**
  * Deletes the current Article object from the database.
  */

  public function delete() {

    // Does the Article object have an ID?
    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 );

    // Delete the Article
    $conn = new PDO( DB_DSN, DB_USERNAME, DB_PASSWORD );
    $st = $conn->prepare ( "DELETE FROM articles WHERE id = :id LIMIT 1" );
    $st->bindValue( ":id", $this->id, PDO::PARAM_INT );
    $st->execute();
    $conn = null;
  }

}

?>

این فایل بسیار طولانی است، اما وقتی آن را تجزیه و بررسی کنیم، چیزهای نسبتاً ساده‌ای را در آن می‌بینیم. بیایید نگاهی به هر بخش از کد بیندازیم:

تعریف کلاس و خصوصیات (Properties)

قبل از هر چیز، کلاس مقاله خود با کد زیر تعریف می‌کنیم:

class Article
{ 

همه چیز بعد از این خط تا بسته شدن پرانتز در انتهای فایل، شامل همه کدهایی است که Class Article را تشکیل می‌دهد. پس از شروع تعریف کلاس، ویژگی‌های کلاس از جمله publicationDate$، $id و ... را تعیین می‌کنیم. هر هدف مقاله‌ را که ایجاد می‌کنیم، در این قسمت ذخیره می‌شود. همان طور که می‌بینید، نام ویژگی‌ها منعکس‌کننده نام فیلدها در جدول پایگاه داده مقالات (مرحله دوم) است.

سازنده (The Constructor)

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

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

$this->propertyName means: “The property of this object that has the name “$propertyName“.

متوجه خواهید شد که این متد، داده‌ها را قبل از اینکه در ویژگی‌ها (Properties) ذخیره کند، فیلتر می‌کند. ویژگی‌های id و publicationDate نیز فیلتر می‌شوند، زیرا این مقادیر همیشه باید شامل اعداد صحیح باشند. عنوان (title) و خلاصه (summary) نیز با استفاده از یک عبارت منظم فیلتر می‌شوند تا فقط محدوده خاصی از کاراکترها را مجاز کنند. این روش امنیتی خوبی است که داده‌ها را به محض وارد کردن فیلتر می‌کند و فقط به مقادیر و کاراکترهای قابل قبول اجازه عبور داده می‌شود. با این حال، ما ویژگی محتوا (content) را فیلتر نمی‌کنیم؛ چرا که احتمالاً کاربر ادمین می‌خواهد از طیف گسترده‌ای از کاراکترها و همچنین نشانه‌گذاری HTML در محتوای مقاله استفاده کند. اگر دامنه کاراکترهای مجاز در محتوا را محدود کنیم، مفید بودن CMS را برای سرپرست محدود کرده‌ایم. همچنین اگر با محتوای تولید شده توسط کاربر، مانند نظرات یا پست‌های اشتراکی سر و کار داشتید، باید مراقب باشید و فقط اجازه دهید از HTML  ایمن استفاده شود. یک ابزار واقعاً عالی برای این کار، HTML Purifier است که ورودی HTML را به طور کامل تجزیه و تحلیل کرده و تمام کدهای بالقوه مخرب را حذف می‌کند.

پیشنهاد آموزشی مرتبط:

storeFormValues()

متد بعدی یعنی “storeFormValues()”، شبیه نمونه قبلی است که یک آرایه ارائه‌شده از داده‌ها را در ویژگی‌های هدف ذخیره می‌کند. تفاوت اصلی این متد با متد قبلی این است که “storeFormValues()” می‌تواند داده‌ها را در قالبی که از طریق فرم‌های New Article و Edit Article ارسال می‌شود (در مراحل بعد آن‌ها را ایجاد خواهیم کرد)، مدیریت کند. این قسمت به طور خاص، می‌تواند تاریخ‌های انتشار را با فرمت YYYY-MM-DD مدیریت و تاریخ را با قالب UNIX مناسب برای ذخیره تبدیل کند. هدف از این متد این است که اسکریپت‌های مدیریت بتوانند داده‌های ارسال شده توسط فرم‌ها را ذخیره کنند. تنها کاری که آن‌ها باید انجام دهند فراخوانی “storeFormValues()” است که در آرایه داده‌های فرم ارسال می‌شود.

getById()

حال به متدهایی می‌رسیم که به پایگاه داده MySQL دسترسی دارند. اولین مورد، getById()، یک آرگومان ID مقاله ($id) را می‌پذیرد، سپس رکورد مقاله را با آن شناسه از جدول articles بازیابی و در یک شی مقاله جدید ذخیره می‌کند. معمولاً برای اینکه یک متد را فراخوانی کنید، نخست باید یک هدف را ایجاد یا بازیابی کرده و سپس متد را روی آن فراخوانی ‌کنید. با این حال، از آنجایی که این متد یک هدف در Article جدید را برمی‌گرداند، اگر بتوان این متد را مستقیماً توسط کد فراخوانی کرد، مفید خواهد بود. در غیر این صورت، هر بار باید یک هدف جدید ایجاد کنیم. برای اینکه بتوانیم متد را بدون نیاز به هدف فراخوانی کنیم، باید کلمه کلیدی ثابت (static) را به تعریف متد اضافه کنیم. این به متد اجازه می‌دهد تا مستقیماً بدون تعیین هدف فراخوانی شود:

  public static function getById( $id ) {

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

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

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

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

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

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

عبارت “SELECT” در کد دستوری بالا، همه فیلدهای رکورد جدول مقالات را که با فیلد id داده شده مطابقت دارد، بازیابی می‌کند. همچنین فیلد “publicationDate” را به‌جای قالب پیش‌فرض تاریخ MySQL در قالب UNIX بازیابی می‌کند تا بتوانیم آن را به راحتی در هدف خود ذخیره کنیم. به جای قرار دادن پارامتر $id به طور مستقیم در رشته SELECT، که می‌تواند یک خطر امنیتی باشد، از “:id” استفاده می‌کنیم. این پارامتر به عنوان Placeholder شناخته می‌شود. در عرض یک دقیقه، ما یک متد PDO را فراخوانی می‌کنیم تا “$id” را به این placeholder متصل کنیم. هنگامی که دستور SELECT خود را در یک خطر قرار دادیم، دستور را طبق دستورالعمل $conn->prepare() آماده کرده، و دسته دستور به دست آمده را در متغیر $st ذخیره می‌کنیم.

اکنون با فراخوانی متد ()bindValue مقدار متغیر $id خود – یعنی شناسه مقاله‌ای که می‌خواهیم بازیابی کنیم – را به جای‌ :id خود متصل می‌کنیم. در نهایت، execute()  را برای اجرای کوئری فراخوانی می‌کنیم، سپس از fetch() برای بازیابی رکورد به عنوان یک آرایه انجمنی از نام فیلدها و مقادیر فیلد مربوطه استفاده خواهیم کرد تا در متغیر $row ذخیره شود.

بستن ارتباط

$conn = null;

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

بازگردانی هدف جدید Article

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

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

getList ()

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

$numRows؛

کد حداکثر تعداد مقالات برای بازیابی را مشخص می‌کند. ما این مقدار را به طور پیش‌فرض روی 1,000,000 قرار می‌دهیم. این پارامتر به ما اجازه می ‌دهد که مثلاً فقط 5 مقاله اول را در صفحه اصلی سایت نمایش دهیم. بسیاری از کدهای این متد، مشابه getById() است. بیایید به چند خط از این کدها نگاه کنیم:

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

توجه کنید که این بار هیچ شرط WHERE در کد بالا وجود ندارد. این به این دلیل است که ما می‌خواهیم همه مقالات را بازیابی کنیم، نه مقاله‌ای که با یک شناسه (ID) خاص مطابقت دارد. همچنین یک شرط LIMIT در کنار پارامتر “$numRows” اضافه شده تا بتوانیم به صورت اختیاری تعداد رکوردهای برگشتی را محدود کنیم. در نهایت، عبارت “SQL_CALC_FOUND_ROWS” به MySQL می‌گوید که تعداد واقعی رکوردهای بازگشتی را برگرداند. این اطلاعات برای نمایش به کاربر و همچنین برای موارد دیگری مانند صفحه‌بندی نتایج مفید است.

  $list = array();

    while ( $row = $st->fetch() ) {
      $article = new Article( $row );
      $list[] = $article;
    }

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

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

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

insert ()

متدهای باقی مانده در Article class ما با افزودن، تغییر و حذف رکوردهای مقاله در پایگاه داده سر و کار دارند. متد “insert()”، رکورد مقاله جدید را با استفاده از مقادیر ذخیره شده در هدف مقاله فعلی به جدول articles اضافه می‌کند:

  • قبل از هر چیز، این متد مطمئن می‌شود که از موضوع مورد نظر، از قبل ویژگی “$id” را ندارد. اگر یک ID داشته باشد، احتمالاً مقاله قبلاً در پایگاه داده وجود داشته، بنابراین ما نباید دوباره آن را درج کنیم.
  • این متد “SQL INSERT” را اجرا می‌کند تا رکورد را در جدول مقالات وارد کرده و از متغیرهایی برای ارسال مقادیر ویژگی به پایگاه داده استفاده می‌کند. برای تبدیل تاریخ انتشار از فرمت UNIX timestamp به قالب MySQL از تابع “MySQL FROM_UNIXTIME()” استفاده کنید.
  • پس از اجرا، این متد شناسه رکورد مقاله جدید را با استفاده از عملکرد “PDO lastInsertId()” بازیابی کرده و آن را در ویژگی “$id” موضوع ذخیره می‌کند تا به مراجع بعدی مراجعه کند.

update ()

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

delete ()

متد “delete()” کاملاً توضیحی بر عملکردش است. این متد از دستور “SQL DELETE” برای حذف مقاله ذخیره شده در جدول هدف مقالات استفاده کرده و از ویژگی “$id” موضوع برای شناسایی رکورد در جدول استفاده می‌کند. به دلایل امنیتی، عبارت LIMIT 1 را به جستار اضافه می‌کنیم تا مطمئن شویم که هر بار فقط 1 رکورد مقاله را می‌توان حذف کرد.

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

تا به این مرحله، کلاس مقاله (Article class) خود را ایجاد کرده‌ایم که کارهای سنگین را برای CMS انجام می‌دهد. سایر کدها ساده‌تر هستند. قبل از هر چیز، باید index.php، یعنی اسکریپتی که نمایش صفحات فرانت‌اند سایت را کنترل می‌کند را ایجاد کنیم. این فایل را در پوشه cms که قبلاً در ابتدای مرحله چهارم ایجاد کرده‌اید، ذخیره کنید:

<?php

require( "config.php" );
$action = isset( $_GET['action'] ) ? $_GET['action'] : "";

switch ( $action ) {
  case 'archive':
    archive();
    break;
  case 'viewArticle':
    viewArticle();
    break;
  default:
    homepage();
}


function archive() {
  $results = array();
  $data = Article::getList();
  $results['articles'] = $data['results'];
  $results['totalRows'] = $data['totalRows'];
  $results['pageTitle'] = "Article Archive | Widget News";
  require( TEMPLATE_PATH . "/archive.php" );
}

function viewArticle() {
  if ( !isset($_GET["articleId"]) || !$_GET["articleId"] ) {
    homepage();
    return;
  }

  $results = array();
  $results['article'] = Article::getById( (int)$_GET["articleId"] );
  $results['pageTitle'] = $results['article']->title . " | Widget News";
  require( TEMPLATE_PATH . "/viewArticle.php" );
}

function homepage() {
  $results = array();
  $data = Article::getList( HOMEPAGE_NUM_ARTICLES );
  $results['articles'] = $data['results'];
  $results['totalRows'] = $data['totalRows'];
  $results['pageTitle'] = "Widget News";
  require( TEMPLATE_PATH . "/homepage.php" );
}

?>

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

وارد کردن فایل کانفیگ

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

گرفتن پارامتر action

پارامتر $_GET['action']را در متغیری به نام $actionذخیره می‌کنیم تا بتوانیم بعداً از آن در اسکریپت استفاده کنیم. قبل از انجام این کار، با استفاده از “isset()” بررسی می‌کنیم که مقدار $_GET['action'] وجود داشته باشد. اگر این‌طور نیست، متغیر $action مربوطه را روی یک خط خالی (“”) قرار می‌دهیم.

تصمیم‌گیری درباره اجرای عملکرد (action)

بلوک “Switch” به پارامتر “Action” در URL نگاه می‌کند تا مشخص کند کدام عملکرد (نمایش آرشیو یا مشاهده مقاله) را انجام دهد. اگر هیچ پارامتر عملی در URL وجود نداشته باشد، اسکریپت صفحه اصلی سایت را نمایش می‌دهد.

archive()

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

viewArticle()

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

homepage()

آخرین تابع، “homepage()” است که صفحه اصلی سایت شامل لیست “HOMEPAGE_NUM_ARTICLES” مقاله را نشان می‌دهد. این تعداد در این کد دستوری به طور پیش‌فرض روی تعداد 5 تنظیم شده است. این عملکرد بسیار شبیه تابع “archive()” است، با این تفاوت که “HOMEPAGE_NUM_ARTICLES” را به متد “getList()” ارسال می‌کند تا تعداد مقالات برگردانده شده را محدود کند.

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

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

<?php

require( "config.php" );
session_start();
$action = isset( $_GET['action'] ) ? $_GET['action'] : "";
$username = isset( $_SESSION['username'] ) ? $_SESSION['username'] : "";

if ( $action != "login" && $action != "logout" && !$username ) {
  login();
  exit;
}

switch ( $action ) {
  case 'login':
    login();
    break;
  case 'logout':
    logout();
    break;
  case 'newArticle':
    newArticle();
    break;
  case 'editArticle':
    editArticle();
    break;
  case 'deleteArticle':
    deleteArticle();
    break;
  default:
    listArticles();
}


function login() {

  $results = array();
  $results['pageTitle'] = "Admin Login | Widget News";

  if ( isset( $_POST['login'] ) ) {

    // User has posted the login form: attempt to log the user in

    if ( $_POST['username'] == ADMIN_USERNAME && $_POST['password'] == ADMIN_PASSWORD ) {

      // Login successful: Create a session and redirect to the admin homepage
      $_SESSION['username'] = ADMIN_USERNAME;
      header( "Location: admin.php" );

    } else {

      // Login failed: display an error message to the user
      $results['errorMessage'] = "Incorrect username or password. Please try again.";
      require( TEMPLATE_PATH . "/admin/loginForm.php" );
    }

  } else {

    // User has not posted the login form yet: display the form
    require( TEMPLATE_PATH . "/admin/loginForm.php" );
  }

}


function logout() {
  unset( $_SESSION['username'] );
  header( "Location: admin.php" );
}


function newArticle() {

  $results = array();
  $results['pageTitle'] = "New Article";
  $results['formAction'] = "newArticle";

  if ( isset( $_POST['saveChanges'] ) ) {

    // User has posted the article edit form: save the new article
    $article = new Article;
    $article->storeFormValues( $_POST );
    $article->insert();
    header( "Location: admin.php?status=changesSaved" );

  } elseif ( isset( $_POST['cancel'] ) ) {

    // User has cancelled their edits: return to the article list
    header( "Location: admin.php" );
  } else {

    // User has not posted the article edit form yet: display the form
    $results['article'] = new Article;
    require( TEMPLATE_PATH . "/admin/editArticle.php" );
  }

}


function editArticle() {

  $results = array();
  $results['pageTitle'] = "Edit Article";
  $results['formAction'] = "editArticle";

  if ( isset( $_POST['saveChanges'] ) ) {

    // User has posted the article edit form: save the article changes

    if ( !$article = Article::getById( (int)$_POST['articleId'] ) ) {
      header( "Location: admin.php?error=articleNotFound" );
      return;
    }

    $article->storeFormValues( $_POST );
    $article->update();
    header( "Location: admin.php?status=changesSaved" );

  } elseif ( isset( $_POST['cancel'] ) ) {

    // User has cancelled their edits: return to the article list
    header( "Location: admin.php" );
  } else {

    // User has not posted the article edit form yet: display the form
    $results['article'] = Article::getById( (int)$_GET['articleId'] );
    require( TEMPLATE_PATH . "/admin/editArticle.php" );
  }

}


function deleteArticle() {

  if ( !$article = Article::getById( (int)$_GET['articleId'] ) ) {
    header( "Location: admin.php?error=articleNotFound" );
    return;
  }

  $article->delete();
  header( "Location: admin.php?status=articleDeleted" );
}


function listArticles() {
  $results = array();
  $data = Article::getList();
  $results['articles'] = $data['results'];
  $results['totalRows'] = $data['totalRows'];
  $results['pageTitle'] = "All Articles";

  if ( isset( $_GET['error'] ) ) {
    if ( $_GET['error'] == "articleNotFound" ) $results['errorMessage'] = "Error: Article not found.";
  }

  if ( isset( $_GET['status'] ) ) {
    if ( $_GET['status'] == "changesSaved" ) $results['statusMessage'] = "Your changes have been saved.";
    if ( $_GET['status'] == "articleDeleted" ) $results['statusMessage'] = "Article deleted.";
  }

  require( TEMPLATE_PATH . "/admin/listArticles.php" );
}

?>

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

پیشنهاد آموزشی مرتبط:

دوره ویدئویی آموزش پروژه محور PHP و MySQL – پیاده سازی انواع لاگین و ثبت نام: اینجا را کلیک کنید (+).

شروع User Session

سمت بالای اسکریپت را “session_start” می‌نامیم. این تابع PHP یک session جدید را برای کاربر شروع می‌کند، که می‌توانیم از آن برای ردیابی اینکه آیا کاربر وارد شده یا خیر، استفاده کنیم. اگر session از قبل برای این کاربر وجود داشته باشد، PHP به طور خودکار آن را برداشته و از آن استفاده می‌کند.

گرفتن پارامتر action و متغیر username session

سپس پارامتر$_GET['action']را در متغیری به نام $action و session متغیر $_SESSION['username']  را در $username قرار می‌دهیم تا بتوانیم بعداً از این مقادیر در اسکریپت استفاده کنیم. قبل از انجام این کار، با استفاده از “isset()” بررسی می‌کنیم که این موارد وجود داشته باشند. اگر آن‌ها وجود نداشته باشند، متغیر مربوطه را روی یک خط خالی (“”) قرار می‌دهیم.

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

کاربر نباید اجازه انجام کاری را داشته باشد مگر اینکه به عنوان یک ادمین وارد سیستم شده باشد. بنابراین، کار بعدی ما این است که “$username” را بررسی کنیم تا ببینیم آیا session، موردی برای کلید نام کاربری دارد، که از آن برای نشان دادن ورود کاربر به سیستم استفاده می‌کنیم.

تصمیم‌گیری درباره اجرای یک عملکرد (action)

بلوک “Switch” بسیار شبیه به همین بلوک در index.php عمل می‌کند. یعنی تابع مناسب را بر اساس مقدار پارامتر action URL فراخوانی می‌کند. عملکرد پیش‌فرض، نمایش لیست مقالات در CMS است.

Login ()

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

Logout ()

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

newArticle()

این تابع به کاربر اجازه می‌دهد یک مقاله جدید ایجاد کند. اگر کاربر به‌تازگی فرم “new article” را پست کرده باشد، تابع یک موضوع Article جدید ایجاد کرده، داده‌های فرم را با فراخوانی “storeFormValues()” در موضوع ذخیره می‌کند، مقاله را با فراخوانی “insert()” در پایگاه داده وارد کرده و به عقب هدایت می‌کند. لیست مقاله، پیام وضعیت “Changes Saved” را نشان می‌دهد. اگر کاربر هنوز فرم “New Article” را پست نکرده باشد، تابع یک موضوع مقاله خالی جدید بدون مقدار ایجاد کرده، سپس از الگوی “editArticle.php” برای نمایش فرم ویرایش مقاله با استفاده از این موضوع خالی Article استفاده می‌کند.

editArticle()

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

deleteArticle()

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

listArticles()

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

مرحله هفتم: ایجاد قالب‌های فرانت‌اند

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

فایل‌های include

پوشه‌ای به نام “templates” در داخل پوشه “cms” خود ایجاد کنید. حالا یک پوشه به نام “include” در داخل پوشه “templates” ایجاد کنید. در این پوشه ما نشانه‌گذاری سرصفحه (header) و پاورقی (footer) را که در هر صفحه از سایت رایج است، قرار می‌دهیم تا از قرار دادن آن در هر فایل الگو جلوگیری کنیم. یک فایل جدید به نام “header.php” در پوشه include خود با کد زیر ایجاد کنید:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title><?php echo htmlspecialchars( $results['pageTitle'] )?></title>
    <link rel="stylesheet" type="text/css" href="style.css" />
  </head>
  <body>
    <div id="container">

      <a href="."><img id="logo" src="images/logo.jpg" alt="Widget News" /></a>

همان‌طور که می‌بینید، این کد به سادگی نشانه‌گذاری برای شروع صفحه HTML را نمایش می‌دهد. از متغیر “$results[‘pageTitle’]” ارسال شده از اسکریپت اصلی (یعنی admin.php یا index.php) برای تنظیم عنصر “title” (عنوان) استفاده کرده و همچنین به یک “style.css” لینک می‌دهد. سپس، یک فایل به نام “footer.php” در همان پوشه ایجاد کنید:

 <div id="footer">
        Widget News &copy; 2011. All rights reserved. <a href="admin.php">Site Admin</a>
      </div>

    </div>
  </body>
</html>

این نشانه‌گذاری، هر صفحه HTML در سیستم را به پایان می‌رساند.

homepage.php

حالا به عقب برگشته و در پوشه “template”، یک فایل template به نام “homepage.php” با کد زیر ایجاد کنید:

<?php include "templates/include/header.php" ?>

      <ul id="headlines">

<?php foreach ( $results['articles'] as $article ) { ?>

        <li>
          <h2>
            <span class="pubDate"><?php echo date('j F', $article->publicationDate)?></span><a href=".?action=viewArticle&amp;articleId=<?php echo $article->id?>"><?php echo htmlspecialchars( $article->title )?></a>
          </h2>
          <p class="summary"><?php echo htmlspecialchars( $article->summary )?></p>
        </li>

<?php } ?>

      </ul>

      <p><a href="./?action=archive">Article Archive</a></p>

<?php include "templates/include/footer.php" ?>

این الگو، سرفصل‌های مقاله را در صفحه اصلی به صورت لیستی نامرتب نمایش می‌دهد. در حقیقت این الگو در موضوعات Article ذخیره شده در “$results[‘articles’]” چرخیده و تاریخ انتشار، عنوان و خلاصه هر مقاله را نمایش می‌دهد. این عنوان به «.» (index.php) پیوند داده شده است. (انتقال action=viewArticle، شناسه مقاله، در URL. به بازدیدکننده اجازه می‌دهد تا با کلیک بر روی عنوان مقاله، بتواند به مقاله دسترسی پیدا کند و آن را بخواند. این الگو همچنین دارای لینک به آرشیو مقاله (“./?action=archive”) است.

archive.php

حالا یک فایل الگو “archive.php” را در پوشه templates ایجاد کنید:

<?php include "templates/include/header.php" ?>

      <h1>Article Archive</h1>

      <ul id="headlines" class="archive">

<?php foreach ( $results['articles'] as $article ) { ?>

        <li>
          <h2>
            <span class="pubDate"><?php echo date('j F Y', $article->publicationDate)?></span><a href=".?action=viewArticle&amp;articleId=<?php echo $article->id?>"><?php echo htmlspecialchars( $article->title )?></a>
          </h2>
          <p class="summary"><?php echo htmlspecialchars( $article->summary )?></p>
        </li>

<?php } ?>

      </ul>

      <p><?php echo $results['totalRows']?> article<?php echo ( $results['totalRows'] != 1 ) ? 's' : '' ?> in total.</p>

      <p><a href="./">Return to Homepage</a></p>

<?php include "templates/include/footer.php" ?>

این الگو آرشیوی از تمام مقالات موجود در CMS را نمایش می‌دهد و همانطور که می‌بینید، تقریباً مشابه homepage.php است. یک کلاس archive CSS را به لیست نامرتب اضافه می‌کند تا بتوانیم کمی تغییرات ایجاد کرده و به لیست صفحه اصلی کمی نظم دهیم. همچنین عدد سال را به تاریخ انتشار مقاله اضافه می‌کند زیرا ممکن است آرشیو به چند سال قبل بازگردد. این صفحه همچنین شامل تعداد کل مقالات موجود در پایگاه داده است که از طریق “$results[‘totalRows’]” بازیابی شده است. در نهایت، به جای لینک آرشیو در پایین صفحه، یک لینک “Return to Homepage” دارد.

viewArticle.php

آخرین قالب فرانت-اند، یک مقاله را به کاربر نمایش می‌دهد. یک فایل به نام “viewArticle.php” در پوشه templates خود ایجاد کنید و markup زیر را اضافه کنید:

<?php include "templates/include/header.php" ?>

      <h1 style="width: 75%;"><?php echo htmlspecialchars( $results['article']->title )?></h1>
      <div style="width: 75%; font-style: italic;"><?php echo htmlspecialchars( $results['article']->summary )?></div>
      <div style="width: 75%;"><?php echo $results['article']->content?></div>
      <p class="pubDate">Published on <?php echo date('j F Y', $results['article']->publicationDate)?></p>

      <p><a href="./">Return to Homepage</a></p>

<?php include "templates/include/footer.php" ?>

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

مرحله هشتم: ایجاد قالب‌های بک-اند

اکنون که قالب‌ها را برای قسمت فرانت-اند سایت ایجاد کرده‌ایم، نوبت به ایجاد 3 قالب مدیریت می‌رسد.

LoginForm.php

در قدم اول، یک پوشه به نام “admin” را در پوشه “templates” ایجاد کنید. در پوشه admin، اولین قالب بک-اند، به نام LoginForm.php را ایجاد می‌کنیم:

<?php include "templates/include/header.php" ?>

      <form action="admin.php?action=login" method="post" style="width: 50%;">
        <input type="hidden" name="login" value="true" />

<?php if ( isset( $results['errorMessage'] ) ) { ?>
        <div class="errorMessage"><?php echo $results['errorMessage'] ?></div>
<?php } ?>

        <ul>

          <li>
            <label for="username">Username</label>
            <input type="text" name="username" id="username" placeholder="Your admin username" required autofocus maxlength="20" />
          </li>

          <li>
            <label for="password">Password</label>
            <input type="password" name="password" id="password" placeholder="Your admin password" required maxlength="20" />
          </li>

        </ul>

        <div class="buttons">
          <input type="submit" name="login" value="Login" />
        </div>

      </form>

<?php include "templates/include/footer.php" ?>

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

ListArticles.php

حالا دومین قالب ادمین را در پوشه “admin” می‌سازیم. نام این قالب “ListArticles.php” است:

<?php include "templates/include/header.php" ?>

      <div id="adminHeader">
        <h2>Widget News Admin</h2>
        <p>You are logged in as <b><?php echo htmlspecialchars( $_SESSION['username']) ?></b>. <a href="admin.php?action=logout"?>Log out</a></p>
      </div>

      <h1>All Articles</h1>

<?php if ( isset( $results['errorMessage'] ) ) { ?>
        <div class="errorMessage"><?php echo $results['errorMessage'] ?></div>
<?php } ?>


<?php if ( isset( $results['statusMessage'] ) ) { ?>
        <div class="statusMessage"><?php echo $results['statusMessage'] ?></div>
<?php } ?>

      <table>
        <tr>
          <th>Publication Date</th>
          <th>Article</th>
        </tr>

<?php foreach ( $results['articles'] as $article ) { ?>

        <tr onclick="location='admin.php?action=editArticle&amp;articleId=<?php echo $article->id?>'">
          <td><?php echo date('j M Y', $article->publicationDate)?></td>
          <td>
            <?php echo $article->title?>
          </td>
        </tr>

<?php } ?>

      </table>

      <p><?php echo $results['totalRows']?> article<?php echo ( $results['totalRows'] != 1 ) ? 's' : '' ?> in total.</p>

      <p><a href="admin.php?action=newArticle">Add a New Article</a></p>

<?php include "templates/include/footer.php" ?>

این الگو لیستی از مقالات را برای ویرایش توسط ادمین نمایش می‌دهد. پس از نمایش هرگونه پیام خطا (error) یا وضعیت (status)، از طریق آرایه موضوعات Article ذخیره شده در “$results[‘articles’]” جستجو کرده و تاریخ انتشار و عنوان هر مقاله را در یک ردیف جدول نمایش می‌دهد. همچنین یک رویداد “onclick” جاوا اسکریپت را به ردیف هر مقاله در جدول اضافه می‌کند، به طوری که ادمین می‌تواند روی یک مقاله برای ویرایش آن کلیک کند. این الگو همچنین شامل تعداد کل مقالات و همچنین لینکی است که به ادمین اجازه می‌دهد مقاله جدیدی اضافه کند.

EditArticle.php

حالا آخرین الگو، یعنی “editArticle.php” را در پوشه admin ذخیره می‌کنیم:

<?php include "templates/include/header.php" ?>

      <div id="adminHeader">
        <h2>Widget News Admin</h2>
        <p>You are logged in as <b><?php echo htmlspecialchars( $_SESSION['username']) ?></b>. <a href="admin.php?action=logout"?>Log out</a></p>
      </div>

      <h1><?php echo $results['pageTitle']?></h1>

      <form action="admin.php?action=<?php echo $results['formAction']?>" method="post">
        <input type="hidden" name="articleId" value="<?php echo $results['article']->id ?>"/>

<?php if ( isset( $results['errorMessage'] ) ) { ?>
        <div class="errorMessage"><?php echo $results['errorMessage'] ?></div>
<?php } ?>

        <ul>

          <li>
            <label for="title">Article Title</label>
            <input type="text" name="title" id="title" placeholder="Name of the article" required autofocus maxlength="255" value="<?php echo htmlspecialchars( $results['article']->title )?>" />
          </li>

          <li>
            <label for="summary">Article Summary</label>
            <textarea name="summary" id="summary" placeholder="Brief description of the article" required maxlength="1000" style="height: 5em;"><?php echo htmlspecialchars( $results['article']->summary )?></textarea>
          </li>

          <li>
            <label for="content">Article Content</label>
            <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>
          </li>

          <li>
            <label for="publicationDate">Publication Date</label>
            <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 ) : "" ?>" />
          </li>


        </ul>

        <div class="buttons">
          <input type="submit" name="saveChanges" value="Save Changes" />
          <input type="submit" formnovalidate name="cancel" value="Cancel" />
        </div>

      </form>

<?php if ( $results['article']->id ) { ?>
      <p><a href="admin.php?action=deleteArticle&amp;articleId=<?php echo $results['article']->id ?>" onclick="return confirm('Delete This Article?')">Delete This Article</a></p>
<?php } ?>

<?php include "templates/include/footer.php" ?>

این فرم هم برای ایجاد مقالات جدید و هم برای ویرایش مقالات موجود استفاده می‌شود. بسته به حجم (value) ارسال شده در متغیر “$results[‘formAction’]”، در یکی از “admin.php?action=newArticle” یا “admin.php?action=editArticle” قرار می‌گیرد. همچنین حاوی یک فیلد پنهان “articleId”، برای ردیابی ID مقاله در حال ویرایش (در صورت وجود) است. این فرم همچنین شامل قسمتی برای پیام‌های خطا و همچنین فیلدهایی برای عنوان مقاله، خلاصه، محتوا و تاریخ انتشار است. در نهایت، 2 دکمه برای ذخیره و لغو تغییرات وجود داشته و همچنین یک لینک وجود دارد که به مدیر اجازه می‌دهد تا مقاله ویرایش شده فعلی را حذف کند.

مرحله نهم: ایجاد Stylesheet و تصویر لوگو

برنامه CMS ما اکنون کامل شده است، اما برای اینکه هم برای بازدیدکنندگان و هم برای مدیر سایت کمی زیباتر به نظر برسد، یک فایل CSS برای بهبود ظاهر سایت ایجاد می‌کنیم. این فایل را تحت عنوان “style.css” در پوشه cms خود ذخیره کنید:

/* Style the body and outer container */

body {
  margin: 0;
  color: #333;
  background-color: #00a0b0;
  font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
  line-height: 1.5em;
}

#container {
  width: 960px;
  background: #fff;
  margin: 20px auto;
  padding: 20px;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px;
}


/* The logo and footer */

#logo {
  display: block;
  width: 300px;
  padding: 0 660px 20px 0;
  border: none;
  border-bottom: 1px solid #00a0b0;
  margin-bottom: 40px;
}

#footer {
  border-top: 1px solid #00a0b0;
  margin-top: 40px;
  padding: 20px 0 0 0;
  font-size: .8em;
}


/* Headings */

h1 {
  color: #eb6841;
  margin-bottom: 30px;
  line-height: 1.2em;
}

h2, h2 a {
  color: #edc951;
}

h2 a {
  text-decoration: none;
}


/* Article headlines */

#headlines {
  list-style: none;
  padding-left: 0;
  width: 75%;
}

#headlines li {
  margin-bottom: 2em;
}

.pubDate {
  font-size: .8em;
  color: #eb6841;
  text-transform: uppercase;
}

#headlines .pubDate {
  display: inline-block;
  width: 100px;
  font-size: .5em;
  vertical-align: middle;
}

#headlines.archive .pubDate {
  width: 130px;
}

.summary {
  padding-left: 100px;
}

#headlines.archive .summary {
  padding-left: 130px;
}


/* "You are logged in..." header on admin pages */

#adminHeader {
  width: 940px;
  padding: 0 10px;
  border-bottom: 1px solid #00a0b0;
  margin: -30px 0 40px 0;
  font-size: 0.8em;
}


/* Style the form with a coloured background, along with curved corners and a drop shadow */

form {
  margin: 20px auto;
  padding: 40px 20px;
  overflow: auto;
  background: #fff4cf;
  border: 1px solid #666;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;  
  border-radius: 5px;
  -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
  -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
  box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
}


/* Give form elements consistent margin, padding and line height */

form ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

form ul li {
  margin: .9em 0 0 0;
  padding: 0;
}

form * {
  line-height: 1em;
}


/* The field labels */

label {
  display: block;
  float: left;
  clear: left;
  text-align: right;
  width: 15%;
  padding: .4em 0 0 0;
  margin: .15em .5em 0 0;
}


/* The fields */

input, select, textarea {
  display: block;
  margin: 0;
  padding: .4em;
  width: 80%;
}

input, textarea, .date {
  border: 2px solid #666;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;    
  border-radius: 5px;
  background: #fff;
}

input {
  font-size: .9em;
}

select {
  padding: 0;
  margin-bottom: 2.5em;
  position: relative;
  top: .7em;
}

textarea {
  font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
  font-size: .9em;
  height: 5em;
  line-height: 1.5em;
}

textarea#content {
  font-family: "Courier New", courier, fixed;
}
  

/* Place a border around focused fields */

form *:focus {
  border: 2px solid #7c412b;
  outline: none;
}


/* Display correctly filled-in fields with a green background */

input:valid, textarea:valid {
  background: #efe;
}


/* Submit buttons */

.buttons {
  text-align: center;
  margin: 40px 0 0 0;
}

input[type="submit"] {
  display: inline;
  margin: 0 20px;
  width: 12em;
  padding: 10px;
  border: 2px solid #7c412b;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;  
  border-radius: 5px;
  -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
  -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
  box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
  color: #fff;
  background: #ef7d50;
  font-weight: bold;
  -webkit-appearance: none;
}

input[type="submit"]:hover, input[type="submit"]:active {
  cursor: pointer;
  background: #fff;
  color: #ef7d50;
}

input[type="submit"]:active {
  background: #eee;
  -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset;
  -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset;
  box-shadow: 0 0 .5em rgba(0, 0, 0, .8) inset;
}


/* Tables */

table {
  width: 100%;
  border-collapse: collapse;
}

tr, th, td {
  padding: 10px;
  margin: 0;
  text-align: left;
}

table, th {
  border: 1px solid #00a0b0;
}

th {
  border-left: none;
  border-right: none;
  background: #ef7d50;
  color: #fff;
  cursor: default;
}

tr:nth-child(odd) {
  background: #fff4cf;
}

tr:nth-child(even) {
  background: #fff;
}

tr:hover {
  background: #ddd;
  cursor: pointer;
}


/* Status and error boxes */

.statusMessage, .errorMessage {
  font-size: .8em;
  padding: .5em;
  margin: 2em 0;
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px; 
  -moz-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
  -webkit-box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
  -box-shadow: 0 0 .5em rgba(0, 0, 0, .8);
}

.statusMessage {
  background-color: #2b2;
  border: 1px solid #080;
  color: #fff;
}

.errorMessage {
  background-color: #f22;
  border: 1px solid #800;
  color: #fff;
}

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

پیشنهاد آموزشی مرتبط:

مجموعه دوره آموزش طراحی سایت با HTML و CSS: اینجا را کلیک کنید (+).

به عنوان آخرین مورد که البته از اهمیت بالایی هم برخوردار است، سایت ما به یک لوگو نیاز دارد. یک لوگو ایجاد کرده و آن را در یک پوشه به نام “images” در پوشه cms خود ذخیره کنید.

جمع‌بندی نهایی

در این مقاله، ما ساخت CMS را به طور کامل آموزش داده و تمامی کدهای موجود در آن را بررسی کردیم. برای امتحان کردن آن، می‌توانید مرورگر را باز کرده و به URL اصلی CMS خود اشاره کنید. روی “em” و سپس لینک “Site Admin” در پاورقی کلیک کرده، وارد شوید و چند مقاله اضافه کنید. سپس سعی کنید آن‌ها را در قسمت فرانت-اند مرور کنید. برای بازگشت به صفحه اصلی روی لوگو کلیک کنید. در این آموزش، شما یک سیستم مدیریت محتوای اولیه را با استفاده از PHP و MySQL ساخته‌اید. آنچه در این مقاله دیدیم، در مورد MySQL، جداول، انواع فیلد، PDO، برنامه‌نویسی شی‌گرا، قالب، امنیت، sessions و بسیاری موارد دیگر است. در حالی که این CMS بسیار ابتدایی است، اما امیدواریم نقطه شروعی برای ساختن وب‌سایت‌های مبتنی بر CMS خود داشته باشید.

برای دانلود فایل کدهای این پروژه، اینجا (+) را کلیک کرده و برای مشاهده نسخه دموی انگلیسی این پروژه، اینجا (+) را کلیک کنید.

پیشنهاد آموزشی مرتبط:

دوره ویدئویی آموزش برنامه نویسی PHP: اینجا را کلیک کنید (+).

 

اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

اگر این مطلب برای شما مفید بوده است، آموزش‌ها و مطالب زیر نیز به شما پیشنهاد می‌شوند:

سارا رسولی هستم. فوق لیسانس فیزیک نظری و به مدت سه سال هست که کار تولید محتوای متنی، مدیریت سایت، ویرایش و ترجمه انجام میدم.

بر اساس رای 1 نفر

آیا این مطلب برای شما مفید بود؟

نظر شما چیست؟

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