DIについては、DI(依存性注入) を参照
DIコンテナは、DIのインスタンス生成を、別のファイルでコンテナ化して解決しようという設計パターンです。
まず、共通で使う Animalクラスを用意します。
手っ取り早くファイル分割せず Animal.php にクラス(Dog、Cat)を記述しました。
interface Animal {
public function bow();
}
class Dog implements Animal {
public function bow() {
echo 'wan! wan!'.PHP_EOL;
}
}
class Cat implements Animal {
public function bow() {
echo 'nya-! nya-!'.PHP_EOL;
}
}
下記のサンプルはコンストラクタ内で Dog を生成して鳴きます。
class AnimalConsole {
protected $dog;
public function __construct() {
$this->dog = new Dog();
}
public function bow() {
$this->dog->bow();
}
}
$animal_console = new AnimalConsole();
$animal_console->bow();
この場合、コンストラクタで new Dog() するため、Dogが実装できるまで AnimalConsole が実装できません。
コンストラクタの引数に、Dog、Cat インスタンスを注入します(コンストラクタインジェクション)
他の記事をみているとこれで「DI」となってますが、まだ Dog に依存しているため Cat に修正したい場合に手間がかかります。
require_once 'Animal.php';
class AnimalConsole {
protected $dog;
public function __construct(Dog $dog) {
$this->dog = $dog;
}
public function bow() {
$this->dog->bow();
}
}
$animal_console = new AnimalConsole(new Dog());
$animal_console->bow();
今度は Dog、Cat 両方扱えるように Amimal を引数とします。
require_once 'Animal.php';
class AnimalConsole {
protected $animal;
public function __construct(Amimal $animal) {
$this->animal = $animal;
}
public function bow() {
$this->animal->bow();
}
}
$animal_console1 = new AnimalConsole(new Dog());
$animal_console1->bow();
$animal_console2 = new AnimalConsole(new Cat());
$animal_console2->bow();
これで、Dog、Cat どちらでも注入できるようになりました。
インスタンスを直接渡すのではなく、コンテナ(箱)内でインスタンスを作成して処理するのが、DIコンテナです。
今回は、composerのContainerライブラリ「pimple」を利用しています。
$ composer require pimple/pimple ~3.0
下記の場合、Dog、Cat のインスタンスをコンテナ化しています。
また、$container['animal'] では、インスタンスを簡単に切り替えることができます。
require_once 'Animal.php';
require_once __DIR__.'/../vendor/autoload.php';
use Pimple\Container;
class AnimalConsole {
protected $animal;
public function __construct(
Animal $animal
) {
$this->animal = $animal;
}
public function bow() {
$this->animal->bow();
}
}
$container = new Container();
$container['dog'] = function($c) {
return new Dog();
};
$container['cat'] = function($c) {
return new Cat();
};
$container['animal'] = function($c) {
return $c['dog'];
};
$container['animalConsole'] = function ($c) {
return new AnimalConsole($c['animal']);
};
$container['animalConsole']->bow();
Container の部分はファイル分離する必要はあります。
ありそうでなかった会計向けSDK
freee が国内初なようで、2020/02時点の対応言語は「C#」「Java」「PHP」
freee API と統制をとるために「OpenApi generator」を利用して有志が開発しているとのこと
https://github.com/freee/freee-accounting-sdk-csharp/
https://github.com/freee/freee-accounting-sdk-java
https://github.com/freee/freee-accounting-sdk-php/
PHPの場合は、composer で用意されており、さらにサンプル用に Docker & Laravel が公開されている。
コールバックURL
freee APIアプリの「コールバックURL」は以下を設定しておく
※デフォルト:urn:ietf:wg:oauth:2.0:oob
※Laravel dでも後に設定する
http://localhost:8000/auth-callback
サンプルは SDK の中に入っているので git clone
$ git clone https://github.com/freee/freee-accounting-sdk-php.git
.env を設定する
$ cd freee-accounting-sdk-php.git
$ cd samples/BasicWebApp
$ cp .env.example .env
freeeアプリ の Client ID、Secret ID を記述
FREEE_ACCOUNTING_CLIENT_ID=Client ID
FREEE_ACCOUNTING_CLIENT_SECRET=Secret ID
freee-accounting-sdk-php にあるサンプルを利用する
$ cd freee-accounting-sdk-php/samples/BasicWebApp
$ cd samples
$ docker-compose build
$ docker-compose up -d
$ docker exec -it samples_webapp_1 /bin/bash
version: "3.7"
services:
console:
build:
context: .
dockerfile: Dockerfile
image: freee-accounting-sdk-php-console
volumes:
- "./BasicConsole:/usr/src/app"
command: /bin/sh -c "while sleep 1000; do :; done"
webapp:
build:
context: .
dockerfile: Dockerfile
image: freee-accounting-sdk-php-webapp
ports:
- "80:80"
- "8000:8000"
volumes:
- "./BasicWebApp:/usr/src/app"
command: /bin/sh -c "while sleep 1000; do :; done"
FROM php:7.3-cli
RUN apt-get update; \
curl -sL https://deb.nodesource.com/setup_12.x | bash -; \
apt-get install -y --no-install-recommends \
git \
libzip-dev \
nodejs \
unzip \
zlib1g-dev \
; \
rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-install zip
WORKDIR /tmp
RUN curl https://raw.githubusercontent.com/composer/getcomposer.org/76a7060ccb93902cd7576b67264ad91c8a2700e2/web/installer -o - -s | php -- --quiet; \
mv composer.phar /usr/local/bin/composer
WORKDIR /usr/src/app
RUN composer global require laravel/installer
ENV PATH /root/.composer/vendor/bin:$PATH
EXPOSE 8000
webpackが用意されているので、すぐインストールできる
# composer install
composer、Laravel を直にインストールする場合
$ composer require freee/freee-accounting-sdk
$ composer require socialiteproviders/generators
$ composer require socialiteproviders/manager
artisan で socialiteproviders/generators を実行
$ php artisan make:socialite FreeeAccounting --spec=oauth2 --authorize_url=https://accounts.secure.freee.co.jp/public_api/authorize --access_token_url=https://accounts.secure.freee.co.jp/public_api/token --user_details_url=https://api.freee.co.jp/api/1/users/me
$ composer dumpautoload
Laravel/UI を作成
$ composer require laravel/ui --dev
$ php artisan ui vue --auth
$ npm install && npm run dev
# php artisan key:generate
# php artisan serve
Docker の場合は、ホスト指定します。
# php artisan serve --host 0.0.0.0
プロジェクトルートは「samples/BasicWebApp/」
Auth::routes([
'register' => false,
'reset' => false,
]);
Route::get('login', 'Auth\LoginController@redirectToProvider')->name('login');
Route::get('auth-callback', 'Auth\LoginController@handleProviderCallback')->name('authCallback');
「client_id」「client_secret」「redirect」をそれぞれ設定 (リダイレクトURLは、freeeのアプリ管理と合わせる)
'freeeaccounting' => [
'client_id' => env('FREEE_ACCOUNTING_CLIENT_ID'),
'client_secret' => env('FREEE_ACCOUNTING_CLIENT_SECRET'),
'redirect' => 'http://localhost:8000/auth-callback',
],
freee用ドライバー、プロバイダー設定
'defaults' => [
'guard' => 'freee',
'passwords' => 'users',
],
....
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'freee' => [
'driver' => 'freee',
'provider' => 'freee',
],
],
...
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'freee' => [
'driver' => 'freee',
],
],
ログイン処理は、Laravel Socialite ドライバーを利用し、GenericUser で認証する
use App\Http\Controllers\Controller;
use Illuminate\Auth\GenericUser;
use Illuminate\Support\Facades\Auth;
use Socialite;
...
public function redirectToProvider()
{
return Socialite::driver('freeeaccounting')->redirect();
}
public function handleProviderCallback()
{
$user = Socialite::driver('freeeaccounting')->user();
$genericUser = $user->getRaw();
$genericUser['token'] = $user->token;
$genericUser['remember_token'] = '';
Auth::login(new GenericUser($genericUser));
return redirect()->intended($this->redirectTo);
}
public function logout()
{
Auth::logout();
return redirect()->intended('/');
}
namespace App\Providers;
use App\Extensions\SampleSessionGuard;
use App\Extensions\FreeeUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
public function boot()
{
$this->registerPolicies();
Auth::extend('freee', function ($app, $name, array $config) {
return new SampleSessionGuard(
$name,
Auth::createUserProvider($config['provider']),
$app['session.store']
);
});
Auth::provider('freee', function ($app, array $config) {
return new FreeeUserProvider();
});
}
}
namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use SocialiteProviders\Manager\SocialiteWasCalled;
use SocialiteProviders\FreeeAccounting\FreeeAccountingExtendSocialite;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
SocialiteWasCalled::class => [
FreeeAccountingExtendSocialite::class,
],
];
public function boot()
{
parent::boot();
}
}
https://api.freee.co.jp/api/1/users/me からユーザを返す関数を作成
protected function getUserByToken($token)
{
$response = $this->getHttpClient()->get('https://api.freee.co.jp/api/1/users/me', [
'headers' => [
'Authorization' => 'Bearer '.$token,
],
]);
$body = json_decode($response->getBody(), true);
return $body['user'];
}
protected function mapUserToObject(array $user)
{
$user['name'] = $user['last_name'] . ' ' . $user['first_name'];
return (new User())->setRaw($user)->map([
'id' => $user['id'],
'name' => $user['name'],
'email' => $user['email'],
'display_name' => $user['display_name'],
'first_name' => $user['first_name'],
'last_name' => $user['last_name'],
'first_name_kana' => $user['first_name_kana'],
'last_name_kana' => $user['last_name_kana'],
]);
}
ログイン認証したユーザトークンを、Configuration クラスに設定
$user = Auth::user();
$config = Configuration::getDefaultConfiguration()->setAccessToken($user->token);
ちなみに、getAccessToken() でアクセストークンを確認できる。
$config->getAccessToken()
CompaniesApi で事業所IDを取得
$companiesApiInstance = new CompaniesApi(null, $config);
$companiesResponse = $companiesApiInstance->getCompanies();
$targetCompanyId = $companiesResponse->getCompanies()[0]->getId();
DealsApi で取引(収入/支出)一覧の取得
$limit = 5;
$dealsApiInstance = new DealsApi(null, $config);
$dealsResponse = $dealsApiInstance->getDeals(
$targetCompanyId,
null, null, null, null, null, null, null, null, null, null, null, null,
$limit);
$deals = $dealsResponse->getDeals();
getDeals() の引数がちょっとどうなんだろ?
freee API は 開発者向けにOSS として公式公開されています。
https://developer.freee.co.jp/
ここでは、リファレンスを利用して認可コード・アクセストークン・API確認をしてみる。
実際の開発は各言語やSDKを使ってコーディングが必要だが、PHPに関しては、freee SDKを Laravel で使ってみる でやってみた。
まず、freeeアカウントを作成(一般ユーザアカウント)する
次に開発ユーザでログインし、開発用テスト環境の作成する。
アプリ追加画面でアプリを追加すると、Client ID, Client secret が発行される
作成したアプリは、アプリ管理 で管理する
「Webアプリ認証用URL」でブラウザでアクセスし、許可すると「認可コード」が発行される
※アクセストークンではない
https://accounts.secure.freee.co.jp/public_api/authorize?client_id={client_id}&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=token
上記のURLに freee アプリで発行された Client ID を入れてブラウザでリクエストする
https://developer.freee.co.jp/docs/accounting/reference#/Companies/get_companies
鍵マークをクリック
value に アクセストークンを入力して [Authorize]し、成功したら[Close]
[Try it out] をクリックし [Execute] するとAPIが実行される
curl -X GET "https://api.freee.co.jp/api/1/companies" -H "accept: application/json" -H "Authorization: Bearer {Client ID}"
https://api.freee.co.jp/api/1/companies
{
"companies": [
{
"id": xxxxxxx,
"name": "",
"name_kana": "",
"display_name": "個人事業主名",
"role": "admin"
},
{
"id": xxxxxxx,
"name": null,
"name_kana": null,
"display_name": "個人事業主スタータープラン: 開発用テスト環境",
"role": "admin"
}
]
}
Segue を利用せずに Storyboard の Storyboard ID を利用して present で画面遷移させる。 遷移先の UIViewController を明示的にクラス名を指定すると値を渡すことも可能
let storyboard: UIStoryboard = self.storyboard!
let page = storyboard.instantiateViewController(identifier: "page") as! PageViewController
page.customValue = customValue
page.modalPresentationStyle = .fullScreen
page.modalTransitionStyle = .crossDissolve
self.present(page, animated: true, completion: nil)
.modalPresentationStyle、.modalTransitionStyle プロパティは、iOSバージョンによって異なる
public enum UIModalTransitionStyle : Int {
case coverVertical
case flipHorizontal
case crossDissolve
@available(iOS 3.2, *)
case partialCurl
}
public enum UIModalPresentationStyle : Int {
case fullScreen
@available(iOS 3.2, *)
case pageSheet
@available(iOS 3.2, *)
case formSheet
@available(iOS 3.2, *)
case currentContext
@available(iOS 7.0, *)
case custom
@available(iOS 8.0, *)
case overFullScreen
@available(iOS 8.0, *)
case overCurrentContext
@available(iOS 8.0, *)
case popover
@available(iOS 7.0, *)
case none
@available(iOS 13.0, *)
case automatic
}
Xcode で vim を利用するには「XVim2」をプラグインをインストールすると良い。
(11.3 で動作確認)
XVimProject/XVim2 にあるソースをダウンロード
$ https://github.com/XVimProject/XVim2.git
SIGNING_Xcode.md を参照
Xcodeに署名する
$ sudo codesign -f -s XcodeSigner /Applications/Xcode.app
$ xcode-select -p
/Applications/Xcode.app/Contents/Developer
※選択されていない場合は、「xcode-select -s」で選択する
$ cd XVim2
$ make
update_xcode_plugins コマンドを gem でインストール
$ sudo gem install update_xcode_plugins
Password:
Fetching: sync-0.5.0.gem (100%)
Successfully installed sync-0.5.0
...
$ update_xcode_plugins
Found:
- Xcode (11.3.1) [BAB79788-ACEE-4291-826B-EC4667A6BEC5]:
...
$ update_xcode_plugins --unsign
Looking for Xcode...
Unsigning Xcode will make it skip library validation allowing it to load plugins.
However, an unsigned Xcode presents security risks, and will be untrusted by both Apple and your system.
This tool will create a backup and allow you to restore Xcode's signature by running
$ update_xcode_plugins --restore
Choose which Xcode you would like to unsign (use arrows): Xcode (11.3.1) [Signed]: /Applications/Xcode.app
Unsign xcodebuild too?: Yes
...
メニューバー >「Edit」>「XVim」が表示される
Codable を利用して、JSONを 構造体の配列で扱ってみる
JSONのデータにあわせて、Codable クラスの構造体を定義する。
struct HiraganaEntry: Codable {
var word:String
var kana:String
var length:Int
}
カスタムクラスのEntity の配列を定義する
var hiraganaEnties: [HiraganaEntry] = []
Bundle.main.path() でプロジェクト内の JSON ファイルのパスを作成する。
guard let path = Bundle.main.path(forResource: name, ofType: "json") else { return nil }
ファイルパスから UTF-8 で String型で読み込む
let json:String
json = try String(contentsOfFile: path, encoding: .utf8)
JSON(String)を、定義したEntity の配列でデコードする
let entries = try! jsonDecoder.decode([HiraganaEntry].self, from: (json.data(using: .utf8)!))
let jsonDecoder = JSONDecoder()
var hiraganaEnties: [HiraganaEntry] = []
struct HiraganaEntry: Codable {
var word:String
var kana:String
var length:Int
}
func getJSON(name:String) -> Codable? {
let json:String
guard let path = Bundle.main.path(forResource: name, ofType: "json") else { return nil }
do {
json = try String(contentsOfFile: path, encoding: .utf8)
} catch _ {
return nil
}
let entries = try! jsonDecoder.decode([HiraganaEntry].self, from: (json.data(using: .utf8)!))
return entries
}
func loadHiragana() {
hiraganaEnties = getJSON(name: "file_name") as! [GameViewController.HiraganaEntry]
print(hiraganaEnties.randomElement())
}
webpack の環境を構築してみる
参考:webpack 4 入門 webpackの最大の特徴は「モジュールバンドラ」を利用して、複数のjsファイルを1つのファイル「bundle.js」にまとめる。
├── node_modules
├── package.json
├── public
│ ├── index.html
│ └── js
│ └── bundle.js
├── src
│ └── js
│ ├── app.js
│ └── modules
│ ├── add-calculator.js
│ └── tax-calculator.js
└── webpack.config.js
上記の場合「app.js」「add-calculator.js」「tax-calculator.js」をビルドして「bundle.js」にまとめます。
※NodeJS, npm, yarn などは解説省略
$ yarn init --yes
yarn init v1.21.1
warning The yes flag has been set. This will automatically answer yes to all questions, which may have security implications.
success Saved package.json
✨ Done in 0.04s.
$ yarn add webpack webpack-cli webpack-dev-server --dev
var path = require('path');
module.exports = {
entry: "./src/js/app.js",
output: {
path: path.join(__dirname, 'public/js'),
filename: 'bundle.js',
},
resolve: {
extensions: ['.js', '.ts', '.svg']
},
module: {
rules: [{
test: /\.js$/,
use: [{
loader: 'babel-loader',
options: {
presets: ['es2015']
}
}],
}, {
test: /\.ts$/,
use: [{
loader: 'ts-loader',
options: {
compilerOptions: {
declaration: false,
target: 'es5',
module: 'commonjs'
},
transpileOnly: true
}
}]
}, {
test: /\.svg$/,
use: [{
loader: 'html-loader',
options: {
minimize: true
}
}]
}]
}
}
entry:コンパイルするメインJSファイルパス(エントリーポイント)
output:出力するメインJSファイルのパス 最初に require した path を利用して最終的に「public」ディレクトリに「bundle.js」を書き出す
require , importするファイル拡張子を省略設定
ビルドする際、利用するモジュールを設定 babel-loader: Bableに対応する
次世代 EcmaScript で書かれたJSを、サポートしていないブラウザでも動くJSに変換
ts-loader:TypeSceiptに対応する
$ yarn install
yarn install v1.21.1
[1/4] 🔍 Resolving packages...
success Already up-to-date.
✨ Done in 0.12s.
$ mkdir src
export default function addCalculator(number1 ,number2) {
return number1 + number2;
}
import addCalculator from './modules/add-calculator';
import taxCalculator from './modules/tax-calculator';
var item1_price = 100;
var item2_price = 300;
var total_price = addCalculator(item1_price, item2_price);
var tax = 0.1;
var tax_price = taxCalculator(total_price, tax);
window.onload = function() {
document.getElementById('calculate-result').innerHTML = tax_price;
};
export default function taxCalculator(price ,tax) {
return Math.round(price * (1 + tax));
}
$ yarn run webpack
node_modules のパスをbash に登録することで、webpack コマンドを直接利用できる
$ vi ~/.bash_profile
export PATH=$PATH:./node_modules/.bin
$ source ~/.bash_profile
$ psql -h localhost -U default -l
psql: error: could not connect to server: FATAL: unsupported frontend protocol 1234.5679: server supports 2.0 to 3.0
Macにインストールした psql のバージョンを変更する。
$ which psql
/usr/local/opt/libpq/bin/psql
$brew search postgresql
...
$ brew install postgresql@11
$ alias psql='/usr/local/opt/postgresql@11/bin/psql'
Install Docker Desktop on Macをダウンロードしてインストール
$ git clone https://github.com/Laradock/laradock.git
laradock/.env #Dockerコンテナイメージ & 設定
projects/ #Laracelプログラムソース
自分が開発する場合、複数のプロジェクトを一つのサーバで管理するため、projects とした。
例) projects/blog/ projects/shop/ ※Laravelプロジェクト直下に、.htaccess でmod_rewirte(Apacheの場合)
$ cd laradock/
$ cp env-example .env
#Dockerのwebサーバーの同期ディレクトリ
#ホスト側のディレクトリを指定
APP_CODE_PATH_HOST=../projects/
#Dockerのstorage等を保存
#ホスト側のディレクトリを指定
DATA_PATH_HOST=../.laradock/data
#PHP
PHP_VERSION=7.3
PHP_INTERPRETER=php-fpm
# php-fpm で PostgreSQLドライバをインストール(MySQLはインストールしない)
PHP_FPM_INSTALL_PGSQL=true
PHP_FPM_INSTALL_MYSQLI=false
コンテナー名が自動的にホスト名になるので、docker-compose.yml で明示的にホスト名を設定します。
### PHP-FPM ##############################################
php-fpm:
hostname: laradock //ホスト名を明示的に設定
build:
context: ./php-fpm
必要に応じてコンテナを指定して、ビルド & 起動(初回は時間がかかります)
$ docker-compose up --build -d apache2 postgres
$ docker-compose ps
Name Command State Ports
---------------------------------------------------------------------------------------------------------------
laradock_apache2_1 /opt/docker/bin/entrypoint ... Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
laradock_docker-in-docker_1 dockerd-entrypoint.sh Up 2375/tcp, 2376/tcp
laradock_php-fpm_1 docker-php-entrypoint php-fpm Up 9000/tcp
laradock_postgres_1 docker-entrypoint.sh postgres Up 0.0.0.0:5432->5432/tcp
laradock_workspace_1 /sbin/my_init Up 0.0.0.0:2222->22/tcp
$ docker network list
NETWORK ID NAME DRIVER SCOPE
1f2d988e2454 bridge bridge local
c2ddd125b91c docker-hello_default bridge local
7376c045c61c host host local
48598c23f7d5 laradock_backend bridge local
63cead02763c laradock_default bridge local
1954ef460361 laradock_frontend bridge local
f8f6d3c3cfe3 none null local
$ docker-compose exec workspace bash
# hostname -i
# composer create-project laravel/laravel project-name
指定したPostgreSQL ホスト、ユーザ情報を Laravelプロジェクトの .env を設定
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=blog
DB_USERNAME=default
DB_PASSWORD=secret
複数のLaravelプロジェクトを Dockerで動かしたいので、プロジェクト直下に .htaccess を作成して、server.php に mod_rewrite をします。
<ifmodule mod_rewrite.c="">
<ifmodule mod_negotiation.c="">
Options -MultiViews
</ifmodule>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ ^$1 [N]
RewriteCond %{REQUEST_URI} (\.\w+$) [NC]
RewriteRule ^(.*)$ public/$1
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ server.php
</ifmodule>
localhost にルーティングされているので、ブラウザで アクセスします。
$ docker-compose stop
Dockerでディスク容量を圧迫してしまいがちで、タグのついていない不要なイメージを削除します。
$ docker ps -f "status=exited"
$ docker container prune
$ docker images --filter "dangling=true"
$ docker image prune
ちなみに、v1.2.5 以前では以下のコマンドで削除していました。
$ docker rmi $(docker images -f "dangling=true" -q)
$ docker network ls
$ docker network prune
$ docker system prune