##記事の概要
作成中のポートフォリオについての説明です。
テーマ、背景・目的、設計・機能、工夫、反省などについて順に記述しています。
##Webアプリのテーマ
その日の出来事に対する感情を明確に記録することで日頃の調子などを把握できるようにする日記「EmoDiary」です。
##背景・目的
####背景
前職で働いていた頃に鬱になりました。なぜ鬱になったのか理由は割愛しますが、初めて鬱になった当時に自身が何にどう感じるか、毎日の調子はどうなのか記録をつけていれば鬱になる前に何かしら対応して防ぐことができていたかもしれないと考えることがあります。
そんな思いから作成したWebアプリがこのEmoDiaryです。
####目的
・パーフェクトPHP7章フレームワークの活用による理解
(パーフェクトPHP7章については事前に学習しています。フレームワークについてはこちらを参照してください)
*フレームワークにこだわっているのは病歴があるにも関わらず面接して頂いた企業様からフレームワークを使えるくらいのスキルは必要だとアドバイスを頂いたためです。(当時転職活動の際は今回とは異なるwebアプリを作成してレンタルサーバーを利用し、閲覧者を制限した上で公開していました。)
設計や機能など
必要な機能を考える際は文字で書いて考えていましたが、加えて簡単なイメージを作りながら考えていました。
大まかに考えられる機能やページとしては
- ログイン
- ログアウト
- 新規登録
- 登録内容の修正
- 日記の入力
- 日記の修正・削除
- 退会
- トップページ
- 日記の閲覧ページ など
が考えられます。
今回の日記については出来事に対する感情を重要視しているので、日記の入力ページをどのようにするかが課題でした。
(実装した機能については次のイメージとテーブル設計の次に載せています)
以下が考えていた頃のいくつかのイメージです。
・新規登録
名前、メールアドレス、パスワード入力によって登録することを想定していました。
実装する際はメールアドレスを重複して登録されることがないようにしました。
・トップページ
左の日記っぽい画像をクリックして日記を開くことを、右側には各感情ごとに期間別にその感情を得た出来事が何件あったのかを表示することを想定していました(実際は日記っぽい画像をボタン代わりにするのは邪魔だと思ったのでやめました。右側についても別ページにまとめることにしました)。
・日記閲覧ページ
その日の出来事とそれに対する感情、その日の写真を表示するページです。カレンダーなどから日付を指定して見たい日の日記を見れるようにすることを考えていました。
・日記編集ページ
この時点では感情の種類は喜怒哀楽と特に何もない普通の5種類でラジオボタンでチェックするようにしていましたが、実際にはプルダウンで40種類ほど選べるようにしました。
最終的には違う形に落ち着きましたが、頭の中にイメージがあるだけのときと可視化された状態のときの違いは少し客観的に見ることができたということです。
不要なところや変更した方が良いと思えるところに気づくことができました。
####DBテーブル設計
次にテーブルの設計です。
以下は最終的に落ち着いた形です。
最初は必要と思う項目を書いていました。
例えばdiariesにuser_idの項目があるにも関わらずcontentsにもuser_idの項目を書いていたというような状況です(下の画像)。
正規化することで重複する不要と思われる箇所を減らし1枚目の画像のように落ち着きました。
####機能
先ほどイメージを作りながら機能を考えていたと述べましたが、以下は実装した機能です。
・サインイン
メールアドレス・パスワードが一致した場合はサインインに成功し、誤りがあると注意が表示されます。
・新規登録
パスワードについては確認パスワードを入力するようにしました。サインイン同様に条件を満たさない場合は注意を表示します。
・ユーザ情報の修正
新規登録時の項目に加えて性別の選択を追加しました。他に趣味欄など追加してもよかったかもしれません。
・退会(アカウント削除)
パスワードが一致すれば表面上は削除されます。データベースではフラグを立てることで削除扱いしているためデータベースには情報が残ります。
・日記の編集・登録
日記は出来事を5つまで書くことができるようにしました。登録するにはその日の調子を必ず選択することが必要です。また出来事に対して状況・感情をそれぞれ必ず選択する必要があります。出来事に対して感情を意識させます。一方で出来事が何も書かれていない場合、つまり空白のままの場合は状況や感情を選択していても登録されません。飛ばして書いた場合は詰めるようになっています。
・日記をまっさらにする
何かしらすでに日記がある場合、内容を削除するためのチェックボタンが出現するようになっています。チェックを入れて保存すると削除されます。データベースではフラグを立てることで削除扱いしているためデータベースには情報が残ります。
・感情数の統計
週間、月間、半年間の期間を選択すると前期・後期形式で各感情の件数が表示されます。このページについてはグラフを追加して直感的にしたいと考えています。
以上が実装した機能です。
####ディレクトリ・ファイル構成
先ほど載せていた機能を満たすたように作成した結果、最終的に以下のような構成になりました。
Emotion-diary/
controllers/
AccountController.php
DiaryController.php
PersonalController.php
ServiceController.php
core/
Application.php
ClassLoader.php
Controller.php
DbManager.php
DbRepository.php
HttpNotFoundException.php
Request.php
Response.php
Router.php
Session.php
UnauthenticatedActionException.php
View.php
models/
DiaryRepository.php
UserRepository.php
peripheral/
ByPeriod.php
Check.php
Files.php
images/
views/
account/
index.php
signing.php
signup.php
emotions.php
diary/
index.php
edit.php
personal/
index.php
edit.php
intent.php
service/
index.php
emotions.php
errors.php
layout.php
web/
css/
normalize.css
style.css
images/
js/
menu.js
.htaccess
index.php
automatic_execution.php
EmotionDiaryApplication.php
####ルーティング
URLからpreg_match()や正規表現を用いて要求されたページを表示するために使用するコントローラとそのアクションなどを決定するためのルーティングです。
array(
'/'
=>array('controller' =>'account', 'action' => 'index'),
'/account/signin'
=>array('controller'=>'account', 'action' => 'signin'),
'/account/authenticate'
=>array('controller'=>'account', 'action'=>'authenticate'),
'/account/signup'
=>array('controller'=>'account', 'action'=>'signup'),
'/account/signup/:action'
=>array('controller'=>'account'),
'/account/signout'
=>array('controller'=>'account', 'action'=>'signout'),
'/account/emotions'
=>array('controller'=>'account', 'action'=>'emotions'),
'/personal'
=>array('controller'=>'personal', 'action'=>'index'),
'/personal/:action'
=>array('controller'=>'personal'),
'/diary/:action/:date'
=>array('controller'=>'diary'),
'/diary/:action'
=>array('controller'=>'diary'),
'/service/:action'
=>array('controller'=>'service'),
);
####レイアウトについて
パーフェクトPHP8章に習い共通レイアウトと各ページのレイアウトを分けて作成しました。
また、フォームでの入力ミスに対する注意文についても共通箇所として分けています。
・共通部分のhtml
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title><?php if(isset($title)){echo $this->escape($title);} ?>-Emo-Diary</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href=" <?php echo $this->escape($stage_url);?>css/normalize.css">
<link rel="stylesheet" href=" <?php echo $this->escape($stage_url);?>css/style.css">
<script type="text/javascript" src="<?php echo $this->escape($stage_url); ?>js/menu.js"></script>
</head>
<body>
<div id="page">
<header id="header">
<div class="title"><h1>EmoDiary</h1>
<a href="<?php echo $base_url; ?>/"><img class="titleImage" src="<?php echo $this->escape($stage_url); ?>images/note2.jpeg"></a>
</div>
<nav class="globalNavi">
<ul>
<li><?php if(isset($user)):?>
<a href="<?php echo $this->escape($base_url); ?>/personal">
<?php echo $this->escape($user['name']).'さん'; ?></a></li>
<?php else: ?>
<?php echo 'ゲストさん'; ?>
<?php endif; ?>
<?php if(!$session->isAuthenticated()): ?>
<li><a href="<?php echo $this->escape($base_url); ?>/account/signin">サインイン</a></li>
<?php else: ?>
<li><a href="<?php echo $this->escape($base_url); ?>/account/signout">サインアウト</a></li>
<?php endif; ?>
</ul>
</nav>
</header>
<div id="hamburgerBtn">
<img src="<?php echo $this->escape($stage_url); ?>images/btn1.jpg">
</div>
<div id="sideMenu" class="">
<nav>
<ul>
<li>ようこそ<?php if(isset($user)){echo $this->escape($user['name']).'さん';} ?></li>
<li><a href="<?php echo $this->escape($base_url); ?>/personal">マイページ</a></li>
<li><a href="<?php echo $this->escape($base_url); ?>/service/emotions">感情の種類</a></li>
<li><a href="<?php echo $this->escape($base_url); ?>/service/index">使い方</a></li>
<?php if(!$session->isAuthenticated()): ?>
<li><a href="<?php echo $this->escape($base_url); ?>/account/signin">サインイン</a></li>
<?php else: ?>
<li><a href="<?php echo $this->escape($base_url); ?>/account/signout">サインアウト</a></li>
<?php endif; ?>
</ul>
</nav>
</div>
<div id="surface" class=""></div>
<div id="main">
<?php echo $content; ?>
</div>
<footer id ="footer">
<p id="copyright"><small>Copyright© 2019 @EmoDiary All Rights Reserved.</small></p>
</footer>
</div>
</body>
・注意文の表示
・各フォームでの注意文の表示(上からサインイン、新規登録、日記編集フォーム)
・注意文の表示のhtml
<div class="errors">
<ul>
<?php for($i=0;$i<count($errors);$i++): ?>
<li><?php echo $this->escape($errors[$i]); ?></li>
<?php endfor; ?>
</ul>
</div>
各ページで表示される内容についてはviews/accountディレクトリやviews/diaryディレクトリにあるファイルに書かれています。
##工夫
基本的にはパーフェクトPHP7章にあるフレームワークを活用したものですが、必要に応じて機能を追加したりリファクタリングしたりしました。
・cssファイルやjsファイルを読み込むために『../』を対応させて取得。
(パーフェクトPHP7章8章ではcssファイルやjsファイルの使用はありませんでした。)
public function getUri() //元からあるメソッド
{
return $_SERVER['REQUEST_URI'];
}
public function getPathStage() //追加した『../』を取得するメソッド
{
$request_uri = $this->getUri();
$script_name = $_SERVER['SCRIPT_NAME'];
$path =str_replace($script_name, '', $request_uri);
$number_stage = mb_substr_count($path,'/');
$stage='';
for($i=0;$i<$number_stage;$i++){
$stage.='../';
}
return $stage;
}
・Checkクラスを作成して〇〇Controllerクラスのメソッド内で行われていた入力チェックをまとめる(リファクタリング?)。
以下はパスワードのチェックについて一部抜粋したものです。
private $unavailable_character='/[^a-zA-Z0-9\-_@]/';
public function checkBlank($string){
if(preg_match($this->blank, $string)===1 || strlen($string)===0){
return true;
}
return false;
}
public function checkAvailableCharacter($string,$character){
if(preg_match($character,$string)===1){
return true;
}
return false;
}
public function scopeString($string,$min_length,$max_length){
if(mb_strlen($string)>$min_length && mb_strlen($string)<$max_length){
return true;
}
return false;
}
public function checkPassword($password){
// 空白チェック
if($this->checkBlank($password)){
return 'パスワードを入力してください';
}
// 使用している文字
if($this->checkAvailableCharacter($password,$this->unavailable_character)){
return 'パスワードに使用できない文字が含まれています。半角英(大小)数字および記号-_@のみ使用できます。';
}
// 文字数
if(!$this->scopeString($password,7,17)){
return 'パスワードに使用できる文字数は8文字以上16文字以内です。';
}
}
・ファイルのアップロードの追加
class Files{
private $picture = '';
private $format = '/^image\/(jpg|jpeg|png|bmp)$/';
// $_FILESであることが前提
public function getFile($name)
{
if(!empty($_FILES[$name]['tmp_name']) && is_uploaded_file($_FILES[$name]['tmp_name'])){
return $_FILES[$name];
}
return false;
}
public function checkIsArray($name)
{
if(is_array($_FILES[$name]['tmp_name'])){
return true;
}
return false;
}
public function checkEmpty($name)
{
if($_FILES[$name]['error']===4){
return true;
}
return false;
}
public function checkErrorCode($name)
{
if($_FILES[$name]['error']===0 || $_FILES[$name]['error']===4){
//0はエラーなし、4はファイルがアップロードされていない
return true;
}
return false;
}
public function checkImangeType($name)
{
if(preg_match($this->format,$_FILES[$name]['type'])){
return true;
}
return false;
}
public function checkSize($name)
{
//3MB以下ならtrue
if($_FILES[$name]['size']<=1048576*5){
return true;
}
return false;
}
public function getCountFiles($name)
{
$number = count($_FILES[$name]['name']);
return $number;
}
//拡張子を取得。
public function getExtension($name)
{
$position = strpos($_FILES[$name]['type'],'/')+1;
return $extension = substr($_FILES[$name]['type'], $position);
}
public function getNewNameFile()
{
return $this->picture;
}
public function saveFile($name,$directory_path,$user_id,$diary_date)
{
$file = $user_id.'_'.date('Ymd',strtotime($diary_date)).'.'.$this->getExtension($name);
$this->picture = $file;
return move_uploaded_file($_FILES[$name]['tmp_name'],$directory_path.'/'.$file);
}
public function checkAll($name)
{
if($this->checkIsArray($name)){
return '配列のため予期しない入力が行われた可能性があります。';
}
if(!$this->checkErrorCode($name)){
return 'エラーコードに該当しました。';
}
if(!$this->checkImangeType($name)){
return '使用できない形式です。';
}
if(!$this->checkSize($name)){
return 'サイズが大きいです。5MBまでの画像にしてください。';
}
return true;
}
}
(ファイルアップロードのエラーコードについてはphp.iniを書き換えればファイルサイズのチェックなど利用できますが、php.iniの変更を忘れて今後他に作成するものなどへの影響を出しそうなので今回は控えました。本番環境ではphp.iniを書き換えると思います。)
・スマートフォン幅でのメニューの表示
申し訳程度ですがjavascriptを用いてメニューを表示・非表示する動きをつけました。
作成する際は『jQueryライブラリを使わずに、JavaScriptだけでドロワーメニューを実装してみた。』を参考にしました。
「idから値を取得してクラス与えたり取り除く。cssには事前に追加するクラスを書いておく。」ということを確認できたのでそれができる方法を調べて作成。恥ずかしながら最終的にほぼ同じ処理になりました。
・日記編集で感情の選択時に感情の説明を吹き出しで表示(追加:9/16作成)
プルダウンで感情を選択した際に感情の説明を表示するようにしました。
別のページに感情の説明一覧ページを作成していますが、意味をちょっと確認したいと思った時に逐一新たにページを開くのは面倒だと思い追加しました。
表示された吹き出しについてはどこでも良いのでクリックすると表示が消えるようになっています。
##反省
今回のWebアプリ作成についての作成期間、技術面、知識面、その他についての反省です。
技術面
.必要なクラスやメソッドについて事前に考えることができない。
そのため今回はやや複雑なクラスについてはスプレッドシートに必要なメソッドなどのメモを残し、それを参考に作成しました。(フレームワークをまるっと綺麗にコピペして使うと考える練習にならないため)
・namespaceを使用できていない。
・null,’’,array(),falseなどの使い分けができない。
・変数名やメソッド名など適切な命名ができてない。
・メソッドを20~30行ほどに収めることができない(controllersディレクトリのクラスのメソッドでは100行超えるものも)。
・staticをもっと活用できたのでは?(CheckクラスやFileクラス、ByPeriodクラス)。
・SQLについては簡単なものしか書けなかったためphp側で複数のSQLの実行をするようにしないといけなかった。
・データベースの構成で削除に関してはis_deleteといったフラグで表面上削除したことにするようにした(アンチパターンだったらしい)。
・JavaScript,html,cssをフワッと書いてる。
・事前に要件や必要なクラス、メソッドを細かく定義できなかった。
知識面
・Webの仕組みに関する知識が足りない
・脆弱性についてはなんとなく対処する方法を知ったくらいで脆弱性そのものや対策についての知識があまり定着してない。
その他
・公開用の環境やアップロードされたファイルを保存するクラウドサービスの選定と使用をすることができなかった(とりあえずアップロードの機能を実装して動作の確認をするためにルートディレクトリ直下にimagesディレクトリを置くなど)。
・検索力がまだ足りない(もっと内部関数をうまく見つけて利用できていたらなど。例えば閏年のチェック)。
・エラー修正の記録を残す際、表示されたエラーの文章を残しておけばよかった(後に同様のエラーが発生したときの対処をしやすくできると思うので)。
以下はエラー修正や機能の追加などの記録です。
##今後
今後取り組みたいことについてです。
####今回のアプリについて
php
・namespaceの利用
・staticの利用
・リファクタリング
・nullなどの適切な利用
・状況や感情、期間などを指定して出来事を抽出して一覧を表示するページの作成。
javascript
・グラフの追加
・日記編集で感情の選択時に感情の説明を吹き出しで表示(9/16作成)
トップページで今までの画像を表示(アルバム)
html,css
それぞれの役割や扱いかたをより適切に。またSassを利用してみる。
####基本的な知識など
・webの仕組みやセキュリティ対策、SQLや読みやすいコードなどの知識の習得。
・クラウドサービスの利用
また、今回とは別にLaravelやVueの学習(環境構築含め)。
##あとがき
最後までお付き合いありがとうございました。
ここに載せた内容が今の自身にできることです。改めて文字にしてみるとできないことや改善したほうが良いこと、今後取り組んだほうが良いことが整理されたので書いてみてよかったと感じています。
プログラミングを始めた方やポートフォリオの作成をしている方の参考になればと思い公開させていただきました。参考になれば幸いです。
##アプリ作成時の参考文献
・パーフェクトPHP (PERFECT SERIES 3)2010/11/12 小川 雄大 (著)柄沢 聡太郎(著)橋口 誠(著)
・基礎からのMySQL 第3版 (基礎からシリーズ) 2017/9/22 西沢 夢路(著)
・PHP逆引きレシピ 第2版 (PROGRAMMER’S RECiPE)2013/10/22 鈴木 憲治 (著), 山田 直明 (著), 山本 義之 (著), 浅野 仁 (著), 櫻井 雄大 (著), 安藤 建一 (著)
・配色アイデア手帖 日本の美しい色と言葉 心に響く和のデザインがつくれる本[完全保存版] 2018/12/21 桜井 輝子 (著)
・「感情」の解剖図鑑: 仕事もプライベートも充実させる、心の操り方 2017/3/10 苫米地 英人 (著)
*ポートフォリオの作成をされている方は以下のnoteを参考されてみると良いと思います。
note
・未経験エンジニアが作るべきポートフォリオとは?
(株式会社メイプルシステムズ代表取締役 望月祐介 さん 著)
・[見かけたQiita記事がポートフォリオとしてもプログラミングの教科書としても良かったので紹介します]
(https://note.mu/tan3_sugarless/n/n1e19f77e807e)
(炭山水 さん 著)