20200414のPHPに関する記事は9件です。

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->appApplicationインスタンスなのは確かなようです。
前回のコードリーディングで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->itemsRepositoryクラスのコンストラクタでセットされるもののようです。このインスタンスはDatabaseManagerインスタンスの$this->app['config']で、$this->appDatabaseManager::__construct$appがセットされています。つまり Laravelアプリケーション自体で初期化したものであるだろうと推測できます。

次回

次回はLarabelアプリケーションの初期設定の流れを追ってみましょう。

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

Raspberry Pi4セットアップメモ RAMP編

Raspberry Pi4セットアップメモ2

前の手順でうまく行かないので焼き直し

環境

  • Raspbian GNU/Linux 10.3 (buster)

Install

  1. sudo apt install mariadb-server-10.0
  2. sudo apt install apache2
  3. sudo apt install -y php php-mysql libapache2-mod-php
  4. sudo apt install phpmyadmin

Setup

MariaDB

ユーザーとDB作成

  1. CREATE USER 'foo'@'localhost' IDENTIFIED BY 'bar';
  2. CREATE DATABASE bar;
  3. GRANT ALL PRIVILEGES ON *.* TO 'foo'@'localhost' WITH GRANT OPTION;

Apache

HTTPルートの変更

  1. sudo nano /etc/apache2/apache2.confで既存の設定をコメントアウトして適当にルートを追加
<Directory /home/pi/Public/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
  1. ファイルの最後にInclude /etc/phpmyadmin/apache.confを追加
  2. sudo nano /etc/apache2/sites-available/000-default.confのディレクトリパスも同様に変更

phpMyAdminにアクセス制限をかける

  1. sudo nano /etc/phpmyadmin/apache.conf
  2. <Directory /usr/share/phpmyadmin>セクションに以下を追加
order deny,allow
deny from all
allow from x.x.x.x # x.x.x.xにはアクセスを許可するIPを記述

Apacheを動かすユーザーの変更

  1. sudo nano /etc/apache2/envvars
  2. export APACHE_RUN_USER=www-dataexport APACHE_RUN_USER=pi に変える
  3. sudo /etc/init.d/apache2 reloadでここまで反映

動作確認

  • /phpmyadminにアクセスして動いてればオッケー
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelでDBのレコードを削除する4つの方法

どれもOKでした!

whereで絞り込んで、delete()で削除

new して削除

$db_data = new Product;
$db_data->where('id', 1)->delete();

new しないで削除

Product::where('id', 1)->delete();

idで一意に絞り込んで、destroy()で削除

new して削除

$db_data = new Product;
$db_data->destroy(1);

new しないで削除

Product::destroy(1);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】Seederの実行時の「Class UsersTableSeeder does not exist」というエラー

状況

既存のSeederの名前を変更してから実行した際にエラーがこんなエラーが...

 ReflectionException  : Class UsersTableSeeder does not exist

解決策

下記を実行してからシーディングをするとうまくいきました。

$ composer dump-autoload

そもそもLaravelではクラスの参照はComposerを利用して行います。したがってクラスの変更があった場合はcomposer dump-autoloadのコマンドで更新します。

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

AWS EC2でサーバを構築 4 - Nginx + Let's Ecnrypt + PHP-FPM編 (脆弱性診断「A+」を狙う)

記事一覧

概要

Nginx、SSL (Let's Ecnrypt)、PHP-FPMの設定を行います。また脆弱性診断で最高の「A+」を狙います。

Apacheを設定済みの場合は削除するか、systemctl disable httpdしておいてください。削除を推奨します。

脆弱性診断結果

結論から書いてしまいますが、以下の設定でSSL Labsの脆弱性診断を行ったところ最高の「A+」となりました。

20200413_131134.png

当初は「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 = 30

NginxとPHP-FPMを再起動します。これは設定を変更するたびに行います。

systemctl restart nginx php-fpm

Composer

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/*
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS EC2でサーバを構築 3 - Apache + Let's Ecnrypt + PHP-FPM編 (Nginxに変更)

記事一覧

概要

ウェブサーバを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/html

Let'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 httpd

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@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.com

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

; 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 = 30

ApacheとPHP-FPMを再起動します。

systemctl restart httpd php-fpm

Composer

Laravel等で使用するcomposerをインストールしておきます。

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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";
    }


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

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/の世代バックアップ先としています。

文字ではイメージが掴みにくいかもしれませんが、図にするとこんな感じになります。
blockimage.jpg

注意点としては、世代管理バックアップではハードリンクを活用する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時間ごとに世代管理バックアップを行なっています。

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

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を用いて導入

実施方法概要

  1. laravelcollectiveのインストール
  2. ビューファイルにコードの記載

実施方法詳細

  1. laravelcollectiveのインストール

    1. アプリ名ディレクトリで下記コマンドを実行する。

      $ composer require laravelcollective/html
      
  2. ビューファイルにコードの記載

    1. セレクトボックスを表示したいビューファイルを開く。
    2. 下記のコードを記載する。

      {{Form::select('age', ['Under 18', '19 to 64', 'Over 65'])}}
      
    3. ローカルサーバを起動し、下記のように表示される事を確認する。

      • 通常時

        スクリーンショット 2020-04-13 17.42.17.png

      • セレクトボックスクリック時

        スクリーンショット 2020-04-13 17.42.24.png

簡単な解説

  1. 先に記載したセレクトボックスのコードを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>
      
  2. セレクトボックスでのデフォルト表示を変更したい時は下記のようにする。また、記載内容を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>
      
  3. セレクトボックスで選択した際の値を任意に決めたい時は下記のようにする。また、記載内容を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.php
    Route::get('/add', 'PostController@add');
    Route::post('/regist', 'PostController@regist');
    
  • 当該処理に必要なコントローラファイルの内容のみ抜粋して記載する。

    アプリ名ディレクトリ/app/Http/Controller/PostController.php
    class 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が格納される。

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