【PHP/Docker/Twig】初めて生PHPのアプリケーション作成してみた【Pスク生必見!?】

# はじめに
初めまして。Ricccckです。

私は、ReactやJavaScriptには、以前触れたことがあるのですが、今回は、初めてのバックエンド言語。初めてPHPに触れ、わからないこと、難しいことも多くありました...。

そんな中で、初めてアプリケーションを作成したので、今回は、その備忘録を書き留めておこうと思います。

この記事では、コードの詳しい内容までは触れず、「全体のモデルの話」や「コードの書き方」に注目して記事をまとめていきます。DockerとPHPの連携、GDライブラリの使用などで詰まった点もあったので、他の初学者の方に、少しでも参考になればと思います！

今は、twigを用いたフロント実装はあまりないかとも思いますが、Laravelではない **「生のPHP」** を触る機会がある場合には、うってつけの教材になると思いますので、ぜひ触れてみてください。

以下敬略にて失礼します。


# ビルドイメージ
### ロゴ画像
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3873077/78dd287a-6cbe-a8bc-e90f-2c3abd0c1406.png" width=50%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3873077/1f0e2edb-815f-63d5-278c-807d26852637.png" width=50%>

### トップ画像
![スクリーンショット 2024-08-29 23.08.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3873077/c524f5ac-c0de-d944-175b-dd36dea27d0d.png)

# 機能
- 画像一覧
- 画像検索
- ユーザー登録（Client, Customer）
- ログイン
- ユーザー情報閲覧
- マイページ
- 画像投稿
- 画像購入
- 購入画像ダウンロード

# ディレクトリ構成
```
PhotoStudio
├ docker ┐
│        ├ mysql ┐
│        │       ├ data --- init.sql
│        │       ├--- Dockerfile
│        │       └--- my.cnf
│        ├ nginx ┐
│        │       ├--- default.conf
│        │       └--- Dockerfile
│        └ php ┐
│              ├--- Dockerfile
│              └--- php.ini
├ src　-┐  
│　　　　├　app　┐
│  　   │     　├　css ┐
│  　   │    　 │      ├---　(各twigテンプレート用のCSSファイル)
│  　   │     　│　　　 └---　common.css
│  　   │    　 ├　fonts ---　BlackOpsOne-Regular.ttf
│  　   │    　 ├　js　┐
│  　   │  　   │  　　├---　addTag.js
│  　   │    　 │  　　├---　changeFavicon.js
│  　   │    　 │  　　├---　fileDrop.js
│  　   │    　 │  　　└---　sendCartId.js
│  　   │    　 ├　logs
│  　   │    　 ├ public ┐
│  　   │    　 │        ├　sample(写真のサンプルを保存するフォルダ)
│  　   │    　 │        ├　upload(アップロードされた写真を保存するフォルダ)
│  　   │    　 │   　　　├---　favicon-black.ico
│  　   │    　 │   　　　├---　favicon-white.ico
│  　   │    　 │   　　　├---　logo-black.png
│  　   │    　 │        └---　logo-white.png
│  　   │    　 └　(各Modelファイル)
│  　   ├ libs ---　(各Contollerファイル)
│    　 ├ logs --- photostudio.log
│   　  ├ templates ┐
│   　  │           ├ admin --- (admin関係のtwigファイル)
│   　  │           ├ authentication　---- (ログイン関係のtwigファイル)
│    　 │           ├ client --- (client関係のtwigファイル)
│    　 │           ├ common --- (共通のtwigファイル)
│    　 │           ├ customer --- (customer関係のtwigファイル)
│   　  │           └--- home.html.twig
│    　 └- vendor 
├--- .env 
├--- composer.json
├--- composer.lock
├--- docker-compose.yml
└--- README.md
```

# Docker構築
### 0\. dockerをインストール
Googleで「Docker」と検索するか
以下のURLからDockerをインストールする
    
https://docs.docker.com/get-started/get-docker/

https://qiita.com/cleyera_f/items/bdd3d33f13527604a663


### 1\. docker-compose.ymlを作成
```
PhotoStudio
├--- docker-compose.yml
└--- README.md
```
<details>
<summary>docker-compose.yml</summary>

```yml
version: '3' # dockerのバージョン

services: #　以下に使用するサービスを書いていく
  nginx: #　画面表示のサービス
    image: nginx:alpine
    #　使用するサービスのバージョン
    container_name: nginx
    # コンテナ名
    ports:　
    # コンテナが使用するポート番号
      - "8000:80"
    volumes:
    # buildするにあたって参照するデータなどのパス
    # (docker-compose.ymlからデータまでのパス):(dockerコンテナでのパス)
      - ./src:/var/www/html
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    networks:
    # アプリケーションが所属するネットワーク名
      - app-network
    
  php: # サービス全般
    image: php:8.2.12-fpm
    build: 
      context: .
      dockerfile: docker/php/Dockerfile
      # phpをビルドする際に参考する設定ファイル（重要）
    volumes:
      - ./src:/var/www/html
      - ./docker/php/php.ini:/usr/local/etc/php/php.ini
    networks:
      - app-network

  mysql: # データベース
    image: mysql:8.0
    container_name: mysql
    ports:
      - '3306:3306'
    environment: # 環境変数
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASS}
      MYSQL_ROOT_PASSWORD: ${ROOT_PASSWORD}
    volumes:
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./docker/mysql/data/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-network

  phpMyAdmin: # データベース画面管理
    image: phpmyadmin/phpmyadmin
    container_name: phpmyadmin
    environment:
      PMA_HOST: mysql
      MYSQL_ROOT_PASSWORD: ${ROOT_PASSWORD}
    depends_on:
      - mysql
    ports:
      - "3000:80"
    networks:
      - app-network

networks:
  app-network:
    driver: bridge
```
</details>

docker-compose.ymlでは、dockerで使用するサービスを定義する

### 2\. Dockerfileファイルを作成
それぞれに`Dockerfile`を作成する
```
PhotoStudio
├ docker ┐
│        ├ mysql --- Dockerfile
│        ├ nginx --- Dockerfile
│        └ php   --- Dockerfile
├--- docker-compose.yml
└--- README.md
```
<details>
<summary>mysql/Dockerfile</summary>

```Dockerfile
FROM mysql:latest
# mysqlのバージョン

ENV TZ=Asia/Tokyo
# タイムゾーン

COPY ./docker/mysql/my.cnf /etc/mysql/conf.d/my.cnf
# mysqlの設定ファイル
COPY ./docker/mysql/data /docker-entrypoint-initdb.d
# 起動時のテーブル読み込み
```
</details>
<details>
<summary>nginx/Dockerfile</summary>

```Dockerfile
FROM nginx:latest
# nginxのバージョン

ADD ./docker/nginx/default.conf /etc/nginx/conf.d/default.conf
# nginxの設定ファイルを追加
ADD ./src /usr/share/nginx/html
# コンテナで表示するソースを追加

RUN echo "start nginx"
# nginxを起動
```
</details>
<details>
<summary>php/Dockerfile</summary>

```Dockerfile
FROM php:8.2.12-fpm
# phpのバージョン

RUN apt-get update && apt-get install -y \
	git \
	curl \
	zip \
	unzip \
	libfreetype6-dev \
	libjpeg62-turbo-dev \
	libpng-dev \
	&& docker-php-ext-configure gd --with-freetype --with-jpeg \
	&& docker-php-ext-install -j$(nproc) gd \
	&& docker-php-ext-install pdo_mysql
 # 諸設定とgdライブラリ, pdo_mysqlライブラリの追加
 

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# composerのインストール

COPY ./src /var/www/html/
COPY ./composer.json /var/www/html/
COPY ./composer.lock /var/www/html/
# コンテナで使用するデータをコピーする

WORKDIR /var/www/html
# 作業ディレクトリの定義
RUN composer install
# composer installの実行

RUN chown -R www-data:www-data /var/www/html \
	&& chmod -R 755 /var/www/html

EXPOSE 80

CMD [ "php-fpm" ]
```
</details>

### 3\. 各サービスの設定ファイルを追加
`mysql/my.conf`, `ngnix/default.conf`, `php.ini`を作成する
```
PhotoStudio
├ docker ┐
│        ├ mysql ┐
│        │       ├--- Dockerfile
│        │       └--- my.cnf
│        ├ nginx ┐
│        │       ├--- default.conf
│        │       └--- Dockerfile
│        └ php ┐
│              ├--- Dockerfile
│              └--- php.ini
├--- docker-compose.yml
└--- README.md
```
<details>
<summary>mysql/my.conf</summary>

```conf
# データベースの文字コードをutf8に
[mysqld]
character-set-server=utf8 
collation-server=utf8_general_ci　

[client]
default-character-set=utf8
```
</details>
<details>
<summary>ngnix/default.conf</summary>

```conf
server {
    listen 80;
    server_name localhost;

    client_max_body_size 500M;


    root /var/www/html/app;
    index home.php;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index home.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    add_header X-Frame-Options "DENY";
    add_header X-Content-Type-Options "nosniff";
    add_header X-XSS-Protection "1; mode=block";
}
```
</details>
<details>
<summary>php/php.ini</summary>

```ini
# 写真共有アプリなのでアップロードできる画像サイズを大きめに
upload_max_filesize = 500M
post_max_size = 500M
memory_limit = 512M
```
</details>

### 4\. mysqlに初期テーブルを作成
`docker/mysql`に`data`フォルダを作成し、
dataフォルダ直下に`init.sql`を作成する
```
PhotoStudio
├ docker ┐
│        ├ mysql ┐
│        │       ├ data --- init.sql
│        │       ├--- Dockerfile
│        │       └--- my.cnf
│        ├ nginx 
│        └ php 
├--- docker-compose.yml
└--- README.md
```
<details>
<summary>init.sql</summary>

```sql
CREATE TABLE IF NOT EXISTS clients (
  client_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(100) NOT NULL,
  first_name VARCHAR(100) NOT NULL,
  last_name VARCHAR(100) NOT NULL,
  first_name_kana VARCHAR(100) NOT NULL,
  last_name_kana VARCHAR(100) NOT NULL,
  company_name VARCHAR(100),
  email VARCHAR(255) NOT NULL,
  phone_number VARCHAR(11) NOT NULL,
  sex TINYINT(1) NOT NULL,
  zip VARCHAR(8) NOT NULL,
  pref VARCHAR(100) NOT NULL,
  city VARCHAR(100) NOT NULL,
  town VARCHAR(100) NOT NULL,
  website VARCHAR(255),
  profile_picture VARCHAR(255),
  regist_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  is_deleted TINYINT(1) UNSIGNED NOT NULL DEFAULT 0
);


CREATE TABLE IF NOT EXISTS client_pass (
  client_id INT UNSIGNED PRIMARY KEY,
  password_hash VARCHAR(255),
  FOREIGN KEY(client_id) REFERENCES clients(client_id) ON DELETE CASCADE
);



--------- 中省略　---------



-- サンプルデータ
INSERT INTO
  clients (
    username,
    first_name,
    last_name,
    first_name_kana,
    last_name_kana,
    company_name,
    email,
    phone_number,
    sex,
    zip,
    pref,
    city,
    town,
    website,
    profile_picture
  )
VALUES
  (
    'Taro106',
    '太郎',
    '山田',
    'タロウ',
    'ヤマダ',
    'サンプル株式会社',
    'client@example.com',
    '09012345678',
    1,
    '1000001',
    '東京都',
    '千代田区',
    '1-1-1',
    'http://www.example.com',
    'profile1.jpg'
  );

--------- 以下省略　---------
```
- `CREATE TABLE IF NOT EXISTS テーブル名 (　カラム名
);` テーブルが存在していない場合、テーブルを作成するコマンド
- `INT, VARCHER, TINYINT` データ型の宣言
- `NOT NULL` NULLを許容しない
- `~_at` DATE型のデータを収納するカラム名は`~_at`か`~_date`とする
- `DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`　登録されているデータが更新されるたびに現在の日付で更新する
- `is_~` フラグ管理を行う際のカラム名は、`is_~, has_~`とするか、`~_flg`とする
- `FOREIGN KEY(カラム名) REFERENCES 参照するテーブル(参照するカラム名) ON DELETE CASCADE` 外部キーで関連付けを行う。`ON DELETE CASCADE`を使用することで、参照先が削除された場合、このデータも同時に削除を行うことが可能になる

- `INSERT INTO テーブル名 (カラム名1, カラム名2) VALUES (カラム名1のデータ, カラム名2のデータ),(...)` テーブルにデータを挿入する際に使用するコマンド。`,`で区切ることにより複数のデータを同時に挿入することが可能
</details>
sqlファイルは理解もだが、テーブルの作成や改善に時間が取られた。
Schemaの作成を先に行い、テーブルを作成する癖をつけていきたい。

### 5\. Dockerの起動
`docker-compose build`コマンドを実行し、コンテナを作成![スクリーンショット 2024-08-30 11.03.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3873077/f1105cc1-80b3-fdeb-ebf5-9b3ec254994b.png)
![スクリーンショット 2024-08-30 11.04.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3873077/f5fdeda8-327b-75e1-7c22-6df6dd8a7d54.png)
問題なく実行できていれば上記のようになる。

# MVCモデルについて
本アプリケーションでは、MVCモデルを採用している。
MVCモデルとは、`Model`, `View`, `Controller`が相互的に作用するアプリケーションモデルのことだ。従来のコードが1つのファイルに全ての情報を記載していたのに比べ、MVCモデルでは一つのファイルに多くのコードが書かれることを嫌い、簡潔に管理のしやすいアプリケーションができる。

https://qiita.com/s_emoto/items/975cc38a3e0de462966a

今回のアプリケーションでは
- `app/~.php`
- `templates/~.html.twig`
- `libs/~.class.php`
がそれぞれの役割を果たしている。

### Model
Modelは、ViewとControllerを繋ぐ役割を担っており、Controllerにどんなデータをデータベースに送って、受け取って、Viewに表示させるのかの処理を書く。
また、できるだけコードを短く保つ必要があり、複雑な処理をする場合もあるが、基本的にはシンプルな構成になっている。

以下は、一例である。

```php
// home.php
<?php
namespace Photostudio;
// このファイルをPhotostudioに割り振り

require_once  __DIR__ . '/../lib/Bootstrap.class.php';
// require_onceでBootstrap.class.phpを呼び出し
// autoloadを起動し、残りのControllerをrequire_onceする必要がなくなる

use Photostudio\libs\Bootstrap;
use Photostudio\libs\PDODatabase;
use Photostudio\libs\Photo;
use Photostudio\libs\Client;
use Photostudio\libs\Customer;
//　使用するクラスを読み込む

$db = new PDODatabase(Bootstrap::DB_HOST, Bootstrap::DB_USER, Bootstrap::DB_PASS, Bootstrap::DB_NAME, Bootstrap::DB_TYPE);
$photo = new Photo($db);
$client = new Client($db);
$customer = new Customer($db);
// 使用クラスのインスタンス化


$loader = new \Twig\Loader\FilesystemLoader(Bootstrap::TEMPLATE_DIR);
$twig = new \Twig\Environment($loader, [
  'cache' => Bootstrap::CACHE_DIR
]);
// twigの環境設定


$ctg_id = (isset($_GET['ctg_id']) === true && preg_match('/^[0-9]+$/', $_GET['ctg_id']) === 1) ? $_GET['ctg_id'] : '';
// 三項演算子
// 変数 = 条件式 ? 正の場合 : 誤の場合

$ctgArr = $photo->getCategoryList();
$photoArr = $photo->getPhotoList($ctg_id);


$dataArr = [];
if($photoArr !== false){
$randomPhoto = array_rand($photoArr);
$dataArr['random_id'] = $photoArr[$randomPhoto]['photo_id'];
$dataArr['random_url'] = $photoArr[$randomPhoto]['photo_url'];
}

$userArr = [];
if (isset($_SESSION['client'])) {
  $userArr = $client->getData($_SESSION['client']);
} elseif (isset($_SESSION['customer'])) {
  $userArr = $customer->getData($_SESSION['customer']);
} else {
  $userArr['username'] = 'Guest';
}
// ユーザー情報の取得
// インスタンス化された関数からメソッドを使用してデータを取得



$context = [];
$context['ctgArr'] = $ctgArr;
$context['photoArr'] = $photoArr;
$context['dataArr'] = $dataArr;
$context['userArr'] = $userArr;
// twigファイルに送るデータを多次元連想配列で作成
$template = $twig->load('home.html.twig');
// どのtwigファイルをレンダリングするのかを決める
$template->display($context);
// 実際にtwigファイルをレンダリングする
```
この場合、インスタンス化されたクラスが、Controllerの役割を果たしており、twigがViewの役割を果たしている。

### View
Viewは、ModelがContorollerから受け取ったデータをユーザーに表示し、ユーザーが入力したデータをモデルに送信する役割を持つ。
twigは、HTMLにPHPから送信されたデータを使いやすくしたライブラリで、`{{}}`と打つことで、どこにでも値を引いてくることができる。
注意点は、モデル内でレンダリングする際に、必要なデータは、必ず渡してやらなければならない点だ。

```twig
home.html.twig
<!DOCTYPE html>
<html lang="ja">
	<head>
		<meta charset="utf-8"/>
		<meta
		name="viewport" content="width=device-width, initial-scale=1">

		{# Bootstrap #}
		<link
		href="（省略）" crossorigin="anonymous">

		{# favicon #}
		<link
		rel="icon" href="/public/favicon-black.ico"/>

		{# javascript #}
		<script src="js/changeFavicon.js"></script>

		{# css #}
		<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
		<link rel="stylesheet" href="css/common.css" type="text/css"/>
		<link
		rel="stylesheet" href="css/home.css" type="text/css"/>

		{# title #}
		<title>PhotoStudio -ホーム-</title>
	</head>

	<body>
		{% include 'common/header.html.twig' %}
        {# includeを使用することで、 別のtwigファイルを読み込ませることが可能 #}

		<main
			class="contents">
			{# photo-search #}
			<div id="photo-search" class="mb-3 bg-secondary">
				<div id="search-bg-img" style="background-image: url('public/upload/{{dataArr.random_url}}');">
					<form method="get" id="search-form" action="list.php">
						<input id="search-window" type="search" name="keyword" placeholder="キーワードを入力">
						<button id="search-btn">
							<i class="fa-solid fa-magnifying-glass"></i>
						</button>
					</form>
				</div>
			</div>

			{# random-photo #}
			<div id="random-photo" class="mb-3">
				<h1 class="title">
					ランダム紹介
				</h1>
				<div class="photo-list">
					{% for value in photoArr %}
						<div class="item">
							<ul>
								<li class="image">
									<a href="detail.php?photo_id={{value.photo_id}}">
										<img src="public/sample/{{value.sample_url}}" alt="現在利用できません"/>
									</a>
								</li>
								<li class="name">
									<a href="detail.php?photo_id={{value.photo_id}}">{{value.photo_title}}</a>
								</li>
							</ul>
						</div>
					{% endfor %}
				</div>
			</div>

			{# choice-category #}
			<div id="choice-category" class="mb-3">
				<h1 class="title">
					カテゴリーから探す
				</h1>
				<div id="category-list">
					{% for value in ctgArr %}
						<div class="category">
							<div class="ctg-bg-img" style="background-image: url('public/ctg_images/{{value.category}}.jpg');">
								<div class="ctg-str">
									<a href="list.php?ctg_id={{value.category_id}}">
										{{value.category}}
									</a>
								</div>
							</div>
						</div>
					{% endfor %}
				</div>
			</div>
		</main>


		{% include './common/footer.html.twig' %}

		{# Bootstrap #}
		<script src="（省略）" crossorigin="anonymous"></script>
	</body>
</html>

```
以上のように、基本的には、HTMLとほぼ変わらない。ところどころで、HTMLタグに埋め込むように、twig変数を使用して画面にデータを表示している。文章中で、forを発生させることができるため、Reactにおける`.map`のような使い方をすることができる。
今回はユーザーに見やすくなるように、Bootstrapを~~かなり後から~~採用した。~~そのせいで挙動エラー、表示エラーが死ぬほど出た。~~
#### Bootstrap
`Bootstrap`は、`MaterialUI`や`shadcn`といった、UIのHTML版とも言えるようなUIライブラリで、HTMLコードにScriptを埋め込むことで、簡単に実装を行うことができる。もちろん、Reactでも使用可能。
HTMLタグのclass属性にcomponents名を入れ、それぞれのコンポーネントを画面に表示することができる。
CSSほど自由度は効かないが、CSSほど複雑ではないので、ぜひ一度、触れてみてほしい。

https://getbootstrap.jp/

### Contoroller
Controllerは、ModelがViewから受け取ったデータをデータベースに保存したり、ModelがViewに表示したい情報をデータベースや自身から送る役割を持つ。
機能が増えることから、コードもある程度長くなることが予想されるが、コンパクトなコードが良いことに変わりはないため、常にコードはクリーンに保てるよう努力する必要がある。
今回は、PHPのclassに、Controllerの役割をしてもらった。
Laravelを触る際も、このMVCモデルの動きは出てくるので、頭の中に留めておいてほしい。
以下は一例である。

```php
// Cart.class.php
<?php
namespace Photostudio\lib;
// namespaceをPhotostudio\libに割り振り

class Cart　// クラス名
{
  private $db = null; 
  //データをやり取りする際に使う別のクラス用の変数
  //privateは外部からアクセスすることができなくなる宣言

  public function __construct($db = null) // 初回に関数を呼び出した時に呼び出される関数
  {
    $this->db = $db;
    // private $dbにconstructの引数で受け取ったdbのクラスを入れる
  }

  public function getCartList($customerId) //　外部から呼び出すことができるメソッドを定義
  {
    $table = ' cart c JOIN upload_photos up ON c.photo_id = up.photo_id LEFT JOIN price p ON up.price = p.price_id ';
    $col = ' crt_id, photo_title, sample_url, p.price ';
    $where = ($customerId !== '') ? ' c.customer_id = ? AND is_purchased = ? AND c.is_deleted = ? ' : '';
    // どのテーブルのどの行のどの情報を受け取るのか

    $arrVal = ($customerId !== '') ? [$customerId, 0, 0] : [];
    $res = $this->db->select($table, $col, $where, $arrVal);
    // dbのselectメソッドを呼び出して$resを取得

    return ($res !== false && count($res) !== 0) ? $res : [];
    // 取得したデータに合わせて結果を返す
  }

    /* ---------- 後略 ----------- */
```
以上のようなクラス（実際にはメソッドがもっとたくさんある）を作成し、Modelファイルが呼び出すことで、データのやり取りを行う。データベースとのやり取りを行う際には、別のControllerクラスを利用することもある。

# 最後に
### アイデア出しに悩んでいる方へ
「Pスクも、残りわずか...成果物を作れって言われてるけど、何作ればいいんだろうなぁ...」
と、そんなこと思っている、そこの貴方！開発のきっかけは、意外なものだったりしますよ！

私が今回、このアプリケーションにしようと思ったきっかけは「有料写真販売サイトなどで、sampleと文字が書かれた画像は、まさか人間が書いているわけじゃあないでしょ」と思ったからです。
それっぽっちの小さなことが、アプリケーションを作るきっかけになります。

まずは、既存サイトの模倣を行うことで、サイトの開発の仕方を学んでいってください。
模倣ができたら、まずは「プラス1のアイデア」を大切にしていってください。

私の今回のアプリケーションでは、画像サイズで画像の値段を決める点や、ドラッグ&ドロップで画像を追加できるようにするなど、細かい点で、こだわりを入れました。
そうしていくうちに、こんなものが作りたい、という考えがきっと浮かんでくるはずです。

そうなったら最後、培った技術でオリジナルのアイデアを実現していきましょう！

### 感想
PHPは、初体験だったので、JavaScriptとはルールの異なる点が多く、慣れるまでには、時間を要しました。特に、末尾に必ず`;`をつけなくてはならない（VSCodeでも自動的に補完してくれない）のにストレスを感じましたねー。

また、これまでは**SPAの開発しかやってこなかったので、MPAのページ数の多さに、脱帽しました。** モデルファイル作って、ビューファイル作って、それだけで２枚ずつ増えますから。ページ数は少なめに抑えたつもりですが、コードを書く手間は、倍以上になる気分なので、本当に大変でした。

ここまで読んでいただき、ありがとうございました！
次回は、React+Laravel(API)+Dockerで、このアプリケーションを作成していきたいと思います。


https://github.com/Ricccck


https://github.com/Ricccck/PhotoStudio