- 投稿日:2020-04-14T23:24:55+09:00
Laravelを知らない中級(中年)プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない!その3「Repositoryクラス」
INDEX
Laravelを知らない中級(中年)プログラマーがマイグレーションファイルの仕組みを調べてみたけど全くわからない!
Repositoryクラス
前回
DatabaseManager
の$this->app['config']['database.default']
ってなんだろうというところまで読みました。それを追ってみましょう。
DatabaseManager
クラスのコンストラクタは以下のように定義されています。Illuminate/Database/DatabaseManager::__construct/** * Create a new database manager instance. * * @param \Illuminate\Contracts\Foundation\Application $app * @param \Illuminate\Database\Connectors\ConnectionFactory $factory * @return void */ public function __construct($app, ConnectionFactory $factory) { $this->app = $app; $this->factory = $factory; $this->reconnector = function ($connection) { $this->reconnect($connection->getName()); }; }
$this->app
には生成される時の第一引数が代入されています。コードヒントにはIlluminate\Contracts\Foundation\Application
とあります。生成する時の手順はまだはっきりとわかりませんが、$this->app
がApplication
インスタンスなのは確かなようです。
前回のコードリーディングでApplication
クラスはContainer
クラスを継承していて、ArrayAccess
インターフェースを実装してることがわかっています。配列としてアクセスすると名前解決され指定されたクラスのインスタンスが渡されます。
エイリアス設定を参照すると、実体はIlluminate\Contracts\Config\Repository
インターフェースを実装したIlluminate\Config\Repository
だということがわかります。見てみましょう!Illuminate/Config/Repository抜粋<?php namespace Illuminate\Config; use ArrayAccess; use Illuminate\Contracts\Config\Repository as ConfigContract; use Illuminate\Support\Arr; class Repository implements ArrayAccess, ConfigContract { /** * All of the configuration items. * * @var array */ protected $items = []; /** * Create a new configuration repository. * * @param array $items * @return void */ public function __construct(array $items = []) { $this->items = $items; } /* ========== 中略 ========== */ /** * Get the specified configuration value. * * @param array|string $key * @param mixed $default * @return mixed */ public function get($key, $default = null) { if (is_array($key)) { return $this->getMany($key); } return Arr::get($this->items, $key, $default); } /* ========== 中略 ========== */ /** * Get a configuration option. * * @param string $key * @return mixed */ public function offsetGet($key) { return $this->get($key); } /* ========== 中略 ========== */ }このクラスも
ArrayAccess
が実装されています。offsetGet()
メソッドはreturn $this->get($key)
と定義されています。配列で渡された時の処理を挟んでいますが、実行するのはreturn Arr::get($this->items, $key, $default)
のようです。Arr
クラスを見てみましょう。Arr::get
Arr
クラスの定義ファイルはIlluminate/Support/Arr.php
です。get()
メソッドは以下のように定義されています。Illuminate/Support/Arr::get()|accessible()/** * Get an item from an array using "dot" notation. * * @param \ArrayAccess|array $array * @param string|int|null $key * @param mixed $default * @return mixed */ public static function get($array, $key, $default = null) { if (! static::accessible($array)) { return value($default); } if (is_null($key)) { return $array; } if (static::exists($array, $key)) { return $array[$key]; } if (strpos($key, '.') === false) { return $array[$key] ?? value($default); } foreach (explode('.', $key) as $segment) { if (static::accessible($array) && static::exists($array, $segment)) { $array = $array[$segment]; } else { return value($default); } } return $array; } /** * Determine whether the given value is array accessible. * * @param mixed $value * @return bool */ public static function accessible($value) { return is_array($value) || $value instanceof ArrayAccess; } /** * Determine if the given key exists in the provided array. * * @param \ArrayAccess|array $array * @param string|int $key * @return bool */ public static function exists($array, $key) { if ($array instanceof ArrayAccess) { return $array->offsetExists($key); } return array_key_exists($key, $array); }説明の通り、「ドット」表記を使用して配列から値を取得するメソッドのようです。第一引数に
ArrayAccess
か配列を、第二引数にキー、第三引数にデフォルト値を受け取ります。受け取った第一引数がArrayAccess
のインスタンスでなく且つ配列でもないものならデフォルト値を戻す、キーがnull
なら第一引数をそのまま戻す。第一引数がArrayAccess
で且つ、その中に第二引数がオフセットとして存在する場合、若しくは第一引数が配列であり且つ第二引数がキーとして存在する場合は第一引数の第二引数オフセット、もしくは第一引数配列のキーが第二引数の値を戻す。第二引数に「.
」が含まれていない場合は、第一引数の第二引数オフセットもしくは第一引数配列にキーが第二引数の値があればそれを、なければ第三引数デフォルト値を戻す。冗長な部分があるきがしますが、気にせず次に行きましょう。ここからがメインです。キーを「
.
」で分割し配列にしforeach()
で回します。 「.
」で区切られた文字列の値で第一引数をネストして、オフセットであるか、配列のキーに存在するかを確認していきます。正当性の確認が取れた場合第一引数から第二引数の「.」記法で指定されたオブジェクト若しくは配列を戻し、正当性が取れない場合はデフォルト値を戻します。 まぁ、平たく言えばドットシンタックスを使ってオブジェクトか配列を階層構造を追って取得する感じでしょうか。 本題に戻りましょう。$this->items
Arr::get()
の挙動は理解できましたがこれが引数として受け取る$array
がまだ追えていません。config
の定数なのではないかと予想はできますが、一応追ってみましょう。Arr::get
を呼び出しているRepository::get()
には、Arr::get($this->items, $key, $default)
とあります。$this->items
はRepository
クラスのコンストラクタでセットされるもののようです。このインスタンスはDatabaseManager
インスタンスの$this->app['config']
で、$this->app
はDatabaseManager::__construct
で$app
がセットされています。つまり Laravelアプリケーション自体で初期化したものであるだろうと推測できます。次回
次回はLarabelアプリケーションの初期設定の流れを追ってみましょう。
- 投稿日:2020-04-14T19:51:53+09:00
Raspberry Pi4セットアップメモ RAMP編
Raspberry Pi4セットアップメモ2
前の手順でうまく行かないので焼き直し
環境
Raspbian GNU/Linux 10.3 (buster)
Install
sudo apt install mariadb-server-10.0
sudo apt install apache2
sudo apt install -y php php-mysql libapache2-mod-php
sudo apt install phpmyadmin
Setup
MariaDB
ユーザーとDB作成
CREATE USER 'foo'@'localhost' IDENTIFIED BY 'bar';
CREATE DATABASE bar;
GRANT ALL PRIVILEGES ON *.* TO 'foo'@'localhost' WITH GRANT OPTION;
Apache
HTTPルートの変更
sudo nano /etc/apache2/apache2.conf
で既存の設定をコメントアウトして適当にルートを追加<Directory /home/pi/Public/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory>
- ファイルの最後に
Include /etc/phpmyadmin/apache.conf
を追加sudo nano /etc/apache2/sites-available/000-default.conf
のディレクトリパスも同様に変更phpMyAdminにアクセス制限をかける
sudo nano /etc/phpmyadmin/apache.conf
<Directory /usr/share/phpmyadmin>
セクションに以下を追加order deny,allow deny from all allow from x.x.x.x # x.x.x.xにはアクセスを許可するIPを記述Apacheを動かすユーザーの変更
sudo nano /etc/apache2/envvars
export APACHE_RUN_USER=www-data
をexport APACHE_RUN_USER=pi
に変えるsudo /etc/init.d/apache2 reload
でここまで反映動作確認
/phpmyadmin
にアクセスして動いてればオッケー
- 投稿日:2020-04-14T16:29:59+09:00
LaravelでDBのレコードを削除する4つの方法
- 投稿日:2020-04-14T14:27:35+09:00
【Laravel】Seederの実行時の「Class UsersTableSeeder does not exist」というエラー
- 投稿日:2020-04-14T12:21:35+09:00
AWS EC2でサーバを構築 4 - Nginx + Let's Ecnrypt + PHP-FPM編 (脆弱性診断「A+」を狙う)
記事一覧
- AWS EC2でサーバを構築 1 - Amazon Linux 2 準備編
- AWS EC2でサーバを構築 2 - 各種インストール編
- AWS EC2でサーバを構築 3 - Apache + Let's Ecnrypt + PHP-FPM編 (Nginxに変更)
- AWS EC2でサーバを構築 4 - Nginx + Let's Ecnrypt + PHP-FPM編 (脆弱性診断「A+」を狙う)
- AWS EC2でサーバを構築 5 - MySQL + phpMyAdmin
- AWS EC2でサーバを構築 6 - PostfixとDovecot (SSL対応とSPAM対策)
- AWS EC2でサーバを構築 7 - セキュリティ対策
概要
Nginx、SSL (Let's Ecnrypt)、PHP-FPMの設定を行います。また脆弱性診断で最高の「A+」を狙います。
Apacheを設定済みの場合は削除するか、
systemctl disable httpd
しておいてください。削除を推奨します。脆弱性診断結果
結論から書いてしまいますが、以下の設定でSSL Labsの脆弱性診断を行ったところ最高の「A+」となりました。
当初は「A」でしたが、設定を見直して「A+」を達成できました。以下の通りの設定で「執筆時点では」「A+」になるはずです。
Nginx
エラー画面でバージョンを吐かないようにし、SSLに関するデフォルト設定を書いておきます。
ここが脆弱性診断「A+」のキモとなります。
注意:以下は
server
ではなくhttp
ディレクティブに追加してください。/etc/nginx/nginx.conf
server_tokens off; ssl_protocols TLSv1.2; ssl_stapling on; ssl_stapling_verify on; ssl_session_cache shared:SSL:50m; ssl_session_timeout 1d; ssl_ciphers HIGH:MEDIUM:!aNULL:!MD5:!SEED:!IDEA:!3DES:!RC4:!DH; ssl_prefer_server_ciphers on; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";Amazon Linux 2のOpenSSLが1.0.2kなので、OpenSSL 1.1.1以降が必要となるTLSv1.3は設定できません。
バーチャルホスト
バーチャルホスト用に公開ディレクトリを作成しておきます。
Apacheとの互換を維持するため、
var/www
で始まりpublic_html
で終わるようにしています。mkdir -p /var/www/html/www.example1.com/public_html mkdir -p /var/www/html/www.example2.com/public_html chown -R nginx. /var/www/html普段は常時SSL設定で良いのですが、Let's Ecnryptの
certbot
が80/tcpへの接続を求めるためreturn
の行はコメントアウトで切り替えて使います。301リダイレクトしている80/tcpのディレクティブにも
root
行があるのはそのためです。また設定ファイル内でドメイン正規化 (wwwありに統一) を行います。Apacheの
.htaccess
が使えないためです。/etc/nginx/conf.d/www.example1.com.conf
server { listen 80; listen 443 ssl http2; server_name example1.com; return 301 https://www.$host$request_uri; ssl_certificate "/etc/letsencrypt/live/example1.com/fullchain.pem"; ssl_certificate_key "/etc/letsencrypt/live/example1.com/privkey.pem"; include /etc/nginx/default.d/*.conf; } server { listen 80; server_name www.example1.com; root /var/www/html/www.example1.com/public_html; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name www.example1.com; root /var/www/html/www.example1.com/public_html; ssl_certificate "/etc/letsencrypt/live/www.example1.com/fullchain.pem"; ssl_certificate_key "/etc/letsencrypt/live/www.example1.com/privkey.pem"; include /etc/nginx/default.d/*.conf; location / { } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } }Aapcheの例と同じく、www.example2.comはBASIC認証を設定します。
生成サービスを利用して暗号化パスワードを生成しておきます。
/var/www/html/www.example2.com/.htpasswd
{ユーザー名}:{暗号化されたパスワード}Apacheと異なり.htaccessが使えないため、設定ファイル内にBASIC認証の設定を記述します。
/etc/nginx/conf.d/www.example2.com.conf
server { listen 80; server_name www.example2.com; root /var/www/html/www.example2.com/public_html; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name www.example2.com; root /var/www/html/www.example2.com/public_html; ssl_certificate "/etc/letsencrypt/live/www.example2.com/fullchain.pem"; ssl_certificate_key "/etc/letsencrypt/live/www.example2.com/privkey.pem"; include /etc/nginx/default.d/*.conf; location / { auth_basic "Enter your ID and Password"; auth_basic_user_file /var/www/html/www.example2.com/.htpasswd; } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } }SSL証明書取得
Let's Ecnryptの証明書を取得します。
certbot certonly --webroot -w /var/www/html/www.example1.com/public_html/ -d example1.com -m root@example1.com certbot certonly --webroot -w /var/www/html/www.example1.com/public_html/ -d www.example1.com -m root@example1.com certbot certonly --webroot -w /var/www/html/www.example2.com/public_html/ -d www.example2.com -m root@example1.com証明書の自動更新
月曜と木曜の4時0分に自動更新されるようにします。
Let's Ecnrypt証明書の期限は3ヶ月です。更新は週に5回までの制限があります。
crontab -e
0 4 * * 1,4 certbot renew --post-hook "systemctl restart nginx php-fpm postfix dovecot"CAAレコード追加
脆弱性診断への対策のため、DNSサーバ (ネームサーバ) でCAAレコードを追加します。
できない場合やわからない場合は読み飛ばしてください。ただし診断結果に「若干」影響する可能性があります。
example1.com. 3600 IN CAA 0 issue "letsencrypt.org"
example2com. 3600 IN CAA 0 issue "letsencrypt.org"
PHP-FPM
まずはPHPそのものの設定を行います。環境によって調整してください。
/etc/php.ini
;expose_php = On expose_php = Off ; max_execution_time = 30 max_execution_time = 120 ; memory_limit = 128M memory_limit = 256M ; post_max_size = 8M post_max_size = 128M ; upload_max_filesize = 2M upload_max_filesize = 128M ;date.timezone = date.timezone = Asia/Tokyo ; session.gc_maxlifetime = 1440 session.gc_maxlifetime = 86400チューニング
実行ユーザー/グループの設定とパフォーマンスチューニングの設定を行います。環境によって調整してください。
/etc/php-fpm.d/www.conf
; user = apache user = nginx ; group = apache group = nginx ;listen.owner = nobody ;listen.group = nobody ;listen.mode = 0660 listen.owner = nginx listen.group = nginx listen.mode = 0660 ; pm.max_children = 50 pm.max_children = 30 ; pm.start_servers = 5 pm.start_servers = 10 ; pm.min_spare_servers = 5 pm.min_spare_servers = 10 ; pm.max_spare_servers = 35 pm.max_spare_servers = 25 ;pm.max_requests = 500 pm.max_requests = 30NginxとPHP-FPMを再起動します。これは設定を変更するたびに行います。
systemctl restart nginx php-fpmComposer
Laravel等で使用するcomposerをインストールしておきます。
curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composerトラブルシューティング
/var/lib/php
内のセッションやキャッシュ用ディレクトリの所有グループがapacheになっている場合の対処法です。chgrp nginx /var/lib/php/opcache chgrp nginx /var/lib/php/session chgrp nginx /var/lib/php/wsdlcacheセッションについては中のファイルが
apache:apache
な状態になっている場合があるので修正。chown nginx. /var/lib/php/sesion/*
- 投稿日:2020-04-14T11:42:46+09:00
AWS EC2でサーバを構築 3 - Apache + Let's Ecnrypt + PHP-FPM編 (Nginxに変更)
記事一覧
- AWS EC2でサーバを構築 1 - Amazon Linux 2 準備編
- AWS EC2でサーバを構築 2 - 各種インストール編
- AWS EC2でサーバを構築 3 - Apache + Let's Ecnrypt + PHP-FPM編 (Nginxに変更)
- AWS EC2でサーバを構築 4 - Nginx + Let's Ecnrypt + PHP-FPM編 (脆弱性診断「A+」を狙う)
- AWS EC2でサーバを構築 5 - MySQL + phpMyAdmin
- AWS EC2でサーバを構築 6 - PostfixとDovecot (SSL対応とSPAM対策)
- AWS EC2でサーバを構築 7 - セキュリティ対策
概要
ウェブサーバをApacheからNginxに変更したので本作業は不要です。
Apacheで構築したい場合を除き、本記事は読み飛ばしてください。Apache (Event MPM) とSSL (Let's Ecnrypt)、PHP-FPMを設定します。
Apahce
管理者メールアドレス等、お決まりの項目は除いて説明します。以下は気をつける部分です。
/etc/httpd/conf/httpd.conf
<Directory "/var/www/html"> ... #Options Indexes FollowSymLinks Options FollowSymLinks ExecCGI MultiViews #AllowOverride None AllowOverride All#AddHandler cgi-script .cgi AddHandler cgi-script .cgiバーチャルホスト
バーチャルホスト用に公開ディレクトリを作成しておきます。
mkdir -p /var/www/html/www.example1.com/public_html mkdir -p /var/www/html/www.example2.com/public_html chown -R apache. /var/www/htmlLet's Ecnryptが80/tcpを使用するため、それを待ち受ける仮設定をしておきます。
/etc/httpd/conf.d/vhosts.conf
<VirtualHost *:80> ServerName www.example1.com ServerAlias example1.com DocumentRoot /var/www/www.example1.com/public_html CustomLog logs/www.example1.com.access_log combined ErrorLog logs/www.example1.com.error_log </VirtualHost> <VirtualHost *:80> ServerName www.example2.com DocumentRoot /var/www/www.example2.com/public_html CustomLog logs/www.example2.com.access_log combined ErrorLog logs/www.example2.com.error_log </VirtualHost>Apacheを起動しておきます。
systemctl start httpdSSL証明書取得
Let's Ecnryptの証明書を取得します。
certbot certonly --webroot -w /var/www/html/www.example1.com/public_html/ -d example1.com -m root@example1.com certbot certonly --webroot -w /var/www/html/www.example1.com/public_html/ -d www.example1.com -m root@example1.com certbot certonly --webroot -w /var/www/html/www.example2.com/public_html/ -d www.example2.com -m root@example2.com証明書の自動更新
月曜と木曜の4時0分に自動更新されるようにします。
Let's Ecnrypt証明書の期限は3ヶ月です。更新は週に5回までの制限があります。
crontab -e
0 4 * * 1,4 certbot renew --post-hook "systemctl restart httpd php-fpm postfix dovecot"バーチャルホスト (SSL対応)
証明書取得後のバーチャルホスト設定を行います。常時SSL化も行います。
ドメイン正規化 (wwwあり/なし) は
.htaccess
で行う想定です。/etc/httpd/conf.d/vhosts.conf
<VirtualHost *:80> ServerName www.example1.com ServerAlias example1.com Redirect permanent / https://www.example1.com/ </VirtualHost> <VirtualHost *:80> ServerName www.example2.com DocumentRoot /var/www/html/www.example2.com/public_html # Redirect permanent / https://www.example2.com/ </VirtualHost> <VirtualHost *:443> ServerName www.example1.com ServerAlias example1.com DocumentRoot /var/www/www.example1.com/public_html CustomLog logs/www.example1.com.access_log combined ErrorLog logs/www.example1.com.error_log SSLEngine on SSLCertificateFile /etc/letsencrypt/live/www.example1.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/www.example1.com/privkey.pem SSLCertificateChainFile /etc/letsencrypt/live/www.example1.com/chain.pem </VirtualHost> <VirtualHost *:443> ServerName www.example2.com DocumentRoot /var/www/html/www.example2.com/public_html CustomLog logs/www.example2.com.access_log combined ErrorLog logs/www.example2.com.error_log SSLEngine on SSLCertificateFile /etc/letsencrypt/live/www.example2.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/www.example2.com/privkey.pem SSLCertificateChainFile /etc/letsencrypt/live/www.example2.com/chain.pem </VirtualHost>CAAレコード追加
脆弱性診断への対策のため、DNSサーバ (ネームサーバ) でCAAレコードを追加します。
できない場合やわからない場合は読み飛ばしてください。ただし診断結果に「若干」影響する可能性があります。
example1.com. 3600 IN CAA 0 issue "letsencrypt.org"
example2com. 3600 IN CAA 0 issue "letsencrypt.org"
MPM変更
ApacheのMPMを古いPreforkからEventに切り替えます。
# LoadModule mpm_prefork_module modules/mod_mpm_prefork.so LoadModule mpm_event_module modules/mod_mpm_event.soチューニング
パフォーマンスチューニング設定です。環境に合わせて調整してください。
/etc/httpd/conf.d/performance.conf
# HostnameLookups off <IfModule mpm_event_module> StartServers 2 MinSpareThreads 2 MaxSpareThreads 12 ThreadsPerChild 2 MaxRequestWorkers 36 MaxRequestsPerChild 0 </IfModule>モジュール版PHP廃止
PHP-FPM (FastCGI) を動かすため、モジュール版を使用しないよう設定します。
/etc/httpd/conf.d/php.conf
# <FilesMatch \.(php|phar)$> # SetHandler application/x-httpd-php # </FilesMatch>BASIC認証
www.example2.com全体をBASIC認証で保護する想定です。
生成サービスを利用してパスワードファイルを作成しておきます。
/var/www/html/www.example2.com/.htpasswd
{ユーザー名}:{暗号化されたパスワード}
.htaccessでBASIC認証を有効にします。
/var/www/html/www.example2.com/public_html/.htaccess
AuthType Basic AuthName "Enter your ID and password" AuthUserFile /var/www/html/www.example2.com/.htpasswd require valid-user
chown -R dev. /var/www/html/www.example2.comPHP-FPM
まずはPHPそのものの設定を行います。環境によって調整してください。
/etc/php.ini
;expose_php = On expose_php = Off ; max_execution_time = 30 max_execution_time = 120 ; memory_limit = 128M memory_limit = 256M ; post_max_size = 8M post_max_size = 128M ; upload_max_filesize = 2M upload_max_filesize = 128M ;date.timezone = date.timezone = Asia/Tokyo ; session.gc_maxlifetime = 1440 session.gc_maxlifetime = 86400チューニング
パフォーマンスチューニングの設定を行います。環境によって調整してください。
/etc/php-fpm.d/www.conf
; pm.max_children = 50 pm.max_children = 30 ; pm.start_servers = 5 pm.start_servers = 10 ; pm.min_spare_servers = 5 pm.min_spare_servers = 10 ; pm.max_spare_servers = 35 pm.max_spare_servers = 25 ;pm.max_requests = 500 pm.max_requests = 30ApacheとPHP-FPMを再起動します。
systemctl restart httpd php-fpmComposer
Laravel等で使用するcomposerをインストールしておきます。
curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer
- 投稿日:2020-04-14T10:59:23+09:00
【PHPで解いてみた】3章 C++入門 AtCoder Programming Guide for beginners (APG4b)
※章を進めるごとに随時更新します。
3.0.1 数値型
キーポイント
- 2つの値を計算する場合は、自動キャストされることがある。
- 方変換は(int)、(string)のような形でする
- PHPのint型の上限はPHP_INT_MAXで確認することが可能です。ちなみに最大はint(9223372036854775807)。
問題
<?php fscanf(STDIN,"%d",$N); $before1 = 2; $before_result = 1; if($N == 1){ echo "1\n"; }else{ $l_now=0; for($i=1;$i<$N;$i++){ $l_now = $before1+$before_result; $before1 = $before_result; $before_result = $l_now; } echo $l_now."\n"; }メモ化再帰を用いた解法
<?php fscanf(STDIN,"%d",$N); $memo = array(); for($i=0;$i<$N;$i++){ $memo[$i] = -1; } function ryuka($n,&$memo){ if($n==1 ){ return 1; } if($n==0){ return 2; } if($memo[$n-1] != -1){ return $memo[$n-1]; } $memo[$n-1] = ryuka($n-1,$memo)+ryuka($n-2,$memo); return $memo[$n-1]; } echo ryuka($N,$memo)."\n";3.0.2 pair/tupleとauto
- autoは型を省略できるものだが、PHPではもともと型の宣言は必要ない。
- 複数の値を表すことができるtupleとpairもPHPにはない。複数の値を表すときにはリストを用いる。
EX22.2つ目の値でソート
<?php fscanf(STDIN,"%d",$N); for($i=0;$i<$N;$i++){ $list = explode(" ",trim(fgets(STDIN))); $tmp1[0] = $list[1]; $tmp1[1] = $list[0]; $pairs_list[] = $tmp1; } sort($pairs_list); var_dump($pairs_list); foreach($pairs_list as $pair){ echo $pair[1]." ".$pair[0]."\n"; } ?>
- 投稿日:2020-04-14T10:16:17+09:00
phpとrsyncでNASのバックアップ
XubuntuとSambaで構築したNASにネットワーク共有用とバックアップ用それぞれ独立したHDDを載せた以下のような環境に対してrsyncでミラーリング及び世代管理バックアップを行う前提で記述したphpスクリプトです。
多少構成が異なっても応用はできるかと思います。NAS用ドライブ
ルート直下に
/data/ … ネットワークドライブ用
というディレクトリを作成。
これをマウントポイント
/home/nas/
にマウントしてあり、
/home/nas/data/
をsambaで共有ディレクトリに設定することでNASのネットワークドライブとして使用。バックアップ用ドライブ
ルート直下に
/data/ … ミラーリング用
/generation/ … 世代管理バックアップ用
というディレクトリを作成。
これをマウントポイント
/home/nas_backup/
にマウントすることで、
/home/nas_backup/data/
を/home/nas/data/のミラーリング先、
/home/nas_backup/generation/
を/home/nas_backup/data/の世代バックアップ先としています。文字ではイメージが掴みにくいかもしれませんが、図にするとこんな感じになります。
注意点としては、世代管理バックアップではハードリンクを活用するrsyncの--link-destオプションを使用しているので、対象ドライブはハードリンクを問題なく扱えるファイルシステムでないと世代管理バックアップのたびに毎回フルバックアップ相当のディスク消費と処理時間がかかると思われます。
私の場合はext4を使用しています。
スクリプト
mirroring.php
NAS共有ディレクトリをバックアップ元として、バックアップ先ドライブに対してrsyncの--deleteオプションを使用したミラーリングを行うスクリプトです。mirroring.php<?php /** * rsync ミラーリング */ // ミラーリング元ディレクトリ define('SOURCE_DIR', '/home/nas/data/'); // ミラーリング先ディレクトリ define('BACKUP_DIR', '/home/nas_backup/data/'); // その他のrsyncオプション 例: '--exclude=/temp/ --exclude=/*.bak'; define('OTHER_OPTIONS', ''); /** * */ set_time_limit(0); date_default_timezone_set('Asia/Tokyo'); // 一時ファイル保存用ディレクトリ define('TEMP_DIR', (file_exists('/dev/shm/') ? '/dev/shm/.' : '/var/tmp/.'). md5(__DIR__)); if(!file_exists(TEMP_DIR)) { mkdir(TEMP_DIR); chmod(TEMP_DIR, 0700); } $tempFile = TEMP_DIR. '/mirroring.tmp'; $temps = getTmpFile($tempFile); // 各ディレクトリ名のデリミタ補正 $sourceDir = preg_replace('|/+$|', '/', SOURCE_DIR. '/'); $backupDir = preg_replace('|/+$|', '/', BACKUP_DIR. '/'); // バックアップ元・バックアップ先が無かったら終了 if(!file_exists($sourceDir) || !preg_match('/:/', $backupDir) && !file_exists($backupDir)) { print "The source '{$sourceDir}' or backup '{$backupDir}' destination directory does not exist.\n"; exit; } // バックアップ元ディスク使用量をチェック、前回から変化が無ければ何もせず終了 // 但しリネームや小サイズの更新ではブロックサイズが変化しない場合もあるので // 前回ミラーリングから1時間以上経過している場合はブロックサイズの変化に関わらずミラーリングを行う exec("df {$sourceDir}", $ret); $usedSize = (preg_split('/\s+/', $ret[1]))[2]; $prevUsedSize = isset($temps['prev_used_size']) ? (time() - filemtime($tempFile) < 3600 ? $temps['prev_used_size'] : 0) : 0; if($usedSize == $prevUsedSize) exit; // ロックファイル名 $lockFilename = TEMP_DIR. '/backup.lock'; // ロックファイルが存在していたら同名のプロセス実行中とみなし終了 if(file_exists($lockFilename)) { print "A process with the same name is running.\n"; exit; } else { // ロックファイル作成 if(!@file_put_contents($lockFilename, 'Process is running.')) { print "Could not create `$lockFilename`.\nSet the permissions of the directory `". TEMP_DIR. "` to 0700.\n"; exit; } chmod($lockFilename, 0600); } // tmpファイルに保存する情報更新 // ミラーリングの場合はバックアップ元の使用ブロック数 $temps['prev_used_size'] = $usedSize; setTmpFile($tempFile, $temps); // rsyncコマンド $command = implode(" ", [ 'rsync -av', '--delete', OTHER_OPTIONS, $sourceDir, $backupDir, ]); print "$command\n"; exec($command); // ロックファイル削除 unlink($lockFilename); exit; /** * */ // tmpファイル取得 function getTmpFile($fn) { if(file_exists($fn)) { $tmp = file_get_contents($fn); return(json_decode($tmp, true)); } return []; } // tmpファイル保存 function setTmpFile($fn, $temps) { if(getTmpFile($fn) != json_encode($temps)) { if(!@file_put_contents($fn, json_encode($temps))) { print "Could not create `$fn`.\nSet the permissions of the directory `". TEMP_DIR. "` to 0700.\n"; exit; } chmod($fn, 0600); } }バックアップ元ディスク容量が前回実行時から変化していなければrsyncは行わず終了するようにしてありますので頻繁に実行しても極端に負荷が高くなることは無いとは思いますが、その辺りは環境に合わせて加減してください。
容量チェックはdfコマンドを使用したものでファイル名の変更や小サイズの変更などブロックサイズの変化しない更新は察知できませんので、前回実行から1時間以上経過していたらバックアップ元ディスク容量が変化していなくてもrsyncを実行するようにしています。主な設定項目
// ミラーリング元ディレクトリ define(‘SOURCE_DIR’, ‘/home/nas/data/’);ミラーリング元となるディレクトリを指定。
// ミラーリング先ディレクトリ define(‘BACKUP_DIR’, ‘/home/nas_backup/data/’);ミラーリング先となるディレクトリを指定。
こちらは先頭に「ユーザー名@ホスト名:」等を含めたリモートの指定も可能です。define(‘BACKUP_DIR’, ‘username@hostname:/home/username/data/’);リモートを指定した場合、cronでの自動実行時にはリモートへのログイン時にパスワード入力待ちが発生しないようパスワード無しでの公開鍵認証ログインができるよう適宜設定しておく必要があります。
generation.php
ミラーリングされたディレクトリを元に、世代管理用ディレクトリに対してrsyncの--link-destオプションを使用したバックアップを行うスクリプトです。
バックアップ用ドライブがNAS本体とは別のリモートにある場合はこのスクリプトもリモート側へ設置します。generation.php<?php /** * rsync 世代バックアップ */ // バックアップ元ディレクトリ define('SOURCE_DIR', '/home/nas_backup/data/'); // バックアップ先ディレクトリ define('BACKUP_DIR', '/home/nas_backup/generation/'); // その他のrsyncオプション 例: '--exclude=/temp/ --exclude=/*.bak'; define('OTHER_OPTIONS', ''); // バックアップ世代数 define('BACKUP_GENERATION', 200); // 古いバックアップを削除するディスク容量閾値(%) // 0の場合はディスク容量のチェックは行いません define('THRESHOLD', 95); /** * */ set_time_limit(0); date_default_timezone_set('Asia/Tokyo'); // 一時ファイル保存用ディレクトリ define('TEMP_DIR', (file_exists('/dev/shm/') ? '/dev/shm/.' : '/var/tmp/.'). md5(__DIR__)); if(!file_exists(TEMP_DIR)) { mkdir(TEMP_DIR); chmod(TEMP_DIR, 0700); } // 各ディレクトリ名のデリミタ補正 $sourceDir = preg_replace('|/+$|', '/', SOURCE_DIR. '/'); $backupDir = preg_replace('|/+$|', '/', BACKUP_DIR. '/'); // バックアップ元・バックアップ先が無かったら終了 if(!file_exists($sourceDir) || !file_exists($backupDir)) { print "The source '{$sourceDir}' or backup '{$backupDir}' destination directory does not exist.\n"; exit; } $nowDate = date('Y-m-d_Hi'); // ロックファイル名 $lockFilename = TEMP_DIR. '/backup.lock'; // ロックファイルが存在していたら同名のプロセス実行中とみなし2分まで待機、その間に開放されなければ終了 $time = time(); while(file_exists($lockFilename)) { sleep(1); if($time + 120 < time()) { print "A process with the same name is running.\n"; exit; } } // ロックファイル作成 if(!@file_put_contents($lockFilename, 'Process is running.')) { print "Could not create `$lockFilename`.\nSet the permissions of the directory `". TEMP_DIR. "` to 0700.\n"; exit; } chmod($lockFilename, 0600); // バックアップ済みディレクトリ名を取得 $backupList = getBackupList($backupDir); // 古いバックアップを間引き $processed = []; foreach($backupList as $backupName) { if(!preg_match('/^(\d{4})-(\d\d)-(\d\d)_(\d\d)(\d\d)/', $backupName, $m) || isset($processed[$backupName])) continue; list($year, $month, $day, $hour, $minute) = array_slice($m, 1); $fDate = "$year-$month-$day $hour:$minute"; // 1か月以上経過しているものはその月の最終のもの以外を削除 if(time() >= strtotime("$fDate +1 month")) { $pickup = []; foreach($backupList as $tmp) { if(substr($tmp, 0, 7) == "{$year}-{$month}" && substr($tmp, 0, 10) <= "{$year}-{$month}-{$day}") $pickup[] = $tmp; } rsort($pickup); foreach(array_slice($pickup, 1) as $tmp) { deleteBackup($backupDir, $tmp, $processed); } } // 1日以上経過しているものはその日の最終のもの以外を削除 elseif(time() >= strtotime("$fDate +1 day")) { $pickup = []; foreach($backupList as $tmp) { if(substr($tmp, 0, 10) == "{$year}-{$month}-{$day}" && $tmp <= $backupName) $pickup[] = $tmp; } rsort($pickup); foreach(array_slice($pickup, 1) as $tmp) { deleteBackup($backupDir, $tmp, $processed); } } } // バックアップ済みディレクトリ名を再取得 $backupList = getBackupList($backupDir); // ディスク使用量が指定割合を下回るまで古いバックアップから削除 sort($backupList); while(THRESHOLD && checkPercentage($backupDir) && count($backupList) > 1) { $command = "rm -rf {$backupDir}{$backupList[0]}"; array_shift($backupList); print "$command\n"; exec($command); } // 既存世代バックアップがある場合 if(count($backupList)) { rsort($backupList); // 保存世代数を超えるバックアップを古いものから削除 if(count($backupList) >= BACKUP_GENERATION) { $delNames = array_slice($backupList, BACKUP_GENERATION -1); foreach($delNames as $del) { $command = "rm -rf {$backupDir}{$del}"; print "$command\n"; exec($command); } } } // 新規バックアップディレクトリ名 $backupName = "{$nowDate}/"; // rsyncコマンド $command = implode(" ", [ "rsync -av", OTHER_OPTIONS, "--link-dest={$sourceDir}", $sourceDir, sprintf("%s%s", $backupDir, $backupName), ]); print "$command\n"; exec($command); // バックアップ済みディレクトリ名を再取得 $backupList = getBackupList($backupDir); // 1世代前のバックアップとの差分でログのみ取得 if(count($backupList) > 1) { rsort($backupList); $command = "rsync -avn --delete --exclude=/_rsync.log {$backupDir}{$backupList[0]}/ {$backupDir}{$backupList[1]}/ > {$backupDir}_rsync.log"; exec($command); exec("mv {$backupDir}_rsync.log {$backupDir}{$backupList[0]}"); } // ロックファイル削除 unlink($lockFilename); exit; /** * */ // 既存バックアップディレクトリ名取得 function getBackupList($backupDir) { $backupList = []; if($dir = opendir($backupDir)) { while($fn = readdir($dir)) { if(preg_match('/^\w{4}-\w{2}-\w{2}_\w{4,6}$/', $fn) && is_dir("{$backupDir}{$fn}")) { $backupList[] = $fn; } } closedir($dir); } return $backupList; } // バックアップ削除 function deleteBackup($backupDir, $str, &$processed) { if(isset($processed[$str])) return; if(file_exists("{$backupDir}{$str}")) { $command = "rm -rf {$backupDir}{$str}"; print"$command\n"; exec($command); $processed[$str] = 1; } } // ディスク使用量チェック function checkPercentage($backupDir) { exec("df {$backupDir}", $ret); if(!isset($ret[1])) return false; if(preg_match('/(\d+)\%/', $ret[1], $ret)) { if($ret[1] >= THRESHOLD) return true; } return false; }実行日時を名前としたディレクトリを作成し、その中にその時点のバックアップを残していきます。
rsyncの--link-destオプションを使うことで新規追加や変化のあったファイルのみが実体として保存され、それ以外のファイルはハードリンクが追加されるだけですので、ディスク容量消費や処理時間は増分バックアップと同程度でありながら作成されるバックアップはそれぞれがフルバックアップ相当になるという特徴があります。
1日以上経過したバックアップはその日の最終版のみ残して削除、1か月以上経過したバックアップはその月の最終版を残して削除、THRESHOLDで指定したディスク使用量に達した場合は下回るまで古いバックアップから削除といった処理もこちらのスクリプトで行なっています。ハードリンクを有効に活用するため--link-destには1世代前のバックアップを指定するのが一般的ですが、今回の場合$sourceDir自身が既にミラーリングされたバックアップの一部なのでこちらを--link-destに指定しています。
こうすることで、容量の節約と同時に処理速度の短縮も図れます。主な設定項目
// バックアップ元ディレクトリ define(‘SOURCE_DIR’, ‘/home/nas_backup/data/’);mirroring.phpでミラーリング先となったディレクトリを指定します。
// バックアップ先ディレクトリ define(‘BACKUP_DIR’, ‘/home/nas_backup/generation/’);世代管理バックアップ保存先を指定します。
このディレクトリの下に
YYYY-MM-DD_HHMM
形式でディレクトリが作成され、その中に各世代のバックアップが保存されていきます。
rsyncの--link-destオプションを使用し変更のないファイルは実体ではなくハードリンクが作成されますので、必要以上にディスク容量を消費しません。// バックアップ世代数 define(‘BACKUP_GENERATION’, 200);保存したい世代数を指定します。
世代バックアップ数がこの値を超えたら古いバックアップから削除されますが、間引き処理やディスク容量による削除処理の兼ね合いで、ここで指定した数に達する前に削除が行なわれる場合もあります。// 古いバックアップを削除するディスク容量閾値(%) // 0の場合はディスク容量のチェックは行いません define(‘THRESHOLD’, 95);dfコマンドでバックアップ先のディスク使用量(%)をチェックし、この値に達していたら値を下回るまで古いバックアップから順に削除を行います。
0では削除処理を行わなくなりますが、バックアップ先の空き容量が足りなくてもrsyncの実行を抑制する等の処理は行いません。
crontab設定例
# rsync mirroring * * * * * php /スクリプト設置パス/mirroring.php &> /dev/null * * * * * sleep 30; php /スクリプト設置パス/mirroring.php &> /dev/null # rsync generation backup 0 */6 * * * php /スクリプト設置パス/generation.php &> /dev/null上記の例では前半ブロックで30秒ごとのミラーリングを、後半ブロックで6時間ごとに世代管理バックアップを行なっています。
- 投稿日:2020-04-14T01:13:42+09:00
Laravel セレクトボックスを実装する
目的
- Laravelでセレクトボックスを実装するときの方法をまとめる
実施環境
- ハードウェア環境
項目 情報 OS macOS Catalina(10.15.3) ハードウェア MacBook Pro (16-inch ,2019) プロセッサ 2.6 GHz 6コアIntel Core i7 メモリ 16 GB 2667 MHz DDR4 グラフィックス AMD Radeon Pro 5300M 4 GB Intel UHD Graphics 630 1536 MB
- ソフトウェア環境
項目 情報 備考 PHP バージョン 7.4.3 Homwbrewを用いて導入 Laravel バージョン 7.0.8 commposerを用いて導入 MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いて導入 実施方法概要
- laravelcollectiveのインストール
- ビューファイルにコードの記載
実施方法詳細
laravelcollectiveのインストール
アプリ名ディレクトリで下記コマンドを実行する。
$ composer require laravelcollective/htmlビューファイルにコードの記載
簡単な解説
先に記載したセレクトボックスのコードをHTMLのselect要素で記載するとどうなるか比較してみる。
Form::select()メソッド
{{Form::select('age', ['Under 18', '19 to 64', 'Over 65'])}}HTMLのselect要素
<select name="age"> <option value="0">Under 18</option> <option value="1">19 to 64</option> <option value="2">Over 65</option> </select>セレクトボックスでのデフォルト表示を変更したい時は下記のようにする。また、記載内容をHTMLのselect要素と比較する。
Form::select()メソッドで「Over 65」をデフォルト表示に設定
{{Form::select('age', ['Under 18', '19 to 64', 'Over 65'], 2)}}HTMLのselect要素
<select name="age"> <option value="0">Under 18</option> <option value="1">19 to 64</option> <option value="2" selected="selected">Over 65</option> </select>セレクトボックスで選択した際の値を任意に決めたい時は下記のようにする。また、記載内容をHTMLのselect要素と比較する。
Form::select()メソッド
{{Form::select('age', ['first_ans' => 'Under 18', 'second_ans' => '19 to 64', 'third_ans' => 'Over 65'])}}HTMLのselect要素
<select name="age"> <option value="first_ans">Under 18</option> <option value="second_ans">19 to 64</option> <option value="third_ans">Over 65</option> </select>実装の例
/add
にてセレクトボックスを表示する。当該ビューファイルに値を送信するボタンを設置し、押下をトリガーとして動作する。/regist
にセレクトボックスでの入力値を飛ばし、リンクするコントローラのアクションで値を受け取る一連の処理を記載する。- コントローラ名はPostControllerとする。
- セレクトボックスを表示するビューファイル名はaddとする。
PostControllerのregistアクションで入力値を取得する。
当該処理に必要なルーティングファイルの内容のみ抜粋して記載する。
アプリ名ディレクトリ/routes/web.phpRoute::get('/add', 'PostController@add'); Route::post('/regist', 'PostController@regist');当該処理に必要なコントローラファイルの内容のみ抜粋して記載する。
アプリ名ディレクトリ/app/Http/Controller/PostController.phpclass PostController extends Controller { public function add_todo() { return view('post.'); } public function regist(Request $request) { $age => $request->age, //DBへのデータ格納処理 //リダイレクト処理 } }当該処理に必要なセレクトボックスを表示するビューファイルの内容のみ抜粋して記載する。
アプリ名ディレクトリ/resources/view/post/add.blade.php<form action="/regist" method="post"> @csrf {{Form::select('age', ['Under 18', '19 to 64', 'Over 65'])}} <input type="submit" value="send"> </form>ビューファイルで「Under 18」を選択して、ブラウザの「send」ボタンをクリックした場合、PostController内のregistアクション内の
$age
には0が格納される。ビューファイルで「19 to 64」を選択して、ブラウザの「send」ボタンをクリックした場合、PostController内のregistアクション内の
$age
には1が格納される。ビューファイルで「Over 65」を選択して、ブラウザの「send」ボタンをクリックした場合、PostController内のregistアクション内の
$age
には2が格納される。