独り言

プログラミングの講師をしています。新人研修で扱う技術の解説と個人の技術メモ、技術書の紹介など

【PHP】PHP入門(オブジェクト指向編)

PHPオブジェクト指向編です。
オブジェクト指向そのもそについての細かい説明は下記の記事で書いたので、ここでは簡単な説明に留めます。
ソースコードを絡めた説明をメインにします。

case10.hateblo.jp

PHPオブジェクト指向以前の内容については以下の記事を参考にしてください。

case10.hateblo.jp

オブジェクト指向の概要

オブジェクト指向の概要の説明はここでもしておきます。
オブジェクト指向は簡単にいえば、プログラミングを効率化するための手法・考え方になります。
プログラムをクラスという単位でまとめ、クラスを元にオブジェクト(インスタンス)を作成し、インスタンスのやりとりによってプログラムの流れを書いていきます。


クラスの利用

オブジェクト指向ではクラスという概念を使用し、クラスを元にオブジェクトを生成してプログラムを書くという仕組みです。
以下、クラスの宣言のサンプルです。

User.php

<?php

// クラス宣言
class User 
{
    
    // プロパティ
    public $id;
    public $age;
    public $name;

    // メソッド
    public function selfIntroduction() 
    {
        echo "名前は $this->name です。年齢は $this->age です。";
    }
}

クラスを利用するファイル index.php

<?php
require_once('User.php');
// インスタンスの生成
$user = new User();
// プロパティへの値のセット
$user->name = '綾鷹';
$user->age = 20;
// メソッドの呼び出し
$user->selfIntroduction();

実行結果を確認するにはWebサーバーを起動し、ブラウザ上からindex.phpを確認します。
ブラウザでの結果

名前は 綾鷹 です。年齢は 20 です。

解説

クラスを宣言は以下のようになります。

<?php
class クラス名 
{
    // プロパティ
    アクセス修飾子 変数名;

    // コンストラクタ
    public function __construct() 
    {
        // 初期化処理
    }

    // メソッド
    アクセス修飾子 function メソッド名()
    {
        // 処理
    }
}

クラス宣言

クラス名はそのクラスを表すものになります。
今回のサンプルではユーザーを表すクラスとしてUserクラスを作成しました。
クラス名は先頭を大文字で命名するのが一般的になっています。
クラス宣言後の中括弧({})の中には、オブジェクトの状態を表す「プロパティ」、振る舞いを表す「メソッド」、オブジェクト生成時の初期化を行う「コンストラクタ」を書くことができます。
コンストラクタについては後ほどサンプルで詳細を解説します。

プロパティ

プロパティはオブジェクトの状態を表すもので、簡単に言ってしまえば変数です。
変数と違うのは、アクセス修飾子を指定する必要があることです。
アクセス修飾子とは、クラスの外部からでもアクセス(参照、代入)ができるのか、あるいはクラス内からしかアクセスできないのか、といったアクセスのレベルを設定するものです。
アクセス修飾子は「public」「protected」「private」の3つがあります。
ざっくりと説明すると、
publicはどこからでもアクセス可能。
protectedは自クラス内と継承先のクラスからアクセス可能。
privateは自クラス内のみからアクセス可能。
となります。

今回の例ではユーザーが持つプロパティとして、id、名前、年齢という変数を作成しています。

メソッド

メソッドは処理を定義するもので、言わば関数です。
クラス(オブジェクト)が持つ関数というイメージです。
プロパティと同じく、アクセス修飾子を指定できます。
指定できるアクセス修飾子もプロパティと同じで、「public」「protected」「private」の3つが指定できます。
アクセス修飾子を省略した場合はpublicになります。

メソッドでは通常の関数を同じく処理をなんでも定義することができますが、一つ関数と違うポイントとして「$this」というキーワードが出てきます。
サンプルのコードではメソッドは

echo "名前は $this->name です。年齢は $this->age です。";

となっています。
thisは「自分自身」を表すキーワードです。
nameとageはプロパティの名前です。
「$this->name」「$this->age」はそのインスタンス(オブジェクト)のname, ageを表すものとなっています。
クラスの中ではnameとageというプロパティはそれぞれ1つずつしか宣言していません。
しかし、後述するnewというキーワードを使用すると、クラスを元にインスタンス(オブジェクト)を作成することができて、作成したインスタンスの分だけname, ageのプロパティも出来上がります。
$thisをつけると、そのインスタンス自身のname, ageを表す、という意味になります。
name, ageの前には$を付けないことに注意しましょう。

また、「$this」とプロパティの間に「->」(ハイフンと不等号)があります。
これは日本語で表すと「の」を意味する接続詞と考えると良いでしょう。
そのインスタンス「の」nameプロパティ。
のような感じです。
この記号はインスタンスを使用する場合に頻繁に出てきますので、覚えておくようにしましょう。
ちなみにJavaでは同じ表現を「.」(ピリオド)を使って表します。

外部ファイルの読み込み

ここからはindex.php(クラスを利用しているファイル)について解説します。
まず外部ファイルの読み込みからです。
ファイルの先頭に

require_once('User.php');

というコードがあります。
これはオブジェクト指向とは直接関係ないですが、PHPで別ファイルとして定義されたもの(外部ファイル)を読み込むための構文になります。
PHPでは外部ファイルを読み込むための構文は4つあります。
「require」「require_once」「include」「include_once」の4つです。
それぞれ、似てはいるものの微妙に動きが異なります。

require:指定されたファイルを読み込み、読み込みに失敗した場合(エラーが発生した場合)は処理が停止します。
require_once:requireとほぼ同じ意味ですが、違う点は、既に読み込まれているかどうかをチェックし、読み込まれている場合には読み込みません。
include:指定されたファイルを読み込み、読み込みに失敗しても処理は停止せずに、警告を出して処理を続行します。
include_once:includeとほぼ同じ意味ですが、既に読み込まれているかをチェックし、読み込まれている場合には読み込みません。

どれを使うかはケースバイケースですが、多くの場合、PHPのみのファイル(クラスが定義されたファイルなど)を読み込む場合はrequire_once、Webページに表示されるコンテンツの一部を表示する場合にはincludeを使う場合が多いです。
ここでは特に問題がない限りはrequire_onceを使用していきます。
ファイルを読み込むには引数に文字列で読み込むファイルのパスを指定する必要があります。
ファイルのパスは絶対パスによる指定と相対パスによる指定が可能です。
今回のサンプルではそれぞれのファイルが同じ階層にいることを前提としてファイル名のみを指定しています。
異なる階層にある場合には絶対パス相対パスでファイルの場所を指定する必要があります。

インスタンスの生成

クラスを元に作成されたオブジェクトのことをPHPではインスタンスと呼びます。
インスタンスを生成するのはnewというキーワードを使用します。
サンプルでは以下のようになっています。

// インスタンスの生成
$user = new User();

構文としては以下のようになります。

変数 = new クラス名();

この場合、クラス名の後の()は省略可能です。
ただし、コンストラクタを定義している場合はその引数に応じた値を渡してあげる必要があります。
この点については後述します。

プロパティ・メソッドへのアクセス

インスタンスは生成しただけではあまり意味はありません。
それを使うことに意義があります。
プロパティにアクセスするためには、
インスタンス名(変数名)->プロパティ名
インスタンス名(変数名)->メソッド名(); とすることでプロパティやメソッドを使用することができます。
サンプルでは以下のようになっています。

// プロパティへの値のセット
$user->name = '綾鷹';
$user->age = 20;
// メソッドの呼び出し
$user->selfIntroduction();

プロパティは変数とおぼ同じなので値を代入することができます。
参照して出力することも可能です。
メソッドは呼び出すことで実行可能です。
今回のサンプルではプロパティやメソッドを自由に使うことができますが、これはそれぞれのプロパティやメソッドのアクセス修飾子がpublicだからです。
privateやprotectedだった場合にはアクセスできずにエラーになってしまいますので注意が必要です。


コンストラク

プロパティとメソッドについては軽くサンプルを示しましたがコンストラクタについてはサンプルを示していなかったのでここで追記してみます。
User.phpを以下のようにします。

<?php

// クラス宣言
class User 
{
    
    // プロパティ
    public $id;
    public $age;
    public $name;

    // コンストラクタ
    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }

    // メソッド
    public function selfIntroduction() 
    {
        echo "名前は $this->name です。年齢は $this->age です。";
    }
}

そしてindex.phpを以下のようにします。

<?php
require_once('User.php');
// インスタンスの生成
$user = new User('綾鷹', 20);
// メソッドの呼び出し
$user->selfIntroduction();

実行結果は最初の例と変更ありません。

コンストラクタの定義

コンストラクタは、インスタンス生成時(つまりnewというキーワードを使った時)に呼ばれる特殊なメソッドです。
メソッド名は「__construct」で固定となります。
サンプルのコードでは以下のようになっています。

// コンストラクタ
public function __construct($name, $age)
{
    $this->name = $name;
    $this->age = $age;
}

ちなみに、PHPでは特殊なメソッド、関数として(アンダースコア)2つから始まるものが定義されていることがあります。
自分でメソッドを定義する時にも
(アンダースコア)2つから始まるメソッドを定義することもできますが、自分で定義した名前なのか、PHPが予め用意しているメソッドの名前なのか区別がつきにくくなるため、メソッドを自分で定義する時に_2つから始まるものは避けるようにしましょう。
(このような関数はマジックメソッドと呼ばれています。他の言語でも似た特徴を持つものもあります。)

サンプルをみてわかるように、コンストラクタでは引数を持つこともできます。
この例ではサンプルで受け取った値をそのままプロパティの値にセットしています。
この例のように、コンストラクタではプロパティの値を初期化するために使用されることが多いです。

ちなみに、コンストラクタは一つしか定義することができません。
引数の数を変更して複数定義しようとしてもエラーになるので注意してください。
Javaなどの言語では、オーバーロードという仕組みがあり、引数が異なれば同じメソッド名を複数定義することも可能ですが、PHPでその機能はサポートされていないので注意してください。)

コンストラクタの使用

コンストラクタを使用するには、newでクラス名を指定した後にコンストラクタの引数に対応した実引数を渡してあげます。

// インスタンスの生成
$user = new User('綾鷹', 20);

クラスプロパティとクラスメソッド

クラス内で定義したプロパティやメソッドは、newを使用してインスタンスを作成するたびに、インスタンス毎に作成されていくようなイメージです。
一方、クラスで1つ(全てのオブジェクトで共有される)プロパティやメソッドを作成することも可能です。
このようなプロパティやメソッドとそれぞれクラスプロパティ、クラスメソッドと呼びます。
staticというキーワードをつけることで実現できます。
User.phpとindex.phpを以下のように修正します。

User.php

<?php

// クラス宣言
class User 
{
    
    // クラスプロパティ
    public static $count = 0;

    // プロパティ
    public $id;
    public $age;
    public $name;

    // コンストラクタ
    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
        self::countUp();  // 追加
    }

    // クラスメソッド
    public static function countUp() 
    {
        self::$count++;
    }

    // メソッド
    public function selfIntroduction() 
    {
        echo "名前は $this->name です。年齢は $this->age です。";
    }
}

index.php

<?php
require_once('User.php');
// インスタンスの生成
$user = new User('綾鷹', 20);
echo 'カウント:' . User::$count . '<br>';

$user = new User('伊右衛門', 30);
echo 'カウント:' . User::$count . '<br>';

User::countUp();
echo 'カウント:' . User::$count . '<br>';

実行結果

カウント:1
カウント:2
カウント:3

クラスプロパティとクラスメソッドの宣言

クラスプロパティとクラスメソッドの宣言方法は、通常のプロパティとメソッドの宣言方法とほとんど同じです。
違いはstaticがつく点です。

// クラスプロパティ
public static $count;

// クラスメソッド
public static function countUp() 
{
    self::$count++;
}

新しく「self::」というキーワードが出てきました。
通常のプロパティやメソッドの場合、自クラス内からアクセスする場合は「$this」というキーワードを付けましたが、クラスプロパティやクラスメソッドにアクセスする場合にはそれが「self::」に変わるので覚えておきましょう。
また、プロパティにアクセスする時には変数名に$マークもつける点にも注意です。

クラスプロパティやクラスメソッドの利用

外部から、クラスプロパティ、クラスメソッドにアクセスする場合には、
クラス名::プロパティ名
クラス名::メソッド名
とします。

User::countUp();
echo 'カウント:' . User::$count . '<br>';

また、サンプルソースではコンストラクタの中でcountUpメソッドを呼び出していることに注意してください。
そうするとnewを使うたびに$countがカウントアップされます。
また、自分で「User::countUp();」を呼び出すことでもカウントアップされます。
結果として、インスタンス生成を2回、カウントアップメソッドの呼び出しを1回しているため、最終的に$countプロパティの値が3になっていることがわかります。
処理の流れを追うことができれば、インスタンス間で値が共有されて、クラスで1つのみであることがイメージできるかと思います。


定数

クラスの中には定数を定義することができます。
定数は再代入することができない変数です。
定数を定義するには、constというキーワードを使用します。
変数名には$マークを付けず、アルファベットの大文字で名前を付けます。

User.php

<?php

// クラス宣言
class User 
{
    // 定数
    public const MAX_SCORE = 100;
    
    // クラスプロパティ
    public static $count = 0;

    // プロパティ
    public $id;
    public $age;
    public $name;

    // コンストラクタ
    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
        self::countUp();  // 追加
    }

    // クラスメソッド
    public static function countUp() 
    {
        self::$count++;
    }

    // メソッド
    public function selfIntroduction() 
    {
        echo "名前は $this->name です。年齢は $this->age です。";
    }
}

index.php

<?php
require_once('User.php');

echo User::MAX_SCORE;

実行結果

100

定数宣言

定数宣言は以下のようになります。

// 定数
public const MAX_SCORE = 100;

使用する場合は、
クラス名::定数名
となります。

echo User::MAX_SCORE;

定数は再代入することができないので、代入する処理を書くとエラーになります。


継承

継承は元々あるクラスの特徴を引き継いで新しいクラスを作ることができる機能です。
User.phpをそのままに、同じ階層にExUser.phpを作成します。

ExUser.php

<?php

require_once('User.php');

class ExUser extends User
{
    // 新しいプロパティ追加
    public $score;

    public function __construct($name, $age, $score)
    {
        parent::__construct($name, $age);
        $this->score = $score;
    }

    // 新しいメソッドの追加
    public function scoreUp()
    {
        $this->score += 10; 
    }

    // メソッドのオーバーライド
    public function selfIntroduction()
    {
        parent::selfIntroduction();
        echo "scoreは $this->score です。";
    }
}

index.phpを以下に書き換えます。

<?php
require_once('ExUser.php');
// インスタンスの生成
$exUser = new ExUser('いろはす', 10, 70);
$exUser->scoreUp();
$exUser->selfIntroduction();

実行結果

名前は いろはす です。年齢は 10 です。scoreは 80 です。

extends

クラスの継承を行う時にはクラス宣言時にextendsというキーワードを使用します。
(拡張という意味があります。)

class ExUser extends User
{
    ...
}

こうすると、Userクラスを継承(拡張)してExUserクラスを作成したことになります。
この時、Userクラスのことを親クラス、あるいはスーパークラスなどと呼びます。
ExUserのことを、子クラス、あるいはサブクラスなどと呼びます。
継承を行うと、親クラスの機能は定義しなくてもそのまま使用することができます。
つまり、$nameプロパティや$ageプロパティなどはそのまま使えます。
もちろんメソッドも使用することができます。
親クラスの状態を受け継いだまま、新しい機能を追記することができます。
今回の例では$scoreプロパティとscoreUpメソッドを新しく追記しています。

parentキーワード

これまで自分自身のインスタンス、クラスを表すキーワードとして「this」や「self」がありましたが、継承を使う場合には新しく親クラス表す「parent」キーワードが登場します。
parent::プロパティ名
parent::メソッド名
で親クラスのプロパティやメソッドにアクセスすることができます。
今回のサンプルでは、コンストラクタの中で親クラスのコンストラクタを呼び出す場合などに利用しています。

多重継承

複数のクラスを同時に継承することを多重継承と呼びます。
PHPでは多重継承はできません。(多くのオブジェクト指向言語では多重継承はできない。一部できるものもある。)
その代わり、何世代にも渡って継承して新しいクラスを作成することは可能です。

オーバーライド

継承を行う上で重要な概念の一つにオーバーライドがあります。
オーバーライドはメソッドの上書きという意味があります。
今回の例ではselfIntroductionメソッド(自己紹介メソッド)がオーバーライドに該当します。
親クラス(Userクラス)でも全く同じ名前のメソッドが存在しますが、子クラス(ExUser)でも同じ名前のメソッドを定義しています。
これがオーバーライドです。
クラスを拡張したことでプロパティが増えた(score)ので、自己紹介メソッドで追加したプロパティも表示するようにしています。
親クラスのメソッドをparentを指定して呼び出しつつ、新しく追加する処理のみを追加しています。
これにより新しく追加したい部分のみの処理を定義すれば良いため、効率が上がります。

抽象クラスとインターフェース

継承を利用すれば、開発を行う際に役割分担をしやすくなるというメリットもあります。
親クラスをAさんが開発し、そのクラスを継承した子クラスをBさんが開発する、のような役割分担ができます。
しかし、役割分担するとミスも生じやすくなります。
例えば、Aさんが作成したクラスは、継承することを前提としており、特定のメソッドを必ずオーバーライドして欲しかったとします。
このとき、子クラスを作成したとしても、メソッドをオーバーライドし忘れたり、あるいはオーバーライドしたつもりでもメソッド名のスペルが間違っていてオーバーライドになっていないといったミスが発生する可能性があります。

このようなミスを発生させないための仕組みとして、抽象クラス、インターフェースという概念があります。

抽象クラス

抽象クラスは、継承することを前提としたクラスです。
抽象クラスは、宣言時にabstractキーワードを使用します。
また、抽象クラスは抽象メソッドと呼ばれる処理のないメソッドを定義することができます。
処理が書かれている通常のメソッドも定義することができます。
抽象クラスではない通常のクラスのことを、抽象クラスの対比として「具象クラス」と呼ぶことがあります。
以下はサンプルです。

抽象クラス
Employee.php

<?php

abstract class Employee 
{
    private $saraly;

    // コンストラクタ
    public function __construct($saraly)
    {
        $this->saraly = $saraly;
    }

    // 通常のメソッド
    public function getSalary()
    {
        return $this->saraly;
    }

    // 抽象メソッド
    public abstract function work();
}

具象クラス
Peogrammer.php

<?php

require_once('Employee.php');

class Programmer extends Employee 
{
    // オーバーライドしないとエラーになる
    public function work()
    {
        echo 'プログラミングする';
    }
}

index.php

<?php
require_once('Programmer.php');
// インスタンスの生成
// $employee = new Employee();  // ←エラーになる

$programer = new Programmer('200000') . '<br>';
echo $programer->getSalary();
$programer->work();

実行結果

200000
プログラミングする

Employeeクラスが抽象クラスです。
クラス宣言にabstractがつきます。
プロパティや通常のメソッドも持っています。(salaryプロパティとgetSalaryメソッド)
workメソッドが抽象メソッドです。
アクセス修飾子の後にabstractキーワードをつけます。
処理は書かず、中かっこ{}も書きません。
その代わりにセミコロン(;)で終了します。

Programmerクラスが抽象クラスを継承した具象クラスです。
抽象クラスを継承した場合、そのクラスに定義されている抽象メソッドをオーバーライドしないとエラーになります。
コメントアウトして実行するとどうなるか確認すると良いでしょう。)
※ただし、抽象クラスを継承したクラス自体を抽象クラスにしてしまえば(abstractをつける)、オーバーライドしなくてもエラーにはなりません。

index.phpで実行結果を確認しています。
抽象クラスは、継承されることを前提としたクラスであると言いましたが、その理由からnewでインスタンスを作成することがでないことに注意して下さい。

インターフェース

抽象クラスとは別にインターフェースという概念もあります。
インターフェースは抽象クラスよりもさらに抽象度の高い概念を定義する機能です。
インターフェースではプロパティや通常のメソッドを定義することができず、抽象メソッドと定数が定義可能です。
インターフェースを定義するときはclassではなくinterfaceというキーワードを使用します。
また、インターフェースは継承ではなく実装すると呼び、extendsではなくimplementsというキーワードを使用します。

インターフェース
Runnable.php

<?php

interface Runnable
{
    // 抽象クラス(absract不要)
    public function run();
}

実装したクラス
Programmer.php

<?php

require_once('Employee.php');
require_once('Runnable.php');

class Programmer extends Employee implements Runnable
{
    public function work()
    {
        echo 'プログラミングする';
    }

    public function run()
    {
        echo 'ゆっくり走る';
    }
}

index.php

<?php
require_once('Programmer.php');
// インスタンスの生成
$programer = new Programmer('200000');
$programer->run();

実行結果

ゆっくり走る

このサンプルではRunnnableがインターフェースです。
インターフェースでは抽象メソッドしか定義できないため、abstractは省略可能です。
ProgrammerクラスがRunnableを実装しているクラスです。
実装するにはimlementsキーワードを使用しています。
メソッドのオーバーライド方法は今までと変わりありません。

インターフェースは抽象クラスしか定義できないため、抽象クラスよりも使いにくいと感じるかもしれません。
しかし、実はインターフェースの抽象度が高いため、柔軟性も高くなります。
PHPではクラスを継承するときに多重継承はできないと書きましたが、インターフェースは複数実装することができます。
オブジェクト指向をうまく活用するときにはこの辺りが役に立ちます。
(クラスの継承の場合、継承元(親クラス)で同じ名前のメソッドがある場合、呼び出し時にどちらを呼び出すかが判断できなくなります。
しかし、インターフェースの場合はそもそも抽象メソッドしか定義できないため、メソッド名が重複してもどっちみちオーバーライドするので関係ありません。)

カプセル化

先に作成していたUserクラスを少しすっきりさせておきます。

User.php

<?php

// クラス宣言
class User 
{   
    // プロパティ
    public $age;
    public $name;

    // コンストラクタ
    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }

    // メソッド
    public function selfIntroduction() 
    {
        echo "名前は $this->name です。年齢は $this->age です。";
    }
}

実はこのクラスいくつか問題があります。
それは、プロパティのアクセス修飾子がpublicになっていることです。
publicでなければ外部からアクセスすることができないため、その方が良いと思うかもしれません。

index.phpを以下のようにしてみます。

<?php
require_once('User.php');
// インスタンスの生成
$user = new User('ポカリ', 20);
$user->age = 300;
$user->selfIntroduction();

実行結果

名前は ポカリ です。年齢は 300 です。

このサンプルでは、インスタンス作成後にプロパティの値を変更されています。
ageは年齢を表すプロパティのため、できれば0~100くらいの値でおさめられる方が望ましいです。
しかし、プロパティがpublicで誰でもアクセス可能となっているため、上記のようなことができてしまいます。
Userクラスを以下のようにするとやりたいことができるようになります。

User.php

<?php

// クラス宣言
class User 
{   
    // プロパティ
    private $age;
    private $name;

    // コンストラクタ
    public function __construct($name, $age)
    {
        $this->setName($name);
        $this->setAge($age);
    }

    // メソッド
    public function selfIntroduction() 
    {
        echo "名前は $this->name です。年齢は $this->age です。";
    }

    public function getAge()
    {
        return $this->age;
    }

    public function setAge($age)
    {
        if ($age >= 0 && $age <= 100) {
            $this->age = $age;
        } else {
            $this->age = 20;
        }
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

index.php

<?php
require_once('User.php');
// インスタンスの生成
$user = new User('ポカリ', 20);
$user->setAge(300);
$user->selfIntroduction();

実行結果

名前は ポカリ です。年齢は 20 です。

プロパティのアクセス修飾子をpriavteにし、自クラス以外はアクセスできないようにしました。
そうすると、外部からのアクセスができなくなりますが、その代わりsetAge, getAgeなど、メソッドを用意し、メソッド経由でアクセスできるようにしています。
こうすることで、想定外の値が渡されてきた場合にも適切な値になるように処理したり、あるいはエラーが出るように処理することなどができるようになります。
メソッド経由でのアクセスにすることで、取得はできるがセットはできないようにする、など、柔軟に対応できるようになります。
このように、プロパティを直接アクセスできないようにし、メソッド経由でアクセスできようにすることをカプセル化と呼びます。
不要な情報は隠蔽し、必要な情報のみを公開する、というオブジェクト指向における重要な概念となります。

名前空間

作成するプログラムの規模が大きくなると、使用するクラスやファイルの数も多くなります。
使用するクラスやファイルの数が多くなると、プログラムの役割ごとにカテゴリ分けしておくと便利です。
また、クラスや関数の数が多くなると、既存のライブラリに用意されているクラスや関数の名前と重複してしまう可能性も出てきます。
このような、名前の重複を回避、かつ役割ごとにカテゴリ分けする仕組みとして用意されているのが名前空間です。
名前空間の定義はnamespaceというキーワードを使用します。
名前空間の定義されたファイルを使用する場合は、useキーワードを使用します。

User.php

<?php
// 名前空間の定義
namespace MyProject;

// クラス宣言
class User 
{   
    // プロパティ
    private $age;
    private $name;

    // コンストラクタ
    public function __construct($name, $age)
    {
        $this->setName($name);
        $this->setAge($age);
    }

    // メソッド
    public function selfIntroduction() 
    {
        echo "名前は $this->name です。年齢は $this->age です。";
    }

    public function getAge()
    {
        return $this->age;
    }

    public function setAge($age)
    {
        if ($age >= 0 && $age <= 100) {
            $this->age = $age;
        } else {
            $this->age = 20;
        }
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

index.php

<?php
require_once('User.php');

// 名前空間がついたクラスの利用
use MyProject\User;

// インスタンスの生成
$user = new User('ポカリ', 20);
$user->selfIntroduction();

名前空間の宣言はファイルの先頭(クラス定義や関数定義の前)に行う必要があるので注意してください。
使用する場合は「名前空間\クラス名」とすることで使用することができるのですが、毎回そう書くのは面倒なので、
use 名前空間\クラス名 でとすことでクラス名だけで使用することができるようになります。
名前空間の付け方は任意ですが、ベンダー名\プロジェクト名 のようにつけることが多いです。

型付け

メソッドの引数やプロパティには、型を指定することができます。
TODO

トレイト

コードの断片を共通化したいときに使える仕組み。
TODO

【SQL】ビットマップインデックスの仕組み

ビットマップインデックスの仕組みについてのメモ。
Qiitaでも同じ記事書いてます。

そもそもインデックスとは

ビットマップインデックスの前にそもそもインデックスとは何かについて簡単に説明。
インデックスはざっくりいえばテーブルに対してのSQL(SELECT文)の処理速度を高速化するための仕組み。
日本語でいうと索引。専門書や辞書で後ろの方についている索引と同じイメージ。
辞書の場合、調べたい用語があるときに、1ページから順番に探していくよりも、先に索引で該当の用語が何ページにあるかを確認し、そのページを調べにいくほうが時短確実に時短。
その仕組みをDBMSで実現しているのがインデックス。

インデックスの種類

インデックスにはいくつかの種類がある。

・Bツリーインデックス
・ビットマップインデックス
・ハッシュインデックス

などなど。
DBMSにおいて単にインデックスというと、大抵の場合Bツリーインデックスのことを指す。
本の索引のイメージに近いのがBツリーインデックス。
名前の通り木構造になっていて、検索値を元に該当レコードが格納されているブロックのアドレスを取得する仕組み。
Bツリーインデックスについては詳しくは解説しているサイトや記事がたくさんあるので他を参照ください。

ビットマップインデックスとは

ビットマップインデックスは、検索に用いられるカラムに対して、その値とレコードとのビットマップを使ってレコードを検索するインデックス。
このインデックスが有効なのは検索するカラムのカーディナリティが低い(取りうる値の種類が少ない)場合。

例えば、「名前」「性別」「血液型」という情報を持つユーザーテーブルがあって、10万件のレコードがあったとする。
この場合、名前はほとんどの場合被らないので、レコード毎にほぼ違った値になる。
つまりカーディナリティが高い。
一方、性別は男と女の2種類の値しか取らないし、血液型はA、B、O、ABの4種類しかない。
つまりカーディナリティが低い。

この場合、名前で検索を行った際にはBツリーインデックスによる検索の方が明らかに効率が良いが、 性別や血液型で検索を行う場合、ビットマップインデックスの方が効率が良くなる可能性が高い。

サンプル

言葉だけの説明だと分かりにくいのでサンプルで。

名前、血液型、性別というカラムを持ったユーザーテーブルを考える。
以下の6件のレコードが登録されているとする。

ユーザー

ID 名前 血液型 性別
1 佐藤 A
2 鈴木 B
3 田中 O
4 高橋 A
5 伊藤 O
6 山本 AB

この時、血液型と性別に対するビットマップインデックスは以下のようになる。

血液型のビットマップ

A 1 0 0 1 0 0
B 0 1 0 0 0 0
O 0 0 1 0 1 0
AB 0 0 0 0 0 1
レコード番号 1 2 3 4 5 6

性別のビットマップ

1 0 1 0 1 0
0 1 0 1 0 1
レコード番号 1 2 3 4 5 6

レコード番号というのは、レコードを特定するための値だと思ってください。
Oracleでいうrowid、PostgreSQLでいうTIDみたいなもの。
ここではわかりやすくするためIDと一致するようにしています。
Markdownでのテーブルの性質上一番上の行が太字になっていますが、特に意味はありません。)

ここで例えば

select * from ユーザー where 血液型 = 'A';

というSQLを実行したとする。
ビットマップインデックスが使用される場合、血液型のビットマップのAのビット列を見る。
「100100」 ビット列はこうなっているので、1つ目のレコードと4つ目のレコードが該当する。
結果的に佐藤、高橋のレコードがヒットする。

このように、条件に合致するビット列を見て、値が1になっているレコードを取得するのがビットマップインデクスの仕組み。

ビットマップインデックスのもう一つのメリット

ビットマップインデックスにはもう一つ、OR検索でもインデックスが使用されるという特徴がある。
Bツリーインデックスの場合、性質上OR演算子があるとほとんどの場合インデックスは使用されないので、その点がBツリーインデックスと異なる点。

例えば

select * from ユーザー where 血液型 = 'O' or 性別 = '';

というSQLを実行したとする。

血液型Oに該当するビット列は
「001010」
性別が女に該当するビット列は
「010101」

それぞれのビット列の論理和を取ると
「011111」となる。
この結果、鈴木さんから山本さんまでの5レコードがヒットする。

同じ理屈でAND条件(論理積)も使えるが、AND条件の場合はBツリーインデックス有効なのでメリットというほどでもないかと。

DBMS毎のビットマップインデックスの仕様

Oracle

Oracleの場合、create index するときに「bitmap」のオプションを付けるとビットマップインデックスになる。

PostgreSQL

インデックス作成時には指定することはできないが、実行計画では「Bitmap Scan」が確認できる。
どうやらSQL実行時にワーキングメモリ内にビットマップを作成して検索速度を上げているらしい。

SQL Server

私が調べた限りサポートしてないっぽい。

他のDBMS製品

すみませんが未調査です。知る機会があれば、もしくは情報いただければ更新します。

【プログラミング全般】オブジェクト指向入門

ここでは様々なプログラミング言語でサポートされているオブジェクト指向について解説します。
オブジェクト指向は一言でいえば、モノ(オブジェクト)に着目したプログラミングの概念で、開発を効率よく行うために必要な考え方です。
オブジェクト指向をサポートしている言語としては、Java, PHP, Ruby, Python, C#, VB .NETなどがあります。
これだけではなく、他のも多くの言語でサポートされています。
90年代以降の作成されたプログラミング言語オブジェクト指向をサポートされているものが多く、最近の言語を学ぶ場合には避けて通れない技術です。

プログラミングの歴史

オブジェクト指向の説明に入る前にざっくりとプログラミングの歴史を振り返ります。

  • 機械語の時代
    0と1の2パターンでプログラミングをしていた時代。
    コンピュータの専門家の人しかできない。

  • アセンブリ言語の時代
    記号、アルファベットが使えるようになって機械語よりはましになった時代。
    けど、まだ簡単とは言えず、一部の専門家の人がプログラミングしていた(らしい)。

  • 高級言語の時代
    COBOLとかFORTRANの時代。
    英語の文法に近くなってかなり分かりやすく書けるようになった時代。
    アセンブリに比べて敷居は下がったが、GOTO文が乱用されており、プログラムの規模が大きくなるとメンテナンスが難しい。

  • 構造化言語の時代
    C言語など。
    順次・分岐・繰り返しというアルゴリズムの3要素で処理の流れを表現する。
    GOTO文がなくなり、メンテナンス性向上。

  • オブジェクト指向言語の時代
    構造化言語では、データを保持するのにグローバル変数という仕組みが使われていた。
    しかし、グローバル変数が多くなると、どこでどの変数が使用されているのかが分かりにくく、メンテナンスが難しくなった。
    その問題を解決するために生まれたのがオブジェクト指向
    データと処理をオブジェクトという概念で1つにまとめることで、プログラミングをしやすく、規模が大きくなってもメンテナンスがしやすくなった。

  • フレームワーク・ライブラリの時代
    オブジェクト指向の開発をさらに効率よく行うために、プログラムを再利用する動きが広まった。
    それがライブラリとフレームワーク
    ライブラリは、他の人が作成したプログラムを使うことで、面倒な処理や複雑な処理を自分で1から作成する必要がなくなる。
    フレームワークは、プログラムの半完成品みたいなもので、決められたルールに基づいてコーディングをすることでソースコードを書く量を減らし、効率を上げる仕組みです。
    フレームワークやライブラリが登場する中で、オブジェクト指向でよく使われる設計のパターンが出てきました。
    そのパターンを体系的にまとめたのがデザインパターンです。
    デザインパターンオブジェクト指向の考えを活かすために設計思想で、ライブラリやフレームワークの中に多く登場します。

  • ローコード・ノーコードの時代
    プログラミング言語ソースコードをほとんど書かずにプログラミングができる技術が流行している。
    ローコードは、少ないコード量で開発ができる技術。
    ノーコードは、全くコードを書かずして開発ができる技術。
    GUI操作でシステムを構築することができるため、非エンジニアでも開発ができることで注目を浴びています。

オブジェクト指向を学ぶメリット

この記事を書いている2020年現在、ローコード・ノーコードが大きな注目を浴びていて、今後も拡大していくことが予想されます。
ノーコードでは、プログラミング言語を学ぶ必要すらないため、今後の動向によってはオブジェクト指向を学ぶ意味は薄れてくるかもしれません。
そうでなくても、プログラミング言語を使って開発する場合、フレームワークを活用した開発がほとんど。
オブジェクト指向の仕組みを理解していなくても開発ができてしまう時代でもあります。
そんな時代にオブジェクト指向を理解する必要は果たしてあるのでしょうか。

今後もしかするとオブジェクト指向を学ぶメリットはなくなってくる可能性はありますが、フレームワークによる開発が主流であるうちは、まだオブジェクト指向を学ぶ意味はあると考えます。
なぜなら、フレームワークの中ではオブジェクト指向の仕組みが多く使われているからです。
オブジェクト指向について深く理解することができれいれば、フレームワークやライブラリの中身を理解することもできます。
そうなれば、フレームワークやライブラリを使用している中でエラーが発生したとしても、自分の力で解決できる可能性が高くなります。
また、フレームワークを自分で開発したり、既存のフレームワークを拡張することも可能になります。
一方、オブジェクト指向について理解が浅いと、フレームワークでエラーが発生しても中の仕組みが分からず、自分の力だけで解決することが難しくなります。

フレームワークやライブラリを使いこなし、トラブル解決も早いプログラマーになるにはオブジェクト指向の理解は不可欠になります。

オブジェクト指向の用語

オブジェクト指向でよく使用される用語について解説します。

オブジェクト

オブジェクトは日本語だとモノという意味です。
パソコンやスマホ、ペットボトルや机、イスなど、人間が認識することができるものは全てオブジェクトと言えます。
目に見えるもの、見えないものでも構いません。
コンピュータだと、ログインしているユーザー、商品、投稿、キャラクター、武器、など、人間が識別できるものがオブジェクトです。
用は何でもありですが、プログラミングをするときに人間にとって扱いやすい単位をオブジェクトとして扱うのがオブジェクト指向プログラミングです。

クラス

オブジェクト指向を学ぶ上で必ず出てくるのがクラスという概念です。
クラスはオブジェクトの基となるものです。
オブジェクトの設計書と呼ばれることもあります。
また、オブジェクトを抽象化したもの、という考え方もできます。
例えば、普段使用しているスマホやPCは、物理的に実体のあるモノですが、「パソコン」や「スマホ」という言葉自体は、実体のあるものを指しているわけではなく、パソコン全体、スマホ全体を表すための概念です。
「人間」という言葉も、特定の誰かを示す言葉ではなく、人間全体を表す概念で、実体のある人間の1人として私やこれを読んでいるあなたという存在がいます。
このように、オブジェクトという具体的なものを抽象化したものがクラスです。
クラスは日本語だと分類という意味を持ちます。
(学校のクラスも分類という意味ですね。)
そのため具体的なオブジェクトを分類するものがクラスという風に考えるのも自然です。
例えば自分自身の存在を分類すると、人間、社会人、男性、などに分類することができます。
このような分類がクラスになります。

インスタンス

クラスと同じ頻度でよく登場する用語がインスタンスです。
インスタンスはクラスを元に作られるもので、オブジェクトとほぼ同義です。
厳密には微妙にニュアンスが異なるようで、オブジェクトの方が幅広いです。
インスタンスは、クラスを元に作られたものに限られます。
多くのオブジェクト指向プログラミング言語ではnewというキーワードを使用してクラスを元にしてインスタンスを作成します。

メソッドとプロパティ

プログラミングにオブジェクト指向の考え方が取り入れられることで何が便利になったのか。
オブジェクト指向以前の言語では、データの保存と処理の定義はそれぞれ切り離されていました。
処理は関数で定義され、データはグローバル変数に保持していました。
それをオブジェクトという単位でデータと処理をまとめることにより、わかりやすく、そして拡張しやすいプログラムを作ることができます。

オブジェクトの中で定義されている処理のことをメソッド、オブジェクトの中で定義されているデータのことをプロパティと呼びます。
プロパティは言語によって呼び方が異なることがあり、例えばJavaではフィールドと呼ばれます。

コンストラク

多くのプログラミング言語では、インスタンスを作成する時にnewというキーワードを使用します。
そしてインスタンスが作成された時に呼び出される特殊な処理を定義することができます。
このインスタンス生成時に呼び出される処理のことをコンストラクタと呼びます。
コンストラクタでは、主にプロパティの初期化の処理などを定義することが多いです。

オブジェクト指向 三大要素

オブジェクト指向にはオブジェクト指向の三大要素と呼ばれるものがあります。
それは

の3つです。
本によっては四大要素として、「抽象化」がプラスされていることもあります。
それぞれを簡単に解説します。

継承

継承は、特徴を受け継いでいくことをいいます。
例えば、「生き物」「哺乳類」「人間」「社会人」という4つのクラスがあったとします。
哺乳類は生き物に含まれます。
つまり、生き物の特徴を受け継いでいます。
生き物の特徴を受け継ぎつつも、哺乳類特有の機能を持っています。
人間は哺乳類に含まれます。
つまり、哺乳類の特徴を受け継ぎつつも、人間独自の機能や特徴を持っています。
社会人は、人間の中でも学生生活を終えて働き始めた人たちのことを挿します。
つまり、人間の特徴を持ちつつ、社会人にしかないステータスや行動をいいます。
この場合、哺乳類は生き物を継承し、人間は哺乳類を継承し、社会人は人間を継承していると言えます。

もう一つ例を出しましょう。
「乗り物」「車」「電気自動車」というクラスがあったとします。
車は乗り物に含まれます。
乗り物の特徴がありつつも、車独自の機能があります。
つまり、車は乗り物を継承しています。
電気自動車は車の特徴がありつつ、電気自動車独自の機能があります。
つまり、電気自動車は車を継承しています。

イメージとしては、継承される継承元の方が概念としては広く、継承先はより具体的になっていくようなイメージです。
継承のイメージができたでしょうか。

プログラミングに継承という概念が導入されると何が良いのでしょうか。
プログラミングでは、継承を使用することでソースコードの量を減らすことができます。

先に例に出した「人間」クラスと「社会人」クラスを考えます。
人間は年齢、名前、性別などがあります。
これらの情報を「人間」クラスに定義していたとします。
そして、人間クラスを継承した社会人クラスを作成したとします。
社会人も人間なので、年齢、名前、性別といった情報はありますが、それらの情報は人間クラスに定義されているので、社会人クラスでは改めて定義する必要がなくなります。
プログラムを書くときに、社会人だけに必要となる情報だけを追加していけばよくなります。
結果として、全体のコード量を減らすことができます。

オーバーライド

オーバーライドはオブジェクト指向の三大要素ではありませんが、継承と非常に深い関係がある概念のため、ここで説明しておきます。
オーバーライドは簡単にいえばメソッドの上書きです。
継承を使うと、親クラスに定義されているメソッドを無条件に使うことができますが、親クラスから受けついだメソッドを上書きすることが可能です。
このような仕組みのことをオーバーライドといいます。
この説明だけだと何のメリットがあるのかわからないと思いますが、後に説明するポリモーフィズムと非常に深い関わりを持ちます。

カプセル化

カプセル化は、オブジェクトが持つデータを外部から直接アクセスできないように隠蔽して、メソッドによってデータのアクセスを提供することです。
多くのプログラミング言語では、アクセス修飾子という仕組みがあります。
オブジェクトが持つプロパティやメソッドに対して、外部からどこまでアクセスを許可するかを設定するものですが、そのような仕組みを駆使してカプセル化を実現します。
説明だけでは理解しにくいかもしれません。
例えば人間は名前、年齢、などのデータを持ちますが、このようデータは他人が勝手に変えることはできません。
つまり、外部から勝手に変えることができないデータです。
現実世界では人の名前を他人が勝手に変更するなんてことはあり得ませんが、プログラムでオブジェクトを作成したとき、作り方によっては外部から簡単にデータを変更することができてしまいます。
そのような不都合が起きないようにする仕組みがカプセル化です。

ポリモーフィズム

ポリモーフィズムは日本語では多態性、多様性などと呼ばれます。
これは継承や抽象化を前提とした機能で、異なるオブジェクトを同一視して扱う機能です。
先に紹介したオーバーライドの機能があることで成り立つ非常に便利な機能です。

例えば、「プログラマークラス」と「営業クラス」があったとします。
プログラマーにプログラミングをして欲しい時には、プログラマークラスのオブジェクトを作成し、プログラミングをするメソッドを呼び出します。
営業に営業の仕事をして欲しかったら、営業クラスのオブジェクトを作成し、営業活動をするメソッドを呼び出します。
ただ、このプログラマーと営業の上司が同じ人だった場合、人によっていちいち指示する内容を変えるのは面倒ですね。
「働け」という同じ命令をすることで、プログラマーならプログラマーとしての働きを、営業なら営業としての働きをしてくれると便利です。
この仕組みを実現するのがポリモーフィズムです。
実際には「社員クラス」のようなクラスを作成し、「働く」メソッドを定義しておきます。
プログラマークラスと営業クラスは、社員クラスを継承し、働くメソッドをオーバーライドし、それぞれの働く内容を上書きします。
そうすると、社員に対して「働く」メソッドを実行してもらうと、そのオブジェクトがプログラマーならプログラマーとしての働きと、営業なら営業としての働きをしてくれるわけです。

メソッドや関数という概念は、呼び出される処理を共通化する仕組みですが、ポリモーフィズムは処理の呼び出しを共通化する仕組みだといえます。
文章の説明だけだと分かりにくいかと思いますが、ソースコードで確認する場合はJavaオブジェクト指向編の記事に書いているのでそちらを参照ください。

UML

UMLはUnified Modeling Languageの略で、オブジェクト指向の分析と設計をするときに使用するモデリング手法です。
いくつかの図(ダイアグラム)が用意されています。
よく使用するのはクラス図、シーケンス図、ユースケース図あたりでしょうか。
ここでは詳細は解説しません。
機会があれば別でUMLについての記事もまとめます。
クラス図はオブジェクト指向の理解を助けるのに重要な役割をはたしますので、クラス図については調べて理解しておくと良いでしょう。

まとめ

ソースコードがないと理解が難しい用語もあるかと思います。
Javaオブジェクト指向編の記事と合わせて読んでいただければと思います。

オブジェクト指向の理解を深めるおすすめ書籍

【プログラミング全般】デバッグの基本

ここではデバッグについて解説します。

デバッグとは

プログラム(ソフトウェア・アプリケーション)の不具合のことをバグと呼びます。
そしてバグを取り除く(プログラムの不具合を修正する)ことをデバッグと呼びます。
バグは日本語では虫の意味があります。
なぜプログラムの不具合のことをバグと呼ぶかというと、昔、コンピュータの隙間に虫(蛾)が入り込み、コンピュータが正常に動かなくなることがあったそうです。
そこで、リアルに虫を取り除いてコンピュータの不具合を取り除いていたそうです。
その名残で、ソフトウェアの不具合もバグと呼ぶようになり、不具合の修正をデバッグを呼ぶようになったそうです。

プログラムの不具合と言っても色々な種類があります。
プログラムの文法が間違っている場合のエラー、実行したときに発生するエラーもあります。
また、エラーが発生しなくても、プログラムが意図した通りに動かない場合は不具合になります。
(例えば計算結果が想定した通りにならない、など)
そういった不具合の原因を調べて解決していく作業のことをデバッグと呼びます。

デバッグ力の重要性

デバッグの能力はエンジニア(プログラマー)として働いていく上では非常に重要なスキルで、デバッグ力=プログラミング力と言っても過言ではありません。
どんなにプログラミングの知識があって経験のあるエンジニアだとしても、作るプログラムの規模が大きくなると、エラーやバグのないプログラムを作ることは不可能に近いです。
人間はミスをする生き物ですし、どんなに頭の良い人でも1人の頭で考えたことは漏れがある可能性も高く、全体を把握するのも難しいです。
プログラムは人が書く以上バグが混在すると思って作る必要があります。
つまりプログラミングをする中でバグを解決する力というのは、プログラミングの基本的な知識と同じレベルで求められる能力になります。

デバッグ関連用語

デバッガ

大抵のプログラミング言語では、開発環境の中にデバッグをサポートするための機能が組み込まれています。
このような機能のことをデバッガと呼びます。
デバッガを使うと、実行中の処理を途中で止めることができます。
そして、その時点で使用することができる変数の値を確認したり、処理を1行ずつ進めながらどこでエラーが発生するかを確認したりすることができます。

ブレークポイント

デバッガを使うと、プログラム実行中に処理を止めることができますが、ソースコード上のどこで処理を止めるかを事前に設定する必要があります。
その処理を止める場所のことをブレークポイントと呼びます。
ブレークポイントを設定した後、デバッグモードでプログラムを実行すると、処理がブレークポイントに到達した時点で一度処理が止まります。
処理が止まると、その時点で使用することができる変数の中身を確認することができます。
また、そこから処理を再開したり、処理を1行ずつ進める(ステップ実行)ことができます。

ステップ実行

プログラムの処理をソースコードの1ステップずつ実行することをステップ実行と呼びます。
デバッグを行うときによく使用します。
ステップ実行にはステップオーバーとステップインの2つがあります。

ステップオーバー

ステップオーバーはステップ実行の1つです。
ステップ実行をしている中でメソッドや関数を呼び出していることもありますが、ステップオーバーの場合はそのメソッドや関数の中には入りこまず、あくまでも現在実行中のメソッド・関数の中を1行ずつ実行します。

ステップイン

ステップインでは、メソッドや関数を呼び出している場合、呼び出し先の処理の中にも入りこんでステップ実行します。

ステップリターン

ステップリターンでは、現在処理中のメソッドや関数の終了まで処理を実行し、呼び出し元まで戻ります。

デバッグの方法はプログラミング言語・開発環境によって異なりますが、ブレークポイント、ステップオーバー、ステップインといった用語は共通していることがほとんどです。
どの言語、環境であっても、デバッグに慣れることで、他の言語で応用が効くようになります。

標準出力とデバッグ

ほとんどのプログラミング言語では、標準出力(コンソールへの出力)を行う命令が用意されています。
Javaの場合は「System.out.print」メソッド。
JavaScriptの場合は「console.log」メソッド。
PHPの場合はvardump関数など。(これはブラウザへの出力ですが)
これらの出力の命令を駆使して、変数の値を確認することでデバッグをすることもあります。
むしろプログラミングを学習したばかりの頃はこの手法で確認をする方が一般的かもしれません。
デバッガを使ったデバッグは、慣れるまでは面倒に感じます。
しかし、プログラムの規模が大きくなったりすると、デバッガを使ってデバッグする方が圧倒的に効率的になりますので、なれて使いこなせるようにしておきましょう。

言語・環境ごとのデバッグ方法

いくつかの言語・環境のデバッグ方法を紹介します。

JavaScript

JavaScriptWebブラウザデバッグを行うことができます。
Google Chromeの場合、F12、またはCtrl + Shift + I でディベロッパーツールが起動します。
ディベロッパーツールのSourceタブを開くと、現在表示されているWebページ上のJavaScriptソースコードを確認できます。
ソースコードの行数の個所をクリックすると、ブレークポイントを設定することができます。
Webページの読み込みや、画面上のイベントなど、該当のソースコードを実行される処理が走るとブレークポイントで処理を止めることができます。
そこからステップ実行したり、変数の値を確認することができます。

【JavaScript】スロットゲーム

JavaScriptで作成したスロットゲームです。
ドットインストールを参考にしようとしつつ、結局全く違うコードになってしまいましたが、とりあえずは動きます。
JavaScriptの学習やゲーム作成の参考にどうぞ。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>スロットゲーム</title>
    <style>
        body {
            text-align: center;
        }
        main section p {
            font-size: 36px;
            height: 100px;
            line-height: 100px;
            margin: 0;
            background-color: #f5f5f5;
        }
        main {
            background-color: #e6e6fa;
            width: 360px;
            margin: 50px auto;
            display: flex;
            justify-content: space-between;
        }
        section {
            width: 100px;
            margin: 10px;
        }
        section button {
            width: 100px;
        }
        #start {
            width: 150px;
            height: 40px;
            font-size: 24px;
        }

    </style>
</head>
<body>
    <h1>スロットゲーム</h1>
    <main>
        <section>
            <p id="slot1"></p>
            <button id="stop1">STOP</button>
        </section>
        <section>
            <p id="slot2"></p>
            <button id="stop2">STOP</button>
        </section>
        <section>
            <p id="slot3"></p>
            <button id="stop3">STOP</button>
        </section>
    </main>
    <button id="start">START</button>

    <script>
        'use strict';

        const nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        const slot1 = document.getElementById('slot1');
        const slot2 = document.getElementById('slot2');
        const slot3 = document.getElementById('slot3');
        const stop1 = document.getElementById('stop1');
        const stop2 = document.getElementById('stop2');
        const stop3 = document.getElementById('stop3');
        const start = document.getElementById('start');

        let setTimeoutId1 = undefined;
        let setTimeoutId2 = undefined;
        let setTimeoutId3 = undefined;

        start.disabled = false;
        stop1.disabled = true;
        stop2.disabled = true;
        stop3.disabled = true;
        slot1.textContent = nums[Math.floor(Math.random() * nums.length)];
        slot2.textContent = nums[Math.floor(Math.random() * nums.length)];
        slot3.textContent = nums[Math.floor(Math.random() * nums.length)];


        function setNum1() {
            slot1.textContent = nums[Math.floor(Math.random() * nums.length)];
            setTimeoutId1 = setTimeout(() => {
                setNum1();
            }, 100);
        }

        function setNum2() {
            slot2.textContent = nums[Math.floor(Math.random() * nums.length)];
            setTimeoutId2 = setTimeout(() => {
                setNum2();
            }, 100);
        }

        function setNum3() {
            slot3.textContent = nums[Math.floor(Math.random() * nums.length)];
            setTimeoutId3 = setTimeout(() => {
                setNum3();
            }, 100);
        }

        function statusCheck() {
            if(start.disabled === true && stop1.disabled === true && stop2.disabled === true && stop3.disabled === true) {
                start.disabled = false;
                stop1.disabled = true;
                stop2.disabled = true;
                stop3.disabled = true;
            }
        }

        start.addEventListener('click', () => {
            start.disabled = true;
            stop1.disabled = false;
            stop2.disabled = false;
            stop3.disabled = false;
            setNum1();
            setNum2();
            setNum3();
        });

        stop1.addEventListener('click', () => {
            stop1.disabled = true;
            clearTimeout(setTimeoutId1)
            statusCheck();
        });

        stop2.addEventListener('click', () => {
            stop2.disabled = true;
            clearTimeout(setTimeoutId2)
            statusCheck();
        });

        stop3.addEventListener('click', () => {
            stop3.disabled = true;
            clearTimeout(setTimeoutId3)
            statusCheck();
        });      


    </script>
</body>
</html>

【JavaScript】タイピンゲーム2

以前JavaScriptでのタイピングゲーム1のソースコードを載せましたが、少し改良したのでそちらも載せます。
改善点としては、

  • リアルタイムで時間が表示される
  • ストップボタンを追加
  • 出題される単語のテーマを選べるように

といった感じです。
こちらもJavaScriptの学習やゲーム作成の参考にどうぞ。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>タイピングゲーム</title>
    <style>
        body {
            text-align: center;
        }
        #target {
            font-size: 48px;
        }
        .btn, #time, #count {
            font-size: 18px;
        }
    </style>
</head>
<body>
    <h1>タイピングゲーム</h1>
    <p>単語テーマ
        <select id="word-theme" name="word-theme">
            <option value="color">カラー</option>
            <option value="fruits">フルーツ</option>
        </select>
    </p>
    <p>
        <button class="btn" type="button" id="start-btn" name="start-btn">スタート<br>(Enter)</button>
        <button class="btn" type="button" id="stop-btn" name="stop-btn">ストップ<br>(ESC)</button>
    </p>
    <p id="target"></p>
    <p id="time"></p>
    <p id="count"></p>
    <script>
        'use strict';

        const target = document.getElementById('target');
        const time = document.getElementById('time');
        const count = document.getElementById('count');
        const startBtn = document.getElementById('start-btn');
        const stopBtn = document.getElementById('stop-btn');
        startBtn.disabled = false;
        stopBtn.disabled = true;

        let words = [];
        let wordCount = 0; // 完了した文字数
        let word = ''  // 現在入力中の文字
        let index = 0; // 文字の現在地
        let missCount = 0;
        let startTime = 0;
        let setTimeoutId = 0;

        function dispTime() {
            time.textContent = ((new Date - startTime) / 1000).toFixed(2) + '秒';
            setTimeoutId = setTimeout(() => {
                dispTime();
            }, 50);
        }

        // ゲームスタート時の処理
        function gameStart() {
            startBtn.disabled = true;
            stopBtn.disabled = false;
            const wordTheme = document.getElementById('word-theme').value;
            switch(wordTheme) {
                case 'color':
                    words = ['red', 'blue', 'green', 'yellow', 'pink', 'black', 'white', 'purple', 'gray', 'orange'];
                    break;
                case 'fruits':
                    words = ['apple', 'banana', 'grape', 'peach', 'orange', 'pear', 'melon', 'lemon', 'strawberry', 'mango'];
                    break;
                default :
                    words = ['abcde'];
                    break;
            }

            wordCount = words.length;
            missCount = 0;
            count.textContent = `残り ${wordCount} つ`;
            startTime = new Date();
            word = words.splice(Math.floor(Math.random() * words.length), 1).join();
            target.textContent = word;
            dispTime();
        }

        // ゲームストップ
        function gameStop() {
            clearTimeout(setTimeoutId);
            // target.textContent = '';
            // time.textContent = '';
            // count.textContent = '';
            startBtn.disabled = false;
            stopBtn.disabled = true;
        }

        // ボタン押下時
        document.getElementById('start-btn').addEventListener('click', gameStart, false);
        document.getElementById('stop-btn').addEventListener('click', gameStop, false);

        // キー押下時
        document.addEventListener('keydown', e => {
            // Enter押下時はスタート
            if(e.key === 'Enter' && startBtn.disabled === false) {
                gameStart();
                return;
            }
            // ESCキー押下でストップ
            if(e.key === 'Escape' && stopBtn.disabled === false) {
                gameStop();
                return;
            }

            if(stopBtn.disabled === true) {
                return;
            }

            if(word.charAt(index) === e.key)  {
                index++;
                target.textContent = '_'.repeat(index) + word.substring(index);

                if(index === word.length) {
                    // 1単語終了時
                    index = 0;
                    wordCount--;
                    count.textContent = `残り ${wordCount} `;

                    if(wordCount === 0) {
                        // ゲーム終了時
                        clearTimeout(setTimeoutId);
                        count.textContent = `タイプミス ${missCount}個`;
                        startBtn.disabled = false;
                        stopBtn.disabled = true;
                    } else {
                        word = words.splice(Math.floor(Math.random() * words.length), 1).join();
                        target.textContent = word;
                    }
                }
            } else {
                missCount++;
            }  

        });

    </script>
</body>
</html>

【JavaScript】タイピングゲーム1

JavaScriptで作成した簡単なタイピングゲームです。
ドットインストールで紹介されていたJavaScriptでのタイピングゲームの動画を参考にアレンジを加えたものです。
JvaScriptの学習、ゲームの作成の参考に。
もう少し改良したいのでバージョンアップしたら別記事でアップします。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>タイピングゲーム</title>
    <style>
        body {
            text-align: center;
        }
        #target {
            font-size: 48px;
        }
        #start-btn {
            font-size: 24px;
        }
    </style>
</head>
<body>
    <h1>タイピングゲーム</h1>
    <p><button type="button" id="start-btn" name="start-btn">スタート<br>(Enter)</button></p>
    <p id="target"></p>
    <p id="count"></p>
    <script>
        'use strict';

        let words = [];
        let count = 0; // 完了した文字数
        let word = ''  // 現在入力中の文字
        let index = 0; // 文字の現在地
        let missCount = 0;
        const target = document.getElementById('target');
        let startTime = 0;

        // ゲームスタート時の処理
        var gameStart = () => {
            words = ['red', 'blue', 'green', 'yellow', 'pink', 'black', 'white', 'purple', 'gray', 'orange'];
            count = words.length;
            missCount = 0;
            document.getElementById('count').textContent = `残り ${count} つ`;
            startTime = new Date();
            word = words.splice(Math.floor(Math.random() * words.length), 1).join();
            target.textContent = word;
        };

        // ボタン押下時
        document.getElementById('start-btn').addEventListener('click', gameStart, false);

        // キー押下時
        document.addEventListener('keydown', e => {
            // Enter押下時はスタート
            if(e.key === 'Enter') {
                gameStart();
                return;
            }

            if(word.charAt(index) === e.key)  {
                index++;
                target.textContent = '_'.repeat(index) + word.substring(index);

                if(index === word.length) {
                    // 1単語終了時
                    index = 0;
                    count--;
                    document.getElementById('count').textContent = `残り ${count} `;

                    if(count === 0) {
                        // ゲーム終了時
                        const clearTime = new Date() - startTime;
                        target.textContent = (clearTime / 1000).toFixed(2) + '秒';
                        document.getElementById('count').textContent = `タイプミス ${missCount}個`;                       
                    } else {
                        word = words.splice(Math.floor(Math.random() * words.length), 1).join();
                        target.textContent = word;
                    }
                }
            } else {
                missCount++;
            }  

        });

    </script>
</body>
</html>