20210315のlaravelに関する記事は16件です。

Laravel+Guzzleを使用してAPIトークンを取得する方法

こんにちは、くりぱんです。

この記事で実現できること

  • Guzzleのインストール
  • Laravel+GuzzleでAPIトークンの取得

説明

今回はPHPのHTTPクライアントであるGuzzleを使用して、とあるAPIのトークンを取得していきたいと思います。

開発環境

  • Laravel: 6.2
  • Guzzule: 7.2

実装の流れ

  1. Laravelプロジェクトの作成
  2. Guzzleのインストール
  3. 日本時間にする
  4. データベース設定
  5. Route
  6. Controller
  7. View

実装

1. Laravelのプロジェクトの作成

$ composer create-project --prefer-dist laravel/laravel testAPI "6.*"

testAPIというLaravel6のプロジェクトが作成されます。

2. Guzzleのインストール

$ composer require guzzlehttp/guzzle

composer.jsonで確認していきます。

composer.json
{

    ーー省略ーー

    "license": "MIT",
    "require": {
        "php": "^7.2.5|^8.0",
        "fideloper/proxy": "^4.4",
        "guzzlehttp/guzzle": "^7.2", // ここチェック
        "laravel/framework": "^6.20",
        "laravel/tinker": "^2.5"
    },

    ーー省略ーー

}

私の場合composer.jsonには"guzzlehttp/guzzle": "^7.2"となっていますが、バージョンが多少異なっていても特に問題はありません。

3. 日本時間にする

APIトークンの有効期限も画面に出したいので、一応日本時間にしておきます。
config/app.phpを編集します。

app.php
return [

    ーー省略ーー

    'timezone' => 'Asia/Tokyo',

    ーー省略ーー

]

4. データベース設定

今回はMySQLを使用していきます。
mysql -u root -pなどでMySQLに入り、test_apiというデータベースを作成してください。

mysql> CREATE DATABASE test_api;

.envに作成したデータベースの設定をしていきます。

.env
// データベース関連のところを抜粋
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=test_api // 今回作成したデータベース名
DB_USERNAME=ユーザー名
DB_PASSWORD=パスワード

5. Route

routes/web.phpでルートの設定を行っていきます。

web.php
<?php

Route::get('/', 'TestApiController@index'); // ここを追記

http://127.0.0.1:8000/にアクセスするとこれから作成するTestApiControllerindexメソッドが走るようになりました。

6. Controller

$ php artisan make:controller TestApiController

このコマンドで、以下のようなapp/Http/Controllers/testApiController.phpが作成されます。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TestApiController extends Controller
{
    //
}

今回は例として下記の情報で接続したと過程します。

  • ベースURL: openapi.test.api.jp
  • HTTP リクエスト: POST openapi.test.api.jp/v2/test
  • API_KEY: hjfdksaJhjfkdw134fjdklsaKJ93JKL
  • user_id: user_id
  • password: password

先程コマンドで作成したapp/Http/Controllers/testApiController.phpを編集していきます。

TestApiController.php
<?php

namespace App\Http\Controllers;

use Facade\Ignition\QueryRecorder\Query;
use Illuminate\Http\Request;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Header;

class TestApiController extends Controller
{
    public function index()
    {
        // ベースURL
        $base_url = 'openapi.test.api.jp';

        // インスタンス作成
        $client = new Client([
            'base_url' => $base_url,
        ]);

        // API_KEY
        $api_key = 'hjfdksaJhjfkdw134fjdklsaKJ93JKL';

        // オプション
        $options = [
            // デバック(デバックしたい時は記述)
            'debug' => true,

            // 
            // パラメーター(Header)
            'headers' => [
                'api-key' => $api_key,
            ],

            // パラメーター(Query)
            'query' => [
                'user_id' => 'user_id',
            ],

            // パラメーター(Body)
            'json' => [
                'password' => 'password',
            ],
        ];

        // パス
        $path = '/v2/test';

        // リクエストするURL(openapi.test.api.jp/v2/test)
        $send_url = $base_url . $path;

        $response = $client->request('POST', $send_url, $options);

        // JSONデータとして取得
        $json = $response->getBody();

        // JSONデータを連想配列にする
        $api_token = json_decode($json, true);

        return view('welcome', compact('api_token'));
    }
}

passwordはTypeがBodyだったのですが、json指定だとうまい感じで変換してくれます。

7. View

JSONを連想配列で取得しているので、有効期限とAPIトークンをそれぞれ取得していきます。
resources/views/welcome.blade.phpを下記の通り編集してください。

welcome.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Laravel</title>

    </head>
    <body>
        <p>有効期限:{{ date('Y年m月d日 H時i分s秒', $api_token['expires_at']) }}</p>
        <p>APIトークン:{{ $api_token['token'] }}</p>
    </body>
</html>

有効期限はミリ秒で取得されるので、date関数でフォーマットを整えています。
これで、php artisan serveしてからブラウザで確かめると、無事有効期限とAPIトークンを取得できるかと思います。

最後に

APIはまだまだわからないことばかりなので、ぜひご指摘等ありましたらコメント欄にお願いいたします。

少しでも役に立った!という時は、LGTMをポチッと、、、笑
1つでもLGTMが付くとその日がハッピーになるんです!
役に立たなかった時は、怒らないでコメント頂けると幸いです笑

Twitterもやってます!
プログラミングや金融知識についてやエンジニアの現実についてつぶやいています!
よかったら見てみてくださいね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VPSにLaravelが動く環境を作る

VPSにLaravel環境を作る デプロイはgithubから行う

より本運用に近い形をイメージして github にリポジトリを作成、productionブランチからサーバーにデプロイするイメージで作成する。

プロジェクトを用意

GitHubにプロジェクトを作る

create_git_repository.png

開発用Macにclone

git clone git@github.com:sugie/my_laravel.git org

orgディレクトリにリポジトリがクローンされる。

Laravelプロジェクトを新規作成

> composer create-project laravel/laravel=6.* my_project

Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
67 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

composer installを実行

> composer install
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
67 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

artisan serveを実行

> php artisan serve
Laravel development server started: http://127.0.0.1:8000
[Mon Mar 15 17:19:41 2021] PHP 7.4.12 Development Server (http://127.0.0.1:8000) started
[Mon Mar 15 17:19:46 2021] 127.0.0.1:57585 Accepted
[Mon Mar 15 17:19:46 2021] 127.0.0.1:57586 Accepted
[Mon Mar 15 17:19:46 2021] 127.0.0.1:57585 Closing
[Mon Mar 15 17:19:47 2021] 127.0.0.1:57586 [200]: GET /favicon.ico
[Mon Mar 15 17:19:47 2021] 127.0.0.1:57586 Closing

ブラウザで
http://127.0.0.1:8000
を開くいて、LaravelのWelcomeページが表示されることを確認。

first.png

githubにpush

> cp -pR ../org/.git .

ステータスを確認

> git status

On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    .editorconfig
    .env.example
    .gitattributes
    .styleci.yml
    README.md
    app/
    artisan
    bootstrap/
    composer.json
    composer.lock
    config/
    database/
    package.json
    phpunit.xml
    public/
    resources/
    routes/
    server.php
    storage/
    tests/
    webpack.mix.js

nothing added to commit but untracked files present (use "git add" to track)

AddしてCommitしてPush

>git add .

>git commit -m 'First commit'

[master (root-commit) 5b38273] First commit
 84 files changed, 9914 insertions(+)
 create mode 100644 .editorconfig
 create mode 100644 .env.example
 create mode 100644 .gitattributes
 create mode 100644 .styleci.yml
 create mode 100644 README.md
 create mode 100644 app/Console/Kernel.php
 create mode 100644 app/Exceptions/Handler.php
 create mode 100644 app/Http/Controllers/Auth/ConfirmPasswordController.php
 create mode 100644 app/Http/Controllers/Auth/ForgotPasswordController.php
 create mode 100644 app/Http/Controllers/Auth/LoginController.php
 create mode 100644 app/Http/Controllers/Auth/RegisterController.php
 create mode 100644 app/Http/Controllers/Auth/ResetPasswordController.php
 create mode 100644 app/Http/Controllers/Auth/VerificationController.php
 create mode 100644 app/Http/Controllers/Controller.php
 create mode 100644 app/Http/Kernel.php
 create mode 100644 app/Http/Middleware/Authenticate.php
 create mode 100644 app/Http/Middleware/CheckForMaintenanceMode.php
 create mode 100644 app/Http/Middleware/EncryptCookies.php
 create mode 100644 app/Http/Middleware/RedirectIfAuthenticated.php
 create mode 100644 app/Http/Middleware/TrimStrings.php
 create mode 100644 app/Http/Middleware/TrustProxies.php
 create mode 100644 app/Http/Middleware/VerifyCsrfToken.php
 create mode 100644 app/Providers/AppServiceProvider.php
 create mode 100644 app/Providers/AuthServiceProvider.php
 create mode 100644 app/Providers/BroadcastServiceProvider.php
 create mode 100644 app/Providers/EventServiceProvider.php
 create mode 100644 app/Providers/RouteServiceProvider.php
 create mode 100644 app/User.php
 create mode 100755 artisan
 create mode 100644 bootstrap/app.php
 create mode 100644 bootstrap/cache/.gitignore
 create mode 100644 composer.json
 create mode 100644 composer.lock
 create mode 100644 config/app.php
 create mode 100644 config/auth.php
 create mode 100644 config/broadcasting.php
 create mode 100644 config/cache.php
 create mode 100644 config/database.php
 create mode 100644 config/filesystems.php
 create mode 100644 config/hashing.php
 create mode 100644 config/logging.php
 create mode 100644 config/mail.php
 create mode 100644 config/queue.php
 create mode 100644 config/services.php
 create mode 100644 config/session.php
 create mode 100644 config/view.php
 create mode 100644 database/factories/UserFactory.php
 create mode 100644 database/migrations/2014_10_12_000000_create_users_table.php
 create mode 100644 database/migrations/2014_10_12_100000_create_password_resets_table.php
 create mode 100644 database/migrations/2019_08_19_000000_create_failed_jobs_table.php
 create mode 100644 database/seeds/DatabaseSeeder.php
 create mode 100644 package.json
 create mode 100644 phpunit.xml
 create mode 100644 public/.htaccess
 create mode 100644 public/favicon.ico
 create mode 100644 public/index.php
 create mode 100644 public/robots.txt
 create mode 100644 public/web.config
 create mode 100644 resources/js/app.js
 create mode 100644 resources/js/bootstrap.js
 create mode 100644 resources/lang/en/auth.php
 create mode 100644 resources/lang/en/pagination.php
 create mode 100644 resources/lang/en/passwords.php
 create mode 100644 resources/lang/en/validation.php
 create mode 100644 resources/sass/app.scss
 create mode 100644 resources/views/welcome.blade.php
 create mode 100644 routes/api.php
 create mode 100644 routes/channels.php
 create mode 100644 routes/console.php
 create mode 100644 routes/web.php
 create mode 100644 server.php
 create mode 100644 storage/app/.gitignore
 create mode 100644 storage/app/public/.gitignore
 create mode 100644 storage/framework/cache/.gitignore
 create mode 100644 storage/framework/cache/data/.gitignore
 create mode 100644 storage/framework/sessions/.gitignore
 create mode 100644 storage/framework/testing/.gitignore
 create mode 100644 storage/framework/views/.gitignore
 create mode 100644 storage/logs/.gitignore
 create mode 100644 tests/CreatesApplication.php
 create mode 100644 tests/Feature/ExampleTest.php
 create mode 100644 tests/TestCase.php
 create mode 100644 tests/Unit/ExampleTest.php
 create mode 100644 webpack.mix.js
git push origin master
Authenticated to github.com ([52.69.186.44]:22).
Counting objects: 110, done.
Delta compression using up to 12 threads.
Compressing objects: 100% (93/93), done.
Writing objects: 100% (110/110), 62.85 KiB | 0 bytes/s, done.
Total 110 (delta 11), reused 0 (delta 0)
remote: Resolving deltas: 100% (11/11), done.
Transferred: sent 67160, received 3404 bytes, in 2.1 seconds
Bytes per second: sent 32330.1, received 1638.7
To git@github.com:sugie/my_laravel.git
 * [new branch]      master -> master

プロダクションブランチを作成してpush

> git checkout production
error: pathspec 'production' did not match any file(s) known to git.

> git branch
* master

> git checkout production
error: pathspec 'production' did not match any file(s) known to git.

> git branch production

> git checkout production
Switched to branch 'production'

> git merge master -m 'merge from master'
Already up-to-date.

> git push origin production
Authenticated to github.com ([52.192.72.89]:22).
Total 0 (delta 0), reused 0 (delta 0)
remote:
remote: Create a pull request for 'production' on GitHub by visiting:
remote:      https://github.com/sugie/my_laravel/pull/new/production
Transferred: sent 2740, received 2764 bytes, in 1.8 seconds
remote:
Bytes per second: sent 1531.3, received 1544.7
To git@github.com:sugie/my_laravel.git
 * [new branch]      production -> production

> 

git flowの手順にしたがって
productionブランチにはデプロイする内容を入れる。

VPSにサーバーを作る

今回はvultr.comを使う。さくらのVPSでもConoHaのVPSでも基本は同じだと思う。

vultr.png

vultrでfirewallを設定する方法は
https://qiita.com/Shiro-neko-kamen/items/0b4a8504eb87a459c7d8
こちらを参照。

サーバーインスタンスができたら詳細画面に遷移

server_detail.png

ターミナルでログインする

Macでターミナルアプリを起動。

detail2.png

コピーアイコンをクリックしてIPアドレスをコピー。

ssh root@コピーしたIPアドレスをペースト

パスワードを聞かれるので、サーバー詳細のrootパスワードをコピーしてペースト。

ss.png

52 updates can be installed immediately.
とか出てくるのでアップデートを最初に行う。

apt-get update
apt-get upgrade

作業用アカウントを用意する

visudo のエディタをvimに変更。nanoエディタを使いたい人は以下のコマンドを実行しない。

update-alternatives --set editor /usr/bin/vim.basic

ユーザー名を決める。taroでも、hanakoでも、itadoriでもtanjiroでもなんでも良い。今回はblueを使う。

adduser blue

パスワードを設定。uShieh#oo8agを設定。

blueをsudoできるようにする。

visudo

blue ALL=(ALL) NOPASSWD:ALL
を27行目くらいに挿入して保存。

ss 1.png

blueの公開鍵を設定

作業ユーザーは鍵認証でsshログインするようにする。鍵認証の詳細は割愛。説明希望の人はコメント欄に説明希望と書いてください。追記します。

su blue
cd
mkdir .ssh
vi .ssh/authorized_keys

公開鍵をペーストして保存

パーミッションを設定
chmod 0700 .ssh
chmod 0400 .ssh/authorized_keys

この設定をすると作業ユーザーでsshログインできるようになる。

Macのターミナルから以下のコマンドを実行
bash
ssh blue@139.180.207.51

sudo できることを確認
bash
sudo pwd

成功したらrootでのリモートログインを禁止して、パスワードログインを禁止してセキュリティ向上をしたいところ。

使いそうなモジュールをインストール

サーバーに作業ユーザーでログインしてapt-get installを実行。

ssh blue@139.180.207.51

sudo apt-get install curl wget net-tools vim vim-gtk3 vim-tiny neovim vim-athena vim-gtk vim-nox python vim-gui-common vim-runtime mycli tree nkf 

ズラーっといろいろ表示される。

Apacheをインストール

ppa:ondrej/apache2 リポジトリを追加

sudo add-apt-repository ppa:ondrej/apache2

sudo apt-get update

sudo apt-get update

Apacheをインストール

sudo apt-get install apache2 w3m

インストールされていることを確認

> systemctl  | grep apache
  apache2.service                                                                     loaded    active running   The Apache HTTP Server                                                       

ブラウザで確認

http://サーバーのIPアドレス

を開く。こんなのが表示される。
ss 2.png

せっかくだからドメインの設定をする

お名前.comのDOMナビの例。DNSの説明は割愛します。
作ったサーバーのIPアドレスを指定して反映。
ss 3.png

登録したURLをブラウザで開いてみる。

ss 4.png
すぐには表示されない。

ある程度時間が経ってDNSが浸透したら開けるようになる。

10分くらいで表示されるようになった。
ss 5.png

MySQLデータベースをインストール

https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-20-04-ja
このサイトを参考にしました。

sudo apt install mysql-server
sudo mysql_secure_installation

Press y|Y for Yes, any other key for No: NO

New password: ohcue3Olu

Remove anonymous users? (Press y|Y for Yes, any other key for No) : yes

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : Y

Remove test database and access to it? (Press y|Y for Yes, any other key for No) : Y

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : Y


MYSQLユーザーの作成

sudo mysql

CREATE USER 'myproject'@'%';
CREATE DATABASE IF NOT EXISTS myproject;
GRANT ALL PRIVILEGES ON myproject.* TO 'myproject'@'%';
CREATE USER 'myproject'@'localhost';
GRANT ALL PRIVILEGES ON myproject.* TO 'myproject'@'localhost';
use mysql
ALTER USER 'myproject'@'localhost' identified BY 'ooH8Tho8umo';
flush privileges;

別のターミナルからログインできることを確認

blue@mylaravel:~$ mysql -umyproject -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.23-0ubuntu0.20.10.1 (Ubuntu)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

mycliでもログインしてみる。コマンドラインからmysqlに繋ぐならmysqlコマンドよりmycliコマンドのほうが便利だ。

blue@mylaravel:~$ mycli -umyproject
Password: 
mysql 8.0.23-0ubuntu0.20.10.1
mycli 1.22.2
Chat: https://gitter.im/dbcli/mycli
Mail: https://groups.google.com/forum/#!forum/mycli-users
Home: http://mycli.net
Thanks to the contributor - Zane C. Bowers-Hadley
mysql myproject@localhost:(none)>
mysql myproject@localhost:(none)> quit
Goodbye!
blue@mylaravel:~$ 

PHPのインストール

sudo apt-get install php7.4-cli  php7.4-common php7.4 php7.4-mbstring php7.4-xml php7.4-dom php7.4-gd php7.4-mysqlnd php7.4-pdo php7.4-zip php7.4-redis php7.4-curl

バージョンを確認

php --version
>php -version
PHP 7.4.9 (cli) (built: Oct 26 2020 15:17:14) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.9, Copyright (c), by Zend Technologies

compoesrをインストール

sudo apt-get install composer

githubから持ってくる

cd ~
vi mygithub.secret
# GitHubに登録した秘密鍵を配置
# 保存したら以下のコマンドでパーミッションを設定

chmod 0400 ~/.ssh/mygithub.secret

vi ~/.ssh/config
Host github.com
    User #GitHubAccount#
    Port 22
    HostName github.com
    IdentityFile ~/.ssh/mygithub.secret
    TCPKeepAlive yes
    IdentitiesOnly yes

クローン
productionブランチを指定

git clone git@github.com:sugie/my_laravel.git --branch production

my_laravelディレクトリを移動

sudo mkdir /var/sites/
sudo mv /home/blue/my_laravel /var/sites/my_laravel
sudo chown www-data:www-data -R /var/sites/my_laravel

composer を実行

cd /var/sites/my_laravel
sudo -u www-data composer install

composer_installing.png

www-dataユーザーでgit pullできるように設定

sudo mkdir /var/www/.ssh -p
sudo cp -p /home/blue/.ssh/#MY_GITHUB_SECRET#.secret /var/www/.ssh/.

所有者をwww-dataにする

sudo vi /var/www/.ssh/config
Host github.com
    User #MY_GITHUB_ACCOUNT#
    Port 22
    HostName github.com
    IdentityFile ~/.ssh/#MY_GITHUB_SECRET#.secret
    TCPKeepAlive yes
    IdentitiesOnly yes
sudo chown www-data:www-data -R /var/www

pullしてみる

cd /var/sites/my_laravel
sudo -u www-data git pull

将来本番サーバーに反映させるときは
productionブランチを用意して、pullを行って反映させる。

環境ファイルの用意

cd /var/sites/my_laravel
sudo cp -p .env.example .env
sudo -u www-data vi .env

データベースの設定を.envに反映。さっき作ったMySQLユーザーとパスワードを記述。

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=myproject
DB_USERNAME=myproject
DB_PASSWORD=ooH8Tho8umo

Key generate

sudo -u www-data php artisan key:generate

.envに
APP_KEY=base64:3epehyg5v9XIHz2ZxTquvcpVdnbKGxlDmHPCaLTdU9w=
が設定される。

migrateを実行

sudo -u www-data php artisan migrate

mig.png

Apacheの設定

既存の設定ファイルを削除。

cd /etc/apache2/sites-enabled
sudo rm 000-default.conf

新しい仮想ホストの設定ファイルを作る

cd ../sites-available/

#設定ファイルはドメイン名+.confを勧めます

sudo vi myproject.ice2nd.mobi.conf

<VirtualHost *:80>
        ServerName myproject.ice2nd.mobi

        ServerAdmin admin@email.address
        DocumentRoot /var/sites/my_laravel/public
        <Directory /var/sites/my_laravel/public>
                AllowOverride All
                Options All
                Require all granted
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
cd /etc/apache2/sites-enabled
sudo ln -s ../sites-available/myproject.ice2nd.mobi.conf .


sudo apache2ctl configtest
Syntax OK

sudo apache2ctl start

apa.png

ブラウザで確認

br.png

https化する

https化するにはSSL証明書が必要。このサイトは正しいサイトですよ。と認証局に認定してもらう。証明書は購入することもできるが、無料でも入手できる。
無料で使えるSSL証明書はLets encryptと呼ばれている。
今回はlets encryptを使って証明書をインストールし、1ヶ月で自動更新するようにする。

cetbotをインストールするためにリポジトリを追加する。

sudo apt-get install certbot python3-certbot-apache

証明書のインストール

sudo certbot --apache

nc.png

ブラウザでhttpsになっていることを確認。

https.png

vultr.com でサーバーを作りなら

https://www.vultr.com/?ref=8408139-6G
このリンクから申し込むと100$分の利用料がもらえるので、よかったら使ってください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【未経験者】PHPとLaravelそれぞれで類似ポートフォリオ作ってみた

はじめに

こんにちは、おーもとと申します。エンジニアに転職をするため学習している初学者です。
私は車が好きで、「近年の若者の車離れ」という問題にフォーカスしたアプリを制作しようと思いました。
色々あって、生のPHPとLaravelの二通りの類似ポートフォリオを制作したので、記事にしてみました。

制作背景

若者が車を持たない理由には様々な理由があると思いますが、
「欲しいと思えるほど魅力を感じる車に出会っていないからなのでは?」
と思い、
・かわいいやかっこいいというスタイル
・大きさ
・国産か外車か
・アウトドアや街乗りという用途
これらの項目に当てはまる車を、結果として表示するアプリを制作することにしました。
(これらの特徴は全て私が定めているため、投票などにより特徴を決める機能をつけたいです)

11月 PHPでアプリ開発

10月からPHPの学習を始めていたので、そのアプリはPHPで制作しました。
カーセンサーAPIを使用して、車の情報を取得します。
解説動画:https://www.youtube.com/watch?v=ZXbgUtjxKM8
スクリーンショット 2020-12-12 12.34.26.png

機能

ユーザー登録関連
⚪︎ ログイン
⚪︎ ログアウト
⚪︎ 新規登録
⚪︎ ユーザー件数を表示

車の検索機能
⚪︎ 車の見た目→「かわいい」「かっこいい」「シンプル」「おしゃれ」「レトロ」
⚪︎ 車のサイズ→「ふつう」「すごくおおきい」「おおきい」「ちいさい」
⚪︎ 車の製造国→「国産車」「外車」
⚪︎ 車の用途 →「街乗り」「アウトドア」「スポーツ」

カーセンサーAPI連携
⚪︎ DBにある車情報と合致した車情報を取得
⚪︎ cronでキャッシュファイル自動生成

苦労した点

検索結果の画像表示高速化

検索の度にAPIからデータを取得していたので、電波の悪い場所では結果の表示に1分以上かかっていました。
そのため、毎日APIからデータを取得するバッチ処理をcronで自動化し、キャッシュ化することで、ユーザーにストレスのない速度で結果を表示させることができました。

EC2へデプロイ

公式ドキュメントを参考にしデプロイしました。
その際、インフラの知識が不足していたため、デプロイに一週間以上かかりました。

APIのサービス終了!!

転職活動を始めようとした際、一週間後にカーセンサーAPIサービスが終了すると知りました。
急いで提供元へ問い合わせたところ、
「完全に提供が終了すること」「24時間以上のキャッシュデータの保有も禁止」、ということを告げられました。
その後、他の車データAPIの提供元を調べましたが他にありませんでした。
画像だけでもどうにかならないかと思い、ト◯タや◯産などの画像利用規約を確認しましたが、
営利目的ではない&提供元のURLなどの情報を記載する
としても、利用は禁止でした。
そのためLaravelの勉強も兼ねて、画像問題を解決できるアプリの制作に取り掛かりました。

1月 Laravelでアプリ開発

12月末からLaravelの学習を始め、1月からアプリの制作に取り掛かりました。

前回のPHPで制作したポートフォリオとの違い

画像の取得にAPIを用いていましたが、ユーザーから愛車の画像を提供してもらう方針に変更し、機能の追加などを行いました。

完成

アプリのURL:https://pf-kurushira.com
(スマホサイズにも対応しています)
スクリーンショット 2021-03-14 16.03.00.png

使用技術

使用言語

⚪︎ HTML
⚪︎ CSS
⚪︎ SCSS
⚪︎ PHP 7.4.14
⚪︎ Laravel 6.20.11

インフラ

⚪︎ Github Actions 自動デプロイ
⚪︎ Docker 20.10.2 / docker-compose 1.27.4
⚪︎ nginx 1.18
⚪︎ mysql 5.7.31 / PHPMyAdmin
⚪︎ AWS ( EC2, ALB, ACM, S3, RDS, Route53, VPC, EIP, IAM)

インフラ構成図

スクリーンショット 2021-02-28 20 11 32

機能一覧

機能 概要
ユーザー管理機能 新規登録・ログイン・ログアウトができます
簡単ログイン機能 ログイン画面のゲストログインをクリックすることで、ゲストユーザーとしてログインできます
おすすめ車種検索機能 条件を選択すると、それにあった車種一覧を表示します
検索履歴機能 直近の検索履歴・結果を表示します
画像提供機能 ユーザーの所有している車の画像を提供できます
提供した画像の削除機能 提供した画像を削除できます
提供画像一覧表示機能 自身が提供した画像一覧を表示します。
ユーザー情報編集機能 ご登録いただいたユーザー名・メールアドレスを変更できます
Twitterシェア機能 車の検索結果をツイートすることができます
レスポンシブ機能 スマホサイズ(320~540px)にも対応しています

DB設計

スクリーンショット 2021-02-20 19 05 58

各テーブルについて

テーブル名 説明
users 登録ユーザー情報
cars 登録車情報
histories 直近の検索結果の情報
car_images 提供画像の情報

苦労した点

ユーザー情報編集ページのバリデーション

LaravelのAuth機能のバリデーションを使いまわそうとしましたが、ブラックボックスになっていて苦労しました。
→新しくバリデーションを作成。

S3からオブジェクト削除

画像の削除機能でDBからだけでなく、S3からもオブジェクトを削除する必要があり苦労しました。
→解決方法を記事にしました
laravel6でS3に画像アップロード&削除

今後の課題

機能

機能 概要
英訳機能 Google Cloud Translation APIを利用して翻訳
通報機能 ユーザーの投票で不適切な画像を削除

技術

⚪︎ テスト
⚪︎ Dockerを用いた本番環境の構築
⚪︎ ECSへデプロイ

参考にした学習教材など

PHP/Laravel

【Udemy】PHP+MySQL(MariaDB) Webサーバーサイドプログラミング入門
【書籍】詳細!PHP 7+MySQL 入門ノート
【書籍】PHPフレームワークLaravel入門 第2版
【書籍】PHPフレームワーク Laravel実践開発
Laravel6.0(PHP7.3)+MySQL+Laradockで簡易的なECサイトを作る

AWS

【Udemy】AWS:ゼロから実践するAmazon Web Services。手を動かしながらインフラの基礎を習得

Docker

【超入門】20分でLaravel開発環境を爆速構築するDockerハンズオン

さいごに

生のPHPでひとつPFを制作したのは、基礎力が身についたので良かったと思います。
今回ポートフォリオ完成を優先したため、ECSではなくEC2へデプロイしました。
まだ課題も多いですが、ブラッシュアップしていきたく思っています。
長くなりましたが、ここまで読んでくださりありがとうございました!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】Viewの文字列とパスを変数に置き換えてメンテ性と汎用性を上げる方法。route、__()、config()、ヘルパーの活用

ビューを作成するときに、メンテ性や汎用性を向上させるため、極力直接入力をなくす。見た目もなんとなくカッコよくなる。

文字列はlangファイルにまとめて記述し、関数で取得するようにする。

パスもメソッドとヘルパーを使って表示するようにする。

実例(Before & After)

構造化マークアップ(Website)を例とする。

before(index.blade.php)
<script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "Website",
    "name": "サイトの名前",
    "url": "サイトのURL",
    "description": "サイトの説明",
    "potentialAction": {
       "@type": "SearchAction",
       "target": "サイトのURL?q={search_term_string}",
       "query-input": "required name=search_term_string"
    }
}
</script>

↓ リファクタリング

after(index.blade.php)
<script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "Website",
    "name": "{{__('page.index.title')}}",
    "url": "{{route('index')}}",
    "description": "{{__('page.origin.description')",
    "potentialAction": {
       "@type": "SearchAction",
       "target": "{{route('index')}}?q={search_term_string}",
       "query-input": "required name=search_term_string"
    }
}
</script>

複数のサイト間で共有できる文字列はそのままとし、固有のものを変数に置き換えた。

パッと見ややこしいが、中身はかなり簡単。

目次

  1. 文字列の取得
    1. 設定言語を日本語に変更
    2. 文字列を記述
    3. 呼び出し
  2. URLの取得
    1. ルート名をつける
    2. ルートを呼び出す
    3. パラメータ付きのURLの場合
  3. 【応用編】confファイルとヘルパーの活用
    1. データ取得の流れ
    2. .envのデータをconfigに呼び出す
    3. ヘルパー関数を作成
    4. ビューで呼び出し


1. 文字列の取得

最初に文字列を変数に置き換える方法について。

resources配下にあるlangディレクトリの中に配列形式とオブジェクトで文字列を記載してく。

1-1. 設定言語を日本語に変更

デフォルトではenになっているので、jaに変更する。

config > app.php の中の localeを参照。

修正後
 'locale' => 'ja',

1-2. 文字列を記述

page.phpファイルを作成し、呼び出しの元となる単語や文章を記載する。(ファイル名は任意)

image.png

page.php
<?php

return [
    'index' => [
        'title' => 'サイトの名前',
        'description' => 'サイトの説明',
    ]
];

今回は、index.blade.phpの情報を作成。プロパティ名と値は必要に応じて変更・追加していく。

1-3. 呼び出し

langファイルに設定した文字列を呼び出す。__()というヘルパ関数を使う。

呼び出し方は、ファイル名(拡張子なし)の後に、ドットでプロパティ名を繋ぐ。

__('ファイル名.プロパティ名')

page.phpの中の、indexn中のtitleを呼び出しは以下のようになる。

サイトの名前を呼び出す
"name": "サイトの名前",

 置き換え

"name": "{{__('page.index.title')}}"

以上で文字列の呼び出しが完了。


2. URLの取得

次に、URLを変数に置き換える。これにはrouteメソッドを使う。

route('ルート名', ['変数'=>渡すデータ])

2-1. ルート名をつける

URLの呼び出しにはルート名を使うため、ルーティング(web.php)に名前を追加する。

ルーティングの後ろに->name('ルート名')をつける。

web.php
Route::get('/',[IndexController::class, 'index'])
    ->name('index');

これで、https://ドメイン にindexと名付けることができた。

2-2. ルートを呼び出す

"url": "サイトのURL",
"target": "サイトのURL?q={search_term_string}",

 置き換え

"url": "{{ route('index') }}",
"target": "{{ route('index') }}?q={search_term_string}",

これでURLを変数で呼び出すことができる。


2-3. パラメータ付きのURLの場合

記事詳細ページなど、idやページ番号といった変数が割り当てられている場合がある。その場合は、データを渡す必要がある。

web.php
Route::get('/article-{articleId}/', [ArticleController::class, 'show'])
        ->name('article.show');

https://ドメイン名/article-23 のように末尾に数字がつくパターン。ルート名はarticle.show。

ルーティングの設定で、数値の部分がarticleIdという変数に格納されて、コントローラのアクションに渡される。

ビューの中でルートで呼び出す時も、articleIdの指定が必須。

コントローラ(ArticleController.php)
public function show (string $articleId)
    {
        $article = Article::article($articleId);
        $articleId = $articleId;

        return view('pages.articles.show', compact(
            'article',
            'articleId',
        ));
    }

Articleモデルから、指定の番号の記事データを取得し、変数articleに代入。

また、ルーティングから受け取った変数articleIdを同じ名前でビューに渡す。

ビュー(show.blade.php)
"url": "{{ route('article.show'), ['articleId' => $articleId] }}",

これで、現在のページのURLを呼び出すことができる。


3. confファイルとヘルパーの活用

応用編として、.envに記載されているサイト設計用のデータを使って変数を作り出すこともできる。

例として、.envにサイトのドメイン情報がある場合に、そこからヘルパーでホストを作成する方法について。

3-1. データ取得の流れ

  1. .envファイル: プロジェクトの基本情報
  2. config/app.phpファイル :環境設定用ファイル
  3. Helper/ViewHelper.php :自分でヘルパー関数を作成
  4. index.blade.php: ビューで呼び出す

3-2. .envのデータをconfigに呼び出す

.envの中で使いたいデータを、configディレクトリ配下のファイルに記述する。

.envは重要な情報が載っているため公開しない。config配下のファイルにデータを記述すると、configヘルパーを使って簡単に呼び出すことができる。

今回は、下記のexample.comを使う場合を考える。

.env
APP_ORIGIN_DOMAIN=example.com

config/app.phpの中に以下を記述。

image.png

app.php
'origin_domain' => env('APP_ORIGIN_DOMAIN'),

env関数を使って、.envファイルの中のデータを抜き出し、変数に格納する。


3-3. 自分でヘルパー関数を作成

envファイルから取得したドメイン名を使って、引数にパスを入れると、https://example.com/引数で渡したパスを返す自作のヘルパ関数を作成する。

3-3-1. ヘルパ関数を定義

app直下に、Helpers > ViewHelper.phpを作成する。

image.png

ViewHelper.php
<?php

if (! function_exists('origin_url')) {
    function origin_url(string $path):string
    {
        return "https://" . config('app.origin_domain') . $path;
    }
}

if (! function_exists('origin_url'))
既存のヘルパ関数と名前が被ってないか確認。被っている場合に上書きしないようにしている。

ヘルパ関数の作成時は必須。

function origin_url(string $path):string
関数名origin_url。引数で変数pathを渡す(型はString)。末尾の:stringは戻り値の型を指定している。

戻り値がstring以外だとエラーが発生する。

return "https://" . config('app.origin_domain') . $path;
戻り値で https://example.com$path を返す。

3-3-2. ヘルパ関数の読み込み

ヘルパ関数を定義したらcomposerで読み込む必要がある。

ルートディレクトリにあるcomposer.jsonのautoloadに以下を追記する。

composer.json
composer.json
    "autoload": {
        "files": [
            "app/Helpers/ViewHelper.php"
        ],
    }

autoloadを更新すれば読み込み完了。

$ composer dump-autoload

これで、定義したヘルパ関数をビューで呼び出せるようになった。


3-4. ビューで呼び出し

直接URLを書き込んでいる箇所を、{{ }}で囲ってヘルパ関数を呼び出せばOK。

index.blade.php
"url": "https://example.com",

  変換

"url": "{{origin_url()}}",



配下のディレクトリも指定する場合は引数でパスを渡す。

index.blade.php
"url": "https://example.com/src/logo.png",

  変換

"url": "{{origin_url(`/src/logo.png`)}}",



以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】関数名の末尾のコロン型(:array, : int, : stringなど)は何を表しているのか?

Laravelで定義した関数名の末尾にコロン型(:array, : int, : stringなど)がついていたため、何の意味があるのか調べてみた。

結論からいうと、戻り値の型を指定している。

function 関数名(引数の型 引数):戻り値の型
    {
        処理
    }

:の後ろはスペースを開けてもいい。:intでも: intでもどちらでも問題ない。

指定した型と異なる場合はTypeErrorが発生する。

error
Errors with:
Fatal error: Uncaught TypeError: Return value of 関数名() must be an instance of 型


#例1
function origin(string $path):string {
        処理
    }

#例2
function counter(?string $src, int $number):int {
       処理
    }

#例3
function page_arr(int $current, int $last):array {
       処理
    }



PHP公式 戻り値 下部の英語の部分に記載されている。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker環境のLaravelアプリをHerokuデプロイした後、画像をS3を利用して表示できるようにする

概要

Docker、Laravel、Heroku、S3(AWS)の勉強がてら画像アップロードができる掲示板アプリを作ったのですが、S3での画像表示にてこずったので備忘録として記事にしました。
この記事はあくまで本番環境でS3を使用して画像表示をするための記事ですので、ローカル環境で画像アップロード機能が仕上がっている事が前提となります。
自分はこちらの記事を参考に画像アップロード機能を作りました↓
https://note.com/koushikagawa/n/n74380a1f3643

アプリケーションについて

簡単な掲示板アプリです。
908a56e59e9e00691f538f81f46e1298.png
新規投稿ページです↑
91cd29065fe23cad6217b129accbb1d3.png
詳細ページにて画像が表示されます↑
トップページには画像を表示しないので省略します。

AWS

AWSの登録等は省略します。S3に移動しバケットを作成します。
a3ddd4d79078faac6a5002e66ac8dfd4.png
バケット名を入力します↑
68f82c67a05f1686d6bdf1c40e774e95.png
バケットの設定をこのようにして「バケットを作成」を押します。

コード

画像アップロードに関係するコードを載せていきます。

create.blade.php
<form action="/posts" method="post" enctype="multipart/form-data">
    {{ csrf_field() }}
    <input type="file" name="image_url">
    @if ($errors->has('image_url'))
        <div>{{ $errors->first('image_url') }}</div>
    @endif
</form>

postsテーブルの画像のカラム名もimage_urlにしています。変えた方がわかりやすかったかもしれないですね。

PostController.php
public function store(request $request)
{
    $post = new Post();
    $post->body = $request->body;
    // ローカル
    // $time = date("Ymdhis");
    // $post->image_url = $request->image_url->storeAs('public/post_images', $time.'_'.Auth::user()->id. '.jpg');
    $image = $request->file('image_url');
    $path = Storage::disk('s3')->putFile('/', $image, 'public');
    $post->image_url = $path;
    $post->user_id = $id;
    $post->save();
    return redirect()->to('/');
}

public function show(Post $post)
{
    $user_id = $post->user_id;
    $user = DB::table('users')->where('id', $user_id)->first();
    return view('posts.detail', [
        'post' => $post,
        'user' => $user,
        // ローカル
        // 'image_url' => str_replace('public/', 'storage/', $post->image_url)
        'image_url' => $post->image_url
    ]);
}

public function update(request $request)
{
    $id = $request->post_id;
    $post = Post::findOrFail($id);
    $post->body = $request->body;
    if($request->hasFile('image_url'))
    {
        // ローカル
        // Storage::delete('public/post_images/' . $post->image_url);
        // $time = date("Ymdhis");
        // $post->image_url = $request->image_url->storeAs('public/post_images', $time.'_'.Auth::user()->id. '.jpg');
        $image = $request->image_url;
        Storage::disk('s3')->delete($image); //画像削除
        $path = Storage::disk('s3')->putFile('/', $image, 'public');
        $post->image_url = $path;
    }
    $post->save();
    return redirect()->to('/');
}
detail.blade.php
@if ($image_url)
    // ローカル
    // <img src="/{{ $image_url }}" width="250px" height="250px">
    <img src="https://バケット名.s3.ap-northeast-1.amazonaws.com/{{ $image_url }}" width="250px" height="250px">
@endif

ローカルのコードも一応載せています。無視してもらって結構です。

HerokuにAWSの環境変数を設定

terminal
% heroku config:set AWS_ACCESS_KEY_ID=◯◯◯◯◯◯◯◯◯◯
% heroku config:set AWS_BUCKET=バケット名
% heroku config:set AWS_DEFAULT_REGION=ap-northeast-1
% heroku config:set AWS_SECRET_ACCESS_KEY=△△△△△△△△△△

アクセスキーとシークレットキーをそれぞれ入力します。

terminal
% git push heroku main

これで本番環境で画像が表示できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

S3を利用して画像を表示できるようにする

概要

Docker、Laravel、Heroku、S3(AWS)の勉強がてら画像アップロードができる掲示板アプリを作ったのですが、S3での画像表示にてこずったので備忘録として記事にしました。
この記事はあくまで本番環境でS3を使用して画像表示をするための記事ですので、ローカル環境で画像アップロード機能が仕上がっている事が前提となります。
自分はこちらの記事を参考に画像アップロード機能を作りました↓
https://note.com/koushikagawa/n/n74380a1f3643

アプリケーションについて

簡単な掲示板アプリです。
908a56e59e9e00691f538f81f46e1298.png
新規投稿ページです↑
91cd29065fe23cad6217b129accbb1d3.png
詳細ページにて画像が表示されます↑
トップページには画像を表示しないので省略します。

AWS

AWSの登録等は省略します。S3に移動しバケットを作成します。
a3ddd4d79078faac6a5002e66ac8dfd4.png
バケット名を入力します↑
68f82c67a05f1686d6bdf1c40e774e95.png
バケットの設定をこのようにして「バケットを作成」を押します。

コード

画像アップロードに関係するコードを載せていきます。

create.blade.php
<form action="/posts" method="post" enctype="multipart/form-data">
    {{ csrf_field() }}
    <input type="file" name="image_url">
    @if ($errors->has('image_url'))
        <div>{{ $errors->first('image_url') }}</div>
    @endif
</form>

postsテーブルの画像のカラム名もimage_urlにしています。変えた方がわかりやすかったかもしれないですね。

PostController.php
public function store(request $request)
{
    $post = new Post();
    $post->body = $request->body;
    // ローカル
    // $time = date("Ymdhis");
    // $post->image_url = $request->image_url->storeAs('public/post_images', $time.'_'.Auth::user()->id. '.jpg');
    $image = $request->file('image_url');
    $path = Storage::disk('s3')->putFile('/', $image, 'public');
    $post->image_url = $path;
    $post->user_id = $id;
    $post->save();
    return redirect()->to('/');
}

public function show(Post $post)
{
    $user_id = $post->user_id;
    $user = DB::table('users')->where('id', $user_id)->first();
    return view('posts.detail', [
        'post' => $post,
        'user' => $user,
        // ローカル
        // 'image_url' => str_replace('public/', 'storage/', $post->image_url)
        'image_url' => $post->image_url
    ]);
}

public function update(request $request)
{
    $id = $request->post_id;
    $post = Post::findOrFail($id);
    $post->body = $request->body;
    if($request->hasFile('image_url'))
    {
        // ローカル
        // Storage::delete('public/post_images/' . $post->image_url);
        // $time = date("Ymdhis");
        // $post->image_url = $request->image_url->storeAs('public/post_images', $time.'_'.Auth::user()->id. '.jpg');
        $image = $request->image_url;
        Storage::disk('s3')->delete($image); //画像削除
        $path = Storage::disk('s3')->putFile('/', $image, 'public');
        $post->image_url = $path;
    }
    $post->save();
    return redirect()->to('/');
}
detail.blade.php
@if ($image_url)
    // ローカル
    // <img src="/{{ $image_url }}" width="250px" height="250px">
    <img src="https://バケット名.s3.ap-northeast-1.amazonaws.com/{{ $image_url }}" width="250px" height="250px">
@endif

ローカルのコードも一応載せています。無視してもらって結構です。

HerokuにAWSの環境変数を設定

terminal
% heroku config:set AWS_ACCESS_KEY_ID=◯◯◯◯◯◯◯◯◯◯
% heroku config:set AWS_BUCKET=バケット名
% heroku config:set AWS_DEFAULT_REGION=ap-northeast-1
% heroku config:set AWS_SECRET_ACCESS_KEY=△△△△△△△△△△

アクセスキーとシークレットキーをそれぞれ入力します。

terminal
% git push heroku main

これで本番環境で画像が表示できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】axiosでCSVを受け取りブラウザでファイルダウンロードに移行させる方法。

前提

サーバーサイドはLaravel、フロントはVue.jsです。
今回はJavaScriptの話がメインなのでPHPやLaravelが分からない方でも支障なく読んでいただけると思います。

やりたかったこと

ユーザー目線:ブラウザに表示されているダウンロードボタンをクリックすると、CSVファイルをダウンロードできる。

僕目線:LaravelでCSVを吐き出すAPIを用意し、axiosを使ってAPIを叩きCSVをダウンロードできるようにする。

起こった問題

ブラウザの検索タブでエンドポイントに直接アクセスするとうまくダウンロードできるが、axiosを使ってエンドポイントにアクセスするとダウンロードできない。(CSVの内容はデータとして返ってくるが、ブラウザがダウンロード画面に移行しない。)

失敗した方法

CSVを返すAPIを単純に叩いてダウンロードできると思ったのですが、うまくいきませんでした。
※関係ない部分は色々省略してます。

sample.js
downloadCsv() {
    axios
      .get("/api/download/csv", {
      })
      .catch((error) => {
        console.log(error.messagae);
      });
  },

CSVのデータはちゃんと返ってきますが、ブラウザがダウンロードに移行しません。(ユーザー目線だと何も起こらない。)

うまくいった方法

コードを下記のようにかえるとうまくダウンロードできました。

sample.js
import saveAs from "file-saver";
// 省略
downloadCsv() {
  axios
    .get("/api/download/csv", {
      responseType: "blob",
    })
    .then((res) => {
      let mineType = res.headers["content-type"];
      const name = res.headers["content-disposition"];
      const blob = new Blob([res.data], { type: mineType });
      saveAs(blob, name);
    })
    .catch((error) => {
      console.log(error.messagae);
    });
};

追記部分を解説していきます。

リクエスト時の処理

sample.js
.get("/api/download/csv", {
  responseType: "blob",
})

データをBLOBとして受け取ります。
BLOBとはBinary Large OBjectの略で、IT用語辞典では以下のように解説されています。

BLOBとは、データベースのフィールド定義などで用いられるデータ型の一つで、テキスト(文字列)や整数のように既存のデータ型としては用意されていない任意のバイナリデータを格納するためのもの。

つまり、BlOBはテキストファイルだけではなく画像やPDFなどいろいろな形式のファイルを扱うことができる訳です。

そしてJavaScriptでは受け取ったデータをBOLBにするとこで、ファイルにすることが可能になります。

CSVはテキストデータなのでBLOBにする必要はないのでは?と思ったのですが、データをファイルにするために必要なようです。

レスポンスを受け取った後の処理

sample.js
.then((res) => {
  let mineType = res.headers["content-type"];
  const name = res.headers["content-disposition"];
  const blob = new Blob([res.data], { type: mineType });
  saveAs(blob, name);
})

res.headers["content-type"]にはLaravel側で設定した'text/csv'が格納されています。今回は必ず'text/csv'が返るようになっていますが、ダウンロード用のメソッドを色々な形式に対応できるようにするためにこのように書く必要がありますね。

res.headers["content-disposition"]にはLaravel側で設定したファイル名が格納されています。

new Blob([res.data], { type: mineType });
JavaScriptのBlobオブジェクトのインスタンスを生成しています。
BlobオブジェクトはBLOBをJavaScriptで扱うためのオブジェクトです。
第一引数にファイルの内容の配列、第二引数にファイルの種類を指定して使います。

saveAs(blob, "file");
'file-server'というライブラリを使ってファイルを保存しています。
詳しくは割愛しますが、ブラウザ間で異なるファイル保存の処理を1行で書けます。

文字化けを修正

無事CSVをダウンロードすることができましたが、ファイルの内容が文字化けしていました。
はっきりとした原因は分かりませんでしたが、Laravel側でSJIS形式に変換していた部分をUTF-8に変換するように変更すると文字化けしなくなりました。

sample.php
// mb_convert_encoding($content, 'SJIS', 'auto');
mb_convert_encoding($content, 'UTF-8', 'auto');

HTMLのmetaタグでUTF-8に指定しているのが原因なのか?
とりあえず、これで内容も分かるCSVファイルをダウンロードできるようになりました。めでたしめでたし。

最後に

aixosを使ってファイルをダウンロードするにはひと手間いることが分かりました。
BLOBについては恥ずかしながら全然知らなかったです。

今回はLaravel側でCSVを作成しましたが、データをJSON形式で送るようにしてVue側でCSVを作成することもできそうです。

他にもこんな方法があるとか、ここ間違ってるよとかあればご指摘等よろしくお願いします!
ではでは。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel Mixで環境に応じて複数の.envで環境変数を管理する

はじめに

こんにちは。先日、ランニングシューズ以外でいい靴を買ってしまいました、筆者です:smile:

さて、LaravelでWebpack使っているので、Laravel Mixを使用しております。

その際に.envで環境変数を分ける機会がありましたので、共有します。

結論

急いでいる方はこちらご参照くださいませ。Laravel Mixのドキュメントの書いてあるんです:ok_hand:
(ただ、スター:star:が超少ないのが気になります...:thinking:)

解説

1. mix-env-file をインストールする。

$ npm install mix-env-file

2. dotenv をインストールする。

あとでコンパイルするときにインストールしろと怒られるのでこちらもインストールします。
(ドキュメントには書いてないんです:sob:)

$ npm install dotenv

3. package.jsonを編集する。

サンプルはこちら↓

package.json
"scripts": {
  "dev": "cross-env ENV_FILE=./.env.dev npm run development",
  "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
  "watch": "npm run development -- --watch",
  "watch-poll": "npm run watch -- --watch-poll",
  "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
  "prod": "cross-env ENV_FILE=./.env.prod npm run production",
  "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
}

上記の場合、以下の動きをします。
npm run devしたときは、./.env.devを読みます。
npm run prodしたときは、./.env.prodを読みます。

4. webpack.mix.js を編集する。

webpack.mix.js
let mix = require('laravel-mix');
require('mix-env-file');

// Then pass your file to this plugin
// If this is not set, this plugin won't do anything and the default .env variables will remain
mix.env(process.env.ENV_FILE);

以上です:ok_hand:

おわりに

.env便利でした:smile:
それでは!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker composeでngrokを簡単に使う方法(QOL爆上がりな件)

docker-composeで開発をしていて、お手元のスマートフォンでの確認や、他の人にサービスを見てもらう際に、
毎度デプロイすることは難あり。
そういった時に、ngrokを使用すると、簡単にローカル環境を外部に一時的に公開することができる。

今回はLaravelの開発においての使用とし、開発環境はdockerである。
laravelが8000ポートで、
データベースが3000ポートとしている。

また、brew install ngrokを済ませていることが前提。

手順

1 Dockerを起動しておく

docker-compose up -d

2 ngrokを起動する

ngrok http 8000

このコマンドでngrokが起動する。

この時の注意点は、
docker自体にngrokなど特に何もインストールする必要はなく、
起動したいサーバーのポート番号を指定する。

laravelアプリはlocalhost:8000だから、
ngrok http 8000となる。
データベースのポート番号、3000は指定する必要はないと確認している。

そうすると、ngrokの起動画面に切り替わり、forwardingと記載されているurlが実際にchromeなどでアクセス
する際のurlになる。

ngrokの使用を停止する時は、
controll + c
で止まる。

これ使ってQOL爆上がりです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravelとreact 一緒に使うけど 厳密に言うと APIじゃない件について

めっちゃ時間無駄にしたので戒めのためのメモ

状況
laravelで作ったサービスをあとからreact化しようとした際 遭遇
axiosにて記事情報を post すると 何故か リレーションしているuser_idがnillになり処理が完了しない。

結論
・routes/api.phpにかかない
普通にweb.phpに書く

ここに書くとpostするとauthのガードに引っかかってuser_idがnillになってエラー


ちなみに私は トークン とかいろいろ 迷子になって laravel passportまでいれて結局関係にことにあとで気づきました・・。 勉強にはなったが。。。

ソース
https://teratail.com/questions/234875

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでQRコードを作ってみた

今回は動的にサイトのURLを作成したかったので、PHPでQRコードを作成したみました。

結論から言いますと、全く難しくないです!!

読み取りもしっかりと行えます!

なので、肩の力を抜いてご覧ください。

それでは説明していきます!

まずはQRコードを作成するためのライブラリをダウンロードしてください。

compose require endroid/qr-code

後は下記の記述を参考にQRコードを作成します。

今回はコントローラーでQRコードを作成します。

<?php

namespace App\Http\Controllers;

use App\Models\Qr;
use Illuminate\Http\Request;
use Endroid\QrCode\Color\Color;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
use Endroid\QrCode\QrCode;
use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
use Endroid\QrCode\Writer\PngWriter;

class QrCodeController extends Controller
{
    public function generate(Request $request) {
        $writer = new PngWriter();

        $origin_URL = 'https://www.example.com';

        // Create QR code
        $qrCode = QrCode::create($origin_URL)
            ->setEncoding(new Encoding('UTF-8'))
            ->setErrorCorrectionLevel(new ErrorCorrectionLevelLow())
            ->setSize(300)
            ->setMargin(10)
            ->setRoundBlockSizeMode(new RoundBlockSizeModeMargin())
            ->setForegroundColor(new Color(0, 0, 0))
            ->setBackgroundColor(new Color(255, 255, 255));

        // QRコードの出力
        $result = $writer->write($qrCode);
        header('Content-Type: '.$result->getMimeType());
        echo $result->getString();


        // // QRコードの画像ファイルを保存
        // $result->saveToFile(__DIR__.'/qrcode.png');

        // // QRコードのURL
        // $dataUri = $result->getDataUri();
    }
}

このようにしてQRコードを作成することができます!

実際にコントローラーを適用しているURLに移動すると下記のように表示されます。

無題.png

めちゃめちゃ簡単!

以上、「PHPでQRコードを作ってみた」でした!

Thank you for reading

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerで作成したLaravelで「bash: composer: command not found」と出たときの対処法

Dockerって環境構築が簡単に行えてすごく便利ですよね。

特にデータベースとかわざわざインストールしなくてもできてしまいますもんね。

そんなDockerでcomposerを使ってあるものをインストールしようとしたのですが、下記のようなエラーが起きました。

bash: composer: command not found

Dockerのコンテナ外ではcomposerコマンドを打てるのですが、コンテナ内では打てませんでした。

結論から言いますと、Dockerのイメージをビルドするときにcomposerをインストールすれば解決します。

Dockerのphpを構築するDockerfileで以下の記述を加えて下さい。

ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer
ENV PATH $PATH:/composer/vendor/bin

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php composer-setup.php \
  && php -r "unlink('composer-setup.php');" \
  && mv composer.phar /usr/local/bin/composer

一応僕のDockerfileのコードを載せておきますね。

FROM php:7.4-fpm-buster
COPY ./backend /work
COPY ./docker/php/php.ini /usr/local/etc/php/php.ini
LABEL maintainer="ucan-lab <info@u-can.tech>"

SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

# tinker(psysh)
ARG PSYSH_DIR=/usr/local/share/psysh
ARG PSYSH_PHP_MANUAL=$PSYSH_DIR/php_manual.sqlite
ARG PHP_MANUAL_URL=http://psysh.org/manual/ja/php_manual.sqlite

# timezone
ARG TZ=Asia/Tokyo

# composer
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer
ENV PATH $PATH:/composer/vendor/bin

RUN apt-get update && \
  apt-get -y install git unzip libzip-dev libicu-dev libonig-dev libjpeg-dev libpng-dev libfreetype6-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  docker-php-ext-install intl pdo_mysql zip bcmath && \
  docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ && \
  docker-php-ext-install gd

# Install Composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php composer-setup.php \
  && php -r "unlink('composer-setup.php');" \
  && mv composer.phar /usr/local/bin/composer

# Install Node.js
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - \
    && apt-get update \
    && apt-get install -y nodejs

RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
RUN apt-get install -y cron

こんな感じでコンテナ内に入り、composer -Vと打ちバージョンが返ってくれば成功です。

以上、「Dockerで作成したLaravelで「bash: composer: command not found」と出たときの対処法」でした!

Thank you for reading

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelの学習をしてウェブアプリを作ったのでやったことまとめ

制作アプリについて

アプリ概要

インスタ風の文字投稿専用SNS
スクリーンショット 2021-03-15 2.52.52.png

会員登録、ログイン、プロフィール編集
投稿時画像加工、投稿・編集・削除

現在の不具合状況について
・アップロード画像が2MBを超えるものの投稿ができない
・投稿した画像が横向きに反転してしまう

作った経緯と目的

・転職時に必要なため

・自分自身のあったらいいなをアプリ化

1つ目。自分自身何かエンジニアとしての能力証明ができる術を持っていなかったので、転職時のポートフォリオとしてのスキル証明用になればと思い制作
2つ目。自分が必要としていることは誰かもまた必要としているのかなと思い「自分自身のあったらいいな」というものを実現させた。
具体的には、
「○○って字を綺麗に書きたいけど検索しても自分の気に入る字体がみつからない」
「参考になる字体を検索して探したい」

という課題の解消させた。
イメージとしては、お手本の字をただ真似て書くのではなく、自分の書きたい文字について探してからお手本にして書く。
プログラミング学習するときに、課題で出されたものを作るのではなく、自分の作りたいものを調べながら作るという感じです。

開発期間

Laravel の基礎学習期間二週間とアプリ開発に1ヶ月ほど

ER図

カテゴリー機能はまだ追加していないので今の所お飾りです。
スクリーンショット 2021-03-11 8.16.22.png

開発環境

・言語:PHP7, Bootstrap, HTML, CSS(Sass)
・フレームワーク:Laravel8
・DB:SQLite→postgreSQL
・環境:ローカル環境(MacOS), VSCode
・インフラ:AWS S3, heroku

主な機能

・CRUD
・会員登録、ログイン、ログアウト
・画像アップロード
・DBリレーション
・検索機能
・マイページ
・ペジネーション
・画像加工
・バリデーション

追加できたらいいもの

・2枚以上の画像投稿
・タグ機能
・お気に入り
・JavaScriptでのデザイン、操作
勉強中であり多言語の知識が必要で時間がかかりそうなため一時保留
画像加工は強制的にしているので何かしらの形で修正したい

感想

簡単に手書きで欲しい機能、デザイン、DB設計などを考えてから始めたものの
途中途中であれもいいかもこれもいいかもなんてことを思いよく脱線したので、一番初めにしっかりと考えてからやれたらもっと早く作り終えたと思う。
でもその中で何が必要で何があったら便利で何がいらない機能なのかなどひとつひとつ客観的に物事を考えて自分自身で試行錯誤するところは楽しかったし面白かったです。
エンジニアの挫折率が高い所以の一つに、エラーや不具合があると思うけど、この模索している時がなんだかんだ一番面白く、丸1日動かなかったエラーが解消されたときはとても気持ちが良かったです。
GitHubにコードあげ忘れることが多かったのでもう少し意識してできたら良かった。

その他メモ

フロントエンド側のデザインUI/IXに関してはあまり自分の希望する分野ではないので最低限に収めてバックグラウンドの方に重きを置いていたので、フロントエンド側にももう少し手を加えて改善したい。
GitHubの使い方、DB操作、他言語、インフラなど知って行くたびに自分の知らないことが多いのでこれからも少しずつ学習していきたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel8 ReactHooks & MySQL Create処理

やりたいこと

フロントはReactHooksを使用して、LaravelはAPIサーバーでバックエンド処理をおこなう。
バックエンド処理はaxiosを使用。

完成図

e147bdcac8ccf1b01931c53e89227bdd.png

開発環境

  • Laravel 8.0
  • php 7.3
  • MySQL 8

State

  • const [ name, setName ] = useState(null);

入力値name(名前)のState

  • const [ email, setEmail ] = useState(null);

入力値email(メールアドレス)のState

  • const [ city, setCity ] = useState(null);

入力値city(住所)のState

  • const [ address, setAddress ] = useState(null);

入力値address(市区町村)のState

  • const [ phone, setPhone ] = useState(null);

入力値phone(電話番号)のState

入力フォーム

map処理はkeyを明示する必要があるので注意

keyの明示についてはVirtualDOMのdiffから実際のDOMに反映させるときに最小限の変更にするために使われる。

form
import React, { useEffect, useState  } from 'react';

import employeeServices from "../services/Employee"

function Form(){

  const [ name, setName ] = useState(null);
  const [ email, setEmail ] = useState(null);
  const [ city, setCity ] = useState(null);
  const [ address, setAddress ] = useState(null);
  const [ phone, setPhone ] = useState(null);
  const [ rol, setRol ] = useState(null);
  const [ listRol, setListRol ] = useState([]);

  useEffect(() => {
    async function fetchDataRol() {
      // load data from API
      const res = await employeeServices.list();
      setListRol(res.data)
    }
    fetchDataRol();
  },[]);

  const saveEmployee = async () => {

    const data = {
      name, email, city, address, phone, rol
    }
    const res = await employeeServices.save(data);

    if (res.success) {
      alert(res.message)
    }
    else {
      alert(res.message)
    }
  }

  return(
    <div>
      <div className="row">
        <div className="col-md-6 mb-3">
          <label htmlFor="firstName">Name employee </label>
          <input type="text" className="form-control" placeholder="Name"
            onChange={(event)=>setName(event.target.value)} />
        </div>
      </div>

      <div className="row">
        <div className="col-md-6 mb-3">
          <label htmlFor="email">Email</label>
          <input type="email" className="form-control" placeholder="you@example.com"
            onChange={(event)=>setEmail(event.target.value)} />
        </div>
      </div>

      <div className="row">
        <div className="col-md-6 mb-3">
          <label htmlFor="phone">City </label>
          <select id="inputState" className="form-control" onChange={(event)=> setCity(event.target.value)}>
             <option selected>Choose...</option>
             <option value="London">London</option>
             <option value="Madrid">Madrid</option>
             <option value="New York">New York</option>
          </select>
        </div>
      </div>

      <div className="row">
        <div className="col-md-6 mb-3">
          <label htmlFor="address">Address</label>
          <input type="text" className="form-control" placeholder="1234 Main St"
            onChange={(event)=>setAddress(event.target.value)} />
        </div>
      </div>

      <div className="row">
        <div className="col-md-6 mb-3">
          <label htmlFor="phone">Phone </label>
          <input type="text" className="form-control" placeholder="123467890"
              onChange={(event)=>setPhone(event.target.value)}  />
        </div>
      </div>

      <div className="row">
        <div className="col-md-6 mb-3">
                    <label htmlFor="phone">Rol </label>
                    <select id="inputState" className="form-control" onChange={(event)=> setRol(event.target.value)}>
             <option selected>Choose...</option>
             {
               listRol.map((item)=>{
                 return(
                   <option key={item.rol_id} value={item.rol_id}>{item.rol_name}</option>
                 )
               })
             }
          </select>
        </div>
      </div>

      <div className="row">
        <div className="col-md-6 mb-3">
          <button className="btn btn-primary btn-block" type="submit"
          onClick={()=>saveEmployee()}>Save</button>
        </div>
      </div>
    </div>
  )
}

export default Form;

saveEmployee押化後、axiosのemployee.saveが呼び出されてlocalhost:8000/api/employee/createにPOSTする。

axios通信

axios
const baseUrl = "http://localhost:8000/api/employee"
import axios from "axios";
const employee = {};

employee.save = async (data) => {
  const urlSave= baseUrl+"/create"
  const res = await axios.post(urlSave,data)
  .then(response=> {return response.data })
  .catch(error=>{ return error; })
  return res;
}

export default employee
api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

Route::post('/employee/create', 'App\Http\Controllers\API\EmployeeController@create');

Laravel側のControllerが呼び出され、実際に処理をするcreateアクションを呼び出します。

保存処理の最後にはresponseを返してあげる。今回は動作確認のため、失敗時にエラーメッセージのみを返しています。

コントローラー

EmployeeController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Employee;

class EmployeeController extends Controller
{
    public function index(){
        return view("employee");
      }

      public function create(Request $request){

        try {

          $insert['name_lastname'] = $request['name'];
          $insert['email'] = $request['email'];
          $insert['city'] = $request['city'];
          $insert['direction'] = $request['address'];
          $insert['phone'] = $request['phone'];

          Employee::insert($insert);

          $response['message'] = "Save succesful";
          $response['succes'] = true;

        } catch (\Exception $e) {
          $response['message'] = $e->getMessage();
          $response['error'] = true;
        }

        return $response;
      }
}

createアクションはこのような流れで実装できます

指摘などがございましたらコメントで教えていただけると幸いです^_^

おわり

次はレコードをmapで繰り返し表示する方法など記事にしていこうと思います〜!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel8+ReactHooks+MySQL Create処理

やりたいこと

フロントはReactHooksを使用して、LaravelはAPIサーバーでバックエンド処理をおこなう。
バックエンド処理はaxiosを使用。

開発環境

  • Laravel 8.0
  • php 7.3
  • MySQL 8

ModelはItem カラムはtext/commentと仮定しています。

State

  • const [ text, setText ] = useState("");

入力値textのState

  • const [ comment, setComment] = useState("");

入力値commentのState

入力フォーム

入力値をSetStatで更新。submitでsaveEmployeeを呼び出す。

form
return (
      <form className="add-form">
        <div className="add-text">
          <label>内容</label>
          <input type="text" value={text} onChange={(event)=>setText(event.target.value)}/>
        </div>
        <div className="add-comment">
          <label>コメント</label>
          <input type="number" value={amount} onChange={(event)=>setComment(event.target.value)}}/>
        </div>

        <div className="form-row">
        <color="primary" aria-label="add"
          type="submit"
          onClick={()=>saveEmployee()}>追加</div>
    </form> 
    )

呼び出されたsaveEmployeeはdataに定義された値を保持してemployeeServices.saveにアクセス。
この際、dataに定義されているtextとcommentはuseStateを参照しています。

axios
const saveEmployee = async () => {

const data = {
  text, 
  comment, 
};

  const res = await employeeServices.save(data);
  setItemlist(res.data)

}

saveEmployeeから渡された値(data)を引数に、LaravelのAPIサーバーにPOSTする。

employee
let url = location.href;

const baseUrl = url + 'api/employee';

const employee = {};

employee.save = async (data) => {
  const urlSave = baseUrl+"/save"
  const res = await axios.post(urlSave,data)
  .then(response=>{ return response.data; })
  .catch(error=>{ return error; })
  return res;
}

Laravel側のapi.phpが呼び出され、実際に処理をするcreateアクションを呼び出します。

api.php
use App\Http\Controllers\EmployeeController;

Route::match(['get', 'post'],'/employee/itemcreate', [EmployeeController::class, 'itemcreate']);

itemcreateメソッド内で保存処理を行い、responseに$itemsを渡してreturnしてあげる。

ItemController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Item;

class EmployeeController extends Controller
{
      public function itemcreate(Request $request){

        try {
            $items = new Item();
            $items->text = $request->text;
            $items->amount = $request->comment;
            $items->save();

            $response['data'] = $items;
            $response['message'] = '成功';
            $response['success'] = true;

          } catch (\Exception $e) {
            $response['message'] = $e->getMessage();
            $response['success'] = false;
          }
          return $response;
      }

}

createアクションはこのような流れで実装できます

指摘などがございましたらコメントで教えていただけると幸いです^_^

次はレコードをmapで繰り返し表示する方法など記事にしていこうと思います〜!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む