作成日:2021/04/12 更新日:2022/07/04

#11 PHPでClassクラスを理解するための準備

以前作ったPHPプログラムをファイル分けしましょう

応用2で作った「sortableアプリ」は1枚のphpファイルでいろんな動作を処理していましたが、 本来は機能ごとにファイルを分割することで、保守性(あとで手直ししたりすること)などに役立てることができます。

前回作ったアプリケーションファイル[8001-sortable]を複製し[sortable2]というフォルダを作り、[8001-sortable]フォルダの中に入れます。
「sortable2」の中で、それぞれ機能ごとにファイルを切り分けましょう。ファイルの内容は以下のとおりです。


localhost:8001/sortable2/でアクセスします。
index.phpファイルの編集箇所
読み込むファイルが増えたので、それぞれのファイルの読み込みを設定しましょう。
index.phpから切り出して新たに作るファイルは4つです。
  1. /config/config.php
  2. /function/insert.php
  3. /function/update.php
  4. /js/sort.js

config.php(データベースに接続する機能)


<?php
/* データベース設定 */
define('DB_DNS', 'mysql:host=localhost; dbname=cri_sortable; charset=utf8');
define('DB_USER', 'root');
define('DB_PASSWORD', 'root');

/* データベースへ接続
======================================================== */
try {
  $dbh = new PDO(DB_DNS, DB_USER, DB_PASSWORD);
  $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch (PDOException $e){
    echo $e->getMessage();
    exit;
}
内容はそのままです

insert.php(データベースに新規登録する機能)


<?php
require_once('../config/config.php');  /* DB接続用のファイルを読み込む */

/* 新規氏名+性別をデータベースへ登録 */
if(!empty($_POST['inputName'])){
  try{
    $sql = '
            INSERT INTO sortable(
              name,
              gender_id
            )
            VALUES(
              :ONAMAE,
              :GENDER
            )
            ';
    $stmt = $dbh->prepare($sql);
    $stmt->bindValue(':ONAMAE', $_POST['inputName'],   PDO::PARAM_STR);
    $stmt->bindValue(':GENDER', $_POST['inputGender'], PDO::PARAM_INT);
    $stmt->execute();

    /* ↓一つ前のページのパスを指定し、処理が終わったらそこに戻る */
    header('location:'.$_SERVER["HTTP_REFERER"]);
  } catch (PDOException $e) {
    echo $e->getMessage();
  }
}
require_once()関数でDB接続させるconfig.phpファイルを読み込みます。
また、INSERT処理が終わったらheader()関数でindex.phpファイルに戻ってページを表示させます。

update.php(データベースの情報を変える機能)


<?php
require_once('../config/config.php');  /* DB接続用のファイルを読み込む */

/* Ajaxを経由してPOST受信した「座標」の変数をDBに登録 */
if(!empty($_POST['left'])){
  try{
    $sql  = '
            UPDATE
              sortable
            SET
              left_x = :LEFT,
              top_y  = :TOP
            WHERE
              id = :NUMBER
            ';
    $stmt = $dbh->prepare($sql);
    $stmt->bindValue(':LEFT'  , (int)$_POST['left'], PDO::PARAM_INT);
    $stmt->bindValue(':TOP'   , (int)$_POST['top'],  PDO::PARAM_INT);
    $stmt->bindValue(':NUMBER', (int)$_POST['id'],   PDO::PARAM_INT);
    $stmt->execute();

    /* ↓一つ前のページのパスを指定し、処理が終わったらそこに戻る */
    header('location:'.$_SERVER["HTTP_REFERER"]);
  } catch (PDOException $e) {
    echo $e->getMessage();
  }
}
insert.php同様、require_once()関数でファイルを読み込みます。
また、UPDATE処理が終わったらindex.phpファイルに戻ってページを表示させます。

sort.js(要素を動かす機能)


/*
 * DRAGGABLE
 =========================== */
$(function(){
  $('.drag').draggable({
    containment:'#drag-area',
    cursor:'move',
    opacity:0.6,
    scroll:true,
    zIndex:10,
    /* ==========STOP処理====================================== */
    stop:function(event, ui){
      let myNum  = $(this).data('num');
      let myLeft = (ui.offset.left - $('#drag-area').offset().left);
      let myTop  = (ui.offset.top  - $('#drag-area').offset().top);
      /* ==========AJAX通信================= */
      $.ajax({
        type:'POST',
        url :'./function/update.php',  /* ←update.phpファイルに飛ばします */
        data: {
          id  :myNum,
          left:myLeft,
          top :myTop
        }
      }).done(function(){
         /*console.log('成功');*/
      }).fail(function(XMLHttpRequest, textStatus, errorThrown){
         console.log(XMLHttpRequest.status);
         console.log(textStatus);
         console.log(errorThrown);
      });
      /* ==========/AJAX通信================= */

    }
    /* ==========/STOP処理====================================== */
  });
});
$.ajax()メソッドのurl引数には、データをとばす先であるupdate.phpのパスを指定します。

index.php(ファイルをリンクさせます)


<?php
require_once('./config/config.php');  /* 01 ←DB接続用のファイルを読み込む */
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sortable_Comp</title>
  <script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
  <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
  <script src="js/sort.js"></script>  
  <link href="css/style.css" rel="stylesheet">
</head>
<body>
<div id="wrapper">
<div id="input_form">
  <form action="./function/insert.php" method="POST">  
    <input type="text" name="inputName" placeholder="新メンバー名を入力">
  ・
  ・
  ・
  1. DB接続用のファイルconfig.phpを読み込ませます。
  2. ドラッグ機能のsort.jsを読み込みます。
  3. 新規登録formタグのaction属性にはデータの飛ばし先として、insert.phpへのパスを指定します。
すべてのindex.php ▼

  <?php
  require_once('./config/config.php');
  ?>
  <!DOCTYPE html>
  <html lang="ja">
  <head>
  	<meta charset="UTF-8">
  	<title>Sortable_Comp</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
    <script src="js/sort.js"></script>
    <link href="css/style.css" rel="stylesheet">
  </head>
  <body>
  <div id="wrapper">
  <div id="input_form">
    <form action="./function/insert.php" method="POST">
      <input type="text" name="inputName" placeholder="新メンバー名を入力">
      <?php
        $sql    = 'SELECT * FROM genders';
        $stmt   = $dbh->query($sql);
        $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
        foreach ($result as $val) {
          $checked = ($val['id'] == 1) ? ' checked="checked"' : '';
          echo '  <input type="radio" name="inputGender" value="'.$val['id'].'"' . $checked . '>'.$val['gender'].PHP_EOL;
        }
      ?>
      <input type="submit" value="登録">
    </form>
  </div>
  <div id="drag-area">
  <?php
  $sql = '
    SELECT
     t1.*,
     genders.gender
    FROM
      sortable AS t1
    LEFT JOIN `genders` ON t1.gender_id = genders.id
  ';
  $stmt = $dbh->query($sql);
  foreach ($stmt as $result){
    echo '  <div class="drag gender'.$result['gender_id'].'" data-num="'.$result['id'].'" style="left:'.$result['left_x'].'px; top:'.$result['top_y'].'px;">'.PHP_EOL;
    echo '    <p><span class="name">'.$result['id'].' '.$result['name'].' ('.$result['gender'].')</span></p>'.PHP_EOL;
    echo '  </div>'.PHP_EOL;
  }
  ?>
  </div>
  </div>
  </body>
  </html>
localhost:8001/sortable2/でアクセスします。

切り出しただけなので、同じ表示になるはずです。
ファイルを機能別に分け、役割ごとにフォルダに入れ、index.phpに読み込むことができました。
これで準備は完了です。

#12 PHPアプリケーションをクラス化してみよう

クラスを理解するための「オブジェクト指向プログラミング」

処理する機能をひとまとめににしたものを Classクラス と呼びます。
クラスを使ってプログラムを作る考え方を オブジェクト指向しこうプログラミング と呼びます。
大人数で、大規模なアプリケーションを作るときはクラス化しておくとたくさんのメリットがあります。

オブジェクト指向プログラミングとは「ある1つの処理は、データや処理をまとめたもの = “オブジェクト”で構成され、複数のオブジェクトを使って全体のプログラミングする考え方」という意味合いで理解しておけば大丈夫です。
最初はオブジェクト指向を理解するのは難しいです。作ったファイルを書き換えることで、なんとなく雰囲気や、書き方をつかめてもらえたら良いでしょう。

さきほど作ってもらった[sortable2]をクラス化していきましょう。
[sortable2]フォルダを複製して[sortable3]を作り編集していきます。


このようなファイル構成になります。

のちほどlocalhost:8001/sortable3/でアクセスします。

①DBに接続する機能config.phpをクラス化してみよう


<?php
/* データベースに接続するクラス */
class Connect
{
  /* プロパティ(定数)の宣言 */
  const DB_NAME ='cri_sortable';
  const HOST    ='localhost';
  const UTF     ='utf8';
  const USER    ='root';
  const PASS    ='root';

  /* データベースに接続する メソッド(関数) */
  public function pdo(){
    $dsn  = "mysql:dbname=" .self::DB_NAME. "; host=" .self::HOST. "; charset=" .self::UTF;
    $user = self::USER;
    $pass = self::PASS;
    try{
      $pdo = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES '.SELF::UTF));
    }catch(Exception $e){
      echo 'エラー '.$e->getMessage;
      die();
    }
    return $pdo;
  }
}

/* SELECT文のときに使用するクラス */
class SelectData extends Connect
{
  /* プロパティ(変数)の宣言 */
  private $sql;

  /* データベースに接続しテーブルデータを取得する メソッド(関数) */
  public function select($sql){
    $items = $this->pdo()->query($sql)->fetchAll(PDO::FETCH_ASSOC);
    return $items;
  }
}
上記の内容をコピーして Connect.php という名前で保存しましょう。
クラスのファイル名は、クラス名をつけます。
その際にControllerというフォルダを作って、その中に入れておきましょう。Controller/Connect.php
▼解説
クラス内では変数と関数の名称が変わります。
変数プロパティ
関数メソッド

<?php
class クラス名{
  const 定数名 = '値';
  public $プロパティ;

  public function メソッド(){
    計算や式などの機能;
  }
}
クラスはこのようにclassと宣言した後、 クラス名{ } の{ }の中に データであるプロパティ(変数)と、処理であるメソッド(関数)を入れます。
クラスの名前は必ず、初めの文字を大文字で設定します。

定数を作る


const 定数名 ='値';

const DB_NAME ='cri_sortable';
const HOST    ='localhost';
const UTF     ='utf8';
const USER    ='root';
const PASS    ='root';
constをつけて定数名に定数値を代入します。

DBへ接続する機能「pdo」を作る


public function メソッド名(){
  機能や処理
}

アクセス修飾子しゅうしょくしpublicを使って、作ったpdoメソッドがどこからでもアクセスできるようにしておきます。
※publicを指定しなくても初期設定がpublicとなりますが、明示しておくことが好ましいです。
publicパブリッククラスの内外、どこからでもアクセス可能
protectedプロテクテッドクラス自身と継承クラスからアクセス可能
privateプライベート同じクラス内だけアクセス可能

/* config.phpの場合 */
$dbh = new PDO(DB_DNS, DB_USER, DB_PASSWORD);

/* Connect.phpの場合 */
$pdo = new PDO($dsn, $user, $pass, オプション);
config.phpでもnew演算子でPDOクラスを使っていましたね。同じ手法で接続します。

DBへ接続したのち、データを取得する機能「select()」

接続することとは別の機能になりますので、新しくクラスを作って分けます。
SelectDataというクラスを作りますが、DB接続の機能であるpdoメソッドつまり、pdo()も使いたいので extendsをつかってDB接続クラスConnectを引き継ぎ(継承けいしょうし)ます。

class SelectData extends Connect
{
  private $sql;
  public function select($sql){
    $items = $this->pdo()->query($sql)->fetchAll(PDO::FETCH_ASSOC);
    return $items;
  }
}
select()というメソッドを作ります。引数として $sql を設定します。

$items = $this->pdo()->query($sql)->fetchAll(PDO::FETCH_ASSOC);
  ↓
  $this->pdo();
  $this->query($sql);
  $this->fetchAll(PDO::FETCH_ASSOC);
  をひとまとめに書いたものです。
pdo()メソッドは先ほど作成したDBに接続させるメソッドです。
query()メソッドで$sql変数に入っているSQL文をDBで実行します
fetchAll()メソッドで連想配列データにして、$items変数に代入します。

更新機能と新規登録を別のクラスファイルとして作成します。


<?php
class AppController extends Connect
{
  /* 座標を登録 */
  public function update_sortable($sql, $left, $top, $id){
    $stmt = $this->pdo()->prepare($sql);
    $stmt->bindValue(':LEFT',   $left, PDO::PARAM_INT);
    $stmt->bindValue(':TOP',    $top,  PDO::PARAM_INT);
    $stmt->bindValue(':NUMBER', $id,   PDO::PARAM_INT);
    $stmt->execute();
    return $stmt;
  }

  /* 新規登録 */
  public function insert_sortable($sql, $name, $gender){
    $stmt = $this->pdo()->prepare($sql);
    $stmt->bindValue(':ONAMAE', $name,   PDO::PARAM_STR);
    $stmt->bindValue(':GENDER', $gender, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt;
  }
}
座標をアップデートさせる機能と、新規登録させる機能をまとめています。
上記の内容をAppController.phpという名前で保存しましょう

▼解説
別のクラスの機能を利用するために、クラスを継承extends(引き継ぎ)させることができます。

class `新しく作る子クラス名` extends `親クラス名`
{
↓
class AppController extends Connect
{
新しく作る 子クラス:AppController
継承させる 親クラス:Connect
Connectクラスで作ったpdo()メソッドを使いたいので、extends ConnectとすることでConnectクラスを継承させます。

座標をアップデートさせる機能


public function update_sortable($sql, $left, $top, $id){
update_sortable()メソッドを作成します。引数は$sql, $left, $top, $idの4つです。

新規登録させる機能


public function insert_sortable($sql, $name, $gender){
insert_sortable()メソッドを作成します。引数は$sql, $name, $genderの3つです。

クラスを使う手順

先程作ったクラスを使ってプログラムします。
クラスをnewしてインスタンスを作成し、インスタンスからクラス内で書いているメソッドを呼び出したり、プロパティ(変数)に値を代入します。

例
<?php
/* クラスファイルの読み込み */
require_once('読み込みたいファイルのパス');
↓
require_once('./config/file.php');

/* クラスからインスタンスを生成 */
$インスタンス = new クラス();
↓
$pdo = new Connect();

/* クラス内のメソッドなどに実際使うデータを代入 */
$インスタンス->プロパティ = '値';
↓
$pdo->prop = '100'

/* クラス内のメソッドの実行 */
$インスタンス->メソッド();
↓
$select->select($sql);


例
<?php
class Sample
{
  public $name;
  public $aisatsu;

  public function koe() {
    echo $this->name.' さん '.$this->aisatsu;
  }
}
/* ① */
$taro = new Sample();
$taro->name = '下北太郎';     /* インスタンス->プロパティ (プロパティに値を代入) */
$taro->aisatsu = 'おはよう';
$taro->koe();                /* インスタンス->メソッド */

echo '
'; /* ② */ $jiro = new Sample(); $jiro->name = '下北二郎'; $jiro->aisatsu = 'おはよう'; $jiro->koe();
実行した結果は

下北太郎 さん おはよう
下北二郎 さん バイバイ
となります。
クラスをインスタンス化し、クラス外でプロパティに名前などの値を代入して、クラス内のメソッド koe() の機能を使って文字を出力しています。
メソッド内の $this はインスタンス $taro の場合は $taro、インスタンス $jiro の場合は $jiro のこととなります。
また、プロパティ(変数)は $ マークをつけない点も注意が必要です。

②update.phpをクラス化に対応させます


<?php
/* 削除 */
/* require_once('../config/config.php'); */

/* 追加 */
require_once('../Controller/Connect.php');
require_once('../Controller/AppController.php');

/* Ajaxを経由してPOSTで受信した「座標」連想配列をDBに登録 ② */
if(!empty($_POST['left'])){
  try{
    $sql  = '
            UPDATE sortable
            SET
              `left_x` = :LEFT,
              `top_y`  = :TOP
            WHERE
              `id` = :NUMBER
            ';
    /* 削除する
    $stmt = $dbh->prepare($sql);
    $stmt->bindValue(':LEFT',   (int)$_POST['left'], PDO::PARAM_INT);
    $stmt->bindValue(':TOP',    (int)$_POST['top'],  PDO::PARAM_INT);
    $stmt->bindValue(':NUMBER', (int)$_POST['id'],   PDO::PARAM_INT);
    $stmt->execute();  */

    /* 追加 */
    $obj = new AppController();
    $obj->update_sortable($sql, $_POST['left'], $_POST['top'], $_POST['id']);

  } catch (PDOException $e) {
    echo $e->getMessage();
  }
}
▼解説

require_once('../Controller/Connect.php');
require_once('../Controller/AppController.php');
DB接続用のクラスファイルConnect.phpと、更新機能のAppController.phpを両方読み込みます。

$stmt = $dbh->prepare($sql);
$stmt->bindValue(':LEFT',   (int)$_POST['left'], PDO::PARAM_INT);
$stmt->bindValue(':TOP',    (int)$_POST['top'],  PDO::PARAM_INT);
$stmt->bindValue(':NUMBER', (int)$_POST['id'],   PDO::PARAM_INT);
$stmt->execute();
これらはAppController.phpで同様の機能を作ったので削除します。

$obj = new AppController();
new演算子を使って、new AppController(); でAppControllerクラスをインスタンス化(実体化)し、$obj変数に代入します。
インスタンス化された変数には、クラスがコピーされ同じ機能の内容が入っていると考えてください。

$obj->update_sortable($sql, $_POST['left'], $_POST['top'], $_POST['id']);
クラスと同じ内容が入っている$objから、メソッドupdate_sortableを使いたいので、 アロー演算子->を使って呼び出します。
メソッド側で引数を4つ指定したので、ここでも4つのデータを引数に渡しましょう。

③insert.phpをクラス化に対応させます


<?php
/* 追加 */
/* require_once('../config/config.php');  */
/* 追加 */
require_once('../Controller/Connect.php');
require_once('../Controller/AppController.php');

/* POST受信した「名前」「性別」「地域」連想配列をDBに登録 ① */
if(!empty($_POST['inputName'])){
  try{
    $sql = '
            INSERT INTO sortable(
              name,
              gender_id
            )
            VALUES(
              :ONAMAE,
              :GENDER
            )
            ';
    /* 削除する
    $stmt = $dbh->prepare($sql);
    $stmt->bindValue(':ONAMAE', $_POST['inputName'], PDO::PARAM_STR);
    $stmt->bindValue(':GENDER',    $_POST['inputGender'],  PDO::PARAM_INT);
    $stmt->execute();  */

    /*追加*/
    $obj = new AppController();
    $obj->insert_sortable($sql, $_POST['inputName'], $_POST['inputGender']);

    header('location:'.$_SERVER["HTTP_REFERER"]);
  } catch (PDOException $e) {
    echo $e->getMessage();
  }
}
▼解説

require_once('../Controller/Connect.php');
require_once('../Controller/AppController.php');
DB接続用のConnect.phpと、更新機能のAppController.phpを両方読み込みます。

$stmt = $dbh->prepare($sql);
$stmt->bindValue(':ONAMAE', $_POST['inputName'], PDO::PARAM_STR);
$stmt->bindValue(':GENDER', $_POST['inputGender'],  PDO::PARAM_INT);
$stmt->execute();
これらはAppController.phpで機能を作ったので削除します。

$obj = new AppController();
$obj->insert_sortable($sql, $_POST['inputName'], $_POST['inputGender']);
AppControllerクラスからインスタンスを作成し、$objに代入します。
insert_sortable()メソッドを呼び出し、決められた引数を渡しました。

④index.phpをクラス化に対応させます


  <?php
  /* 削除 */
  /* require_once('./config/config.php'); */

  /* 追加 */
  require_once('./Controller/Connect.php');
  $select = new SelectData();
  ?>
  <!DOCTYPE html>
  <html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>Sortable_Comp</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
    <script src="js/sort.js"></script>
    <link href="css/style.css" rel="stylesheet">
  </head>
  <body>
  <div id="wrapper">
  <div id="input_form">
    <form action="./function/insert.php" method="POST">
      <input type="text" name="inputName" placeholder="新メンバー名を入力">
      <?php
        $sql = 'SELECT * FROM genders';

        /* 削除
        $stmt   = $dbh->query($sql);
        $result = $stmt->fetchAll(PDO::FETCH_ASSOC);  */

        /* 追加 */
        $result = $select->select($sql);

        foreach ($result as $val) {
          $checked = ($val['id'] == 1) ? ' checked="checked"' : '';
          echo '  <input type="radio" name="inputGender" value="'.$val['id'].'"' . $checked . '>'.$val['gender'].PHP_EOL;
        }
      ?>
      <input type="submit" value="登録">
    </form>
  </div>
  <div id="drag-area">
  <?php
  $sql = '
          SELECT
            t1.*,
            genders.gender
          FROM
            sortable AS t1
          LEFT JOIN `genders` ON t1.gender_id = genders.id
        ';

  /*$result = $dbh->query($sql);  //削除 */
  /* 追加 */
  $result = $select->select($sql);

  foreach ($result as $val){
    echo '  <div class="drag gender'.$val['gender_id'].'" data-num="'.$val['id'].'" style="left:'.$val['left_x'].'px; top:'.$val['top_y'].'px;">'.PHP_EOL;
    echo '    <p><span class="name">'.$val['id'].' '.$val['name'].' ('.$val['gender'].')</span></p>'.PHP_EOL;
    echo '  </div>'.PHP_EOL;
  }
  ?>
  </div>
  </div>
  </body>
  </html>
▼解説

require_once('./Controller/Connect.php');
$select = new SelectData();
DB接続用のConnect.phpを読み込んで、SelectData()クラスからインスタンスを作成し、変数に代入しておきます。
性別のラジオボタン用のデータを取得します

/* 削除 */
$stmt   = $dbh->query($sql);
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);

/* 追加 */
$result = $select->select($sql);
$selectからselect()メソッドを利用し、$resultに代入します。
drag-area内のデータを取得します

/* 削除 */
$result = $dbh->query($sql);

/* 追加 */
$result = $select->select($sql);
$selectからselect()メソッドを利用し、$resultに代入します。

これでクラス化は完了です。config.phpはconfigフォルダごと削除してください。