Vue.jsとの共同開発にて、出退勤の時間を打刻するアプリを作成中です。
DBに時間を格納する方法にはCarbonを使えばいいと聞き、不明点が多くLaravelのみで動作検証したかったため試してみました。
DBに時間を格納し、扱うのが初めてでデータ型をどうしようと思ったのですが、dateTime型で成功しました。
以下を参考にしました。
Carbon:Introduction
PHPで日付時刻処理を書くならCarbonを使うべき
環境
- Laravel 5.7.19
- MySQL 8.0.12
Carbonを使った日時データの格納
LaravelにはCarbonが用意されてます。ない場合は以下の方法でComposer使ってインストール。
composer require nesbot/carbon
DB設計について
本筋から外れてしまいますがログインについてはメールアドレスを使ってではなく、ログインIDでのログインとします。
Usersテーブル
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('loginid')->unique()->comment('ログインID');
$table->string('password');
$table->tinyInteger('role')->unsigned()->default(10)->comment('権限0:system 5:admin 10:user');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
上記に伴い、app/Http/Controllers/Auth/LoginController.php
を変更します。
参照:app/Http/Controllers/Auth/LoginController.php
Timestampsテーブル
次に打刻用データベースです。
出勤の打刻と同時にカラムを作成、退勤時間はNullに設定しました。もちろん、Usersテーブルのidと外部キー制約でまとめます。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTimestampsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('timestamps', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->index();
$table->dateTime('punchIn');
$table->dateTime('punchOut')->nullable();
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('timestamps');
}
}
1対多リレーション
UserモデルとTimestampモデルを用意し、一対多の構造用意。
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'loginid', 'password'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token', 'role',
];
/**
* Timestamp関連付け
* 1対多
*/
public function timestamp()
{
return $this->hasMany(Timestamp::class);
}
}
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Timestamp extends Model
{
protected $fillable = ['user_id', 'punchIn', 'punchOut'];
/**
* ユーザー関連付け
* 1対多
*/
public function user()
{
$this->belongsTo(User::class);
}
}
ルート
ルーティングは以下のようにコーディングしました。
AuthServiceProviderを使った認証でページのアクセスをadminとuserで制限しています。
参照:app/Providers/AuthServiceProvider.php
Route::get('/', function () {
return view('home');
})->middleware('auth');
Route::get('/login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('/login', 'Auth\LoginController@login');
Route::post('/logout', 'Auth\LoginController@logout')->name('logout');
Route::group(['middleware' => ['auth', 'can:admin']], function() {
Route::get('admin/user/index', 'UserController@index')->name('admin/user/index');
Route::get('admin/user/show/{id}', 'UserController@show')->name('admin/user/show');
});
Route::group(['middleware' => 'auth'], function() {
Route::post('/punchin', 'TimestampsController@punchIn')->name('timestamp/punchin');
Route::post('/punchout', 'TimestampsController@punchOut')->name('timestamp/punchout');
});
コントローラー
以下のような挙動を考えて設計しました。
- 出勤ボタンを連続して押せない(同日中)
- 出勤打刻がない状態で退勤打刻を押せない
(2019/02/07 TimestampsController一部修正)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
use Carbon\Carbon;
use App\User;
use App\Timestamp;
class TimestampsController extends Controller
{
public function punchIn()
{
$user = Auth::user();
/**
* 打刻は1日一回までにしたい
* DB
*/
$oldTimestamp = Timestamp::where('user_id', $user->id)->latest()->first();
if ($oldTimestamp) {
$oldTimestampPunchIn = new Carbon($oldTimestamp->punchIn);
$oldTimestampDay = $oldTimestampPunchIn->startOfDay();
}
$newTimestampDay = Carbon::today();
/**
* 日付を比較する。同日付の出勤打刻で、かつ直前のTimestampの退勤打刻がされていない場合エラーを吐き出す。
*/
if (($oldTimestampDay == $newTimestampDay) && (empty($oldTimestamp->punchOut))){
return redirect()->back()->with('error', 'すでに出勤打刻がされています');
}
$timestamp = Timestamp::create([
'user_id' => $user->id,
'punchIn' => Carbon::now(),
]);
return redirect()->back()->with('my_status', '出勤打刻が完了しました');
}
public function punchOut()
{
$user = Auth::user();
$timestamp = Timestamp::where('user_id', $user->id)->latest()->first();
if( !empty($timestamp->punchOut)) {
return redirect()->back()->with('error', '既に退勤の打刻がされているか、出勤打刻されていません');
}
$timestamp->update([
'punchOut' => Carbon::now()
]);
return redirect()->back()->with('my_status', '退勤打刻が完了しました');
}
}
View周りはGitHubをご覧ください。