20200630のPHPに関する記事は22件です。

artisanコマンドの自作で引数付きのSeederを作成する

あらすじ

Laravelにはseederがありテスト時などにサンプルデータを気軽に作ることができます。
ただartisanコマンドを用いて呼び出すのですが、この時にshellscriptのように作成するデータ数などを引数として与えられたらなあ...と思っていました。
そんなところartisanのコマンド自作で行けそうといアドバイスをいただいたので、コマンド作成の練習もかねて作成してみることにしました。

通常のSeeder

今回は例として、Laravelのプロジェクトに最初からついてくるUserテーブルのSeederをもとに作っていきます。似た構造のレコードを複数追加するのでFactoryを記述して(されていて)、

Userfactory.php
<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\User;
use Illuminate\Support\Str;
use Faker\Generator as Faker;

$factory->define(User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
});

これをSeederから呼び出します。
factoryの第二引数を変えることで生成するレコードの数を変えられるので、ここにartisanコマンドで受け取った引数を渡すことにします。

UsersTableSeeder.php
<?php

use Illuminate\Database\Seeder;

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(User::class, 10)->create();
    }
}

artisanコマンドで引数を使用する

引数の扱いは、
https://qiita.com/nenokido2000/items/abbf70c87c9ad86a2b89
などを参考にしました。

まずはターミナルから雛形を生成します。

$ php artisan make:command BulkUserSeed

作成されたファイルを変更して、
・呼び出すためのコマンド
・引数
・呼ばれた際の処理
を記述していきます。
雛形そのままで動く部分は省略してあります。

BulkUserSeed.php
<?php

namespace App\Console\Commands;

use App\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Schema;

class BulkUserSeed extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:BulkUserSeed {number=10 : number of users}';

    // 〜〜(中略)〜〜

    public function handle()
    {
        print("Start seeding.\n");
        $numberOfUsers = (int)$this->argument("number");
        factory(User::class, $numberOfUsers)->create();
        print("Seeding complete.\n");
    }
}

また、オプションとしての引数も与えられるので、今回は試しにmigrate:refreshのようにテーブルをまず全消去するかを選べるようにしたいと思います。

BulkUserSeed.php
// ... ...
protected $signature = 'command:BulkUserSeed
    {number=10 : number of users}
    {--refresh : drop all existing users}';

// ... ...

public function handle()
    {   
        $refresh = $this->option("refresh");
        if ($refresh){    
            print("Are you sure you want to delete all users? [Press y] \n");
            $input = trim(fgets(STDIN));
            if ($input === 'y') {
                print("User table truncated.\n");
                User::truncate();
            } else {
                print("Seeding canceled.\n");
                return;
        }
        print("Start seeding.\n");
        $numberOfUsers = (int)$this->argument("number");
        factory(User::class, $numberOfUsers)->create();
        print("Seeding complete.\n");
    }
}

ログなどはお好みで。
これで

> php artisan command:BulkUserSeed 
> php artisan command:BulkUserSeed 100
> php artisan command:BulkUserSeed 100 --refresh

などのコマンドが選択できるようになりました。

おまけ 連番でデータを作成する

実は元々作っていたプロジェクトでは(と言っても練習用の仮想の物ですが)、日にちごとに担当者を決めて管理するという機能を付けようとしていました。そこで用いるテーブルには以下のようなものがあります。
スクリーンショット 2020-06-30 20.52.07.png

例えば1月1日から12月31日までの仮データを一気に作りたい!と思った時は、先ほどと同じ方法ではうまくいかず、dateの値を加算しながら与える必要があります。

1日ずつずれた日付を与えるにはfor文を回しながら
・基準となる日にちを決め、+i日加算した日付を与える
・ループごとに変数を1日ずつインクリメントする
などで達成できます。下のコードでは後者を用いています。

なお、+1 monthなど月単位で加算する場合、月末の日付のズレなどに気を使う必要があるようです。
(参考: https://www.p-nt.com/technicblog/archives/11 など)

BulkAssignmentSeed.php
<?php

namespace App\Console\Commands;

use App\Assignment;
use Illuminate\Console\Command;

class BulkAssignmentSeed extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:BulkAssignmentSeed
    {number=10 : number of assignments}
    {first_date=1970-01-01 : the date of first row}
    {--refresh : drop all existing assignments}';
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Seeding arbitrary number of assignments.';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $refresh = $this->option("refresh");
        if ($refresh){    
            print("Are you sure you want to delete all Assignments? [Press y] \n");
            $input = trim(fgets(STDIN));
            if ($input === 'y') {
                print("Assignment table truncated.\n");
                Assignment::truncate();
            } else {
                print("Seeding canceled.\n");
                return;
            }
        }

        print("Start seeding.\n");
        $numberOfAssignments = (int)$this->argument("number");
        $base = $this->argument("first_date");
        for($i = 0; $i < $numberOfAssignments; $i++){ 
            // Log::debug($baseDate);
            $date = date("Y-m-d", strtotime($base . "+($i) day"));
            factory(Assignment::class)->create([
                'date' => $date,
            ]);

        }

        print("Seeding complete.\n");
    }
}

AssignmentFactory.php
<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Assignment;
use Faker\Generator as Faker;

$factory->define(Assignment::class, function (Faker $faker) {
    return [
        'staff' => $faker->name,
    ];
});

終わりに

コマンド拡張の楽しさを知ったので、これからも色々やってみたいと思います。

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

nikic/PHP-Parser を使ってPHPコードをパースして出来る事とサンプル

nikic/PHP-Parser

https://github.com/nikic/PHP-Parser
PHPをASTとしてパースし、サクッとコード変換ツールを作成するのに便利なライブラリの紹介とサンプルです

対象読者

  • PHPのコードをツールで機械的に安全に一斉変換したい
  • preg_match/preg_replaceだと、不安が残る
  • Rectorが動かなかった
  • PHP-Parserで変換処理を書こうと思ってる矢先の人

といういるのかいないのか分からない超限定的な状況の方 (自分)

書くこと

  • 使おうと思った経緯
  • 変換したい内容と、実現するコードのサンプル

書かないこと

  • 導入方法や、機能の紹介
    • 懇切丁寧なドキュメントがあるので、そちらを参考にして下さい。

経緯

CakePHP1.3からCakePHP3系にバージョンアップをしたい中で、Rectorという便利なツールを使いたかったのですが、そちらが変換対象コードの名前空間によるautoloadが前提になっていたので、CakePHP1.3のrequire全盛のコードをすべてuseに書き直してしまおうという所が発端です。ついでに、実験的にいくつかの変換を行ってみました。

なお、記載のコードはプロダクションコードではなく、私が業務の合間に裏でコソコソ実験しているコードなので、改善の余地あり&改善提案求むなものですのでご了承下さい。

実行

  • 特に、実行コマンドがあるわけではなく、自分でphp書いてそれを実行するだけです
  • サンプルに run.php として載せます

サンプル

変換要件

  • requireをuseに変換する
    • requireのパスを解析して、useにする
    • c_**.phpC**Component に書き換える
    • DSという定数は/と解釈し、\に変換する
    • APP/CAKEをApp/Cakeに変える
  • $this->request->dataへの代入をやめ、withDataメソッドを使うように変更する
    • 配列のキーが変数の場合も、いい感じにする
  • 特定のディレクトリは変換対象外とする

言葉では説明しにくいのでdiffです。
対象コードでは以下の様な変換をかける必要があったので、それを満たす様に実装しました。

 <?php

-require_once APP . 'path/to/app.php';
-require_once APP . 'path' . DS . 'to' . DS . 'func.php';
-require_once APP . 'controllers' . DS . 'components' . DS . 'c_app.php';
-require_once CAKE . 'tests' . DS . 'lib' . DS . 'test_manager.php';
+use App\Path\To\App;
+use App\Path\To\Func;
+use App\Controller\Component\CAppComponent;
+use Cake\Test\Lib\TestManager;

 class TestController
 {
     public function __construct()
     {
-        $this->request->data['url'] = array('data' => 'value');
+        $this->request = $this->request->withData("url", array('data' => 'value'));
         $pass = 'pass';
-        $this->request->data[$pass]['sub'] = array('data' => 'value');
+        $this->request = $this->request->withData("{$pass}.sub", array('data' => 'value'));
     }
 }

ディレクトリ構成

.
├── PHP-Parser
│   ├── その他のコード(git cloneなりcomposer installなり)
│   ├── composer.json
│   ├── scripts
│   │   └── run.php
│   └── visitors
│       ├── Request.php
│       └── RequireToUses.php
└── test
    └── app
        └── contrllers
            └── test_controllers.php

実行するPHPを作る

実行は、php run.phpとするだけなので、まずそれを作ります。

run.php
<?php
$dir = dirname(__FILE__);
require $dir . '/../vendor/autoload.php';

use PhpParser\{
    ParserFactory,
    Parser,
    PrettyPrinter,
    NodeTraverser,
    NodeVisitor,
    NodeDumper,
    NodeVisitor\NameResolver,
    Lexer
};

// 変換の定義。これをforeachする方がコード重複がなくて済むので適当に実装した
$configs = [
   [
       'target' => 'app/controllers',
       'exclude' => ['.git', 'vendors', 'plugins'],
       'visitors' => [
           'Visitor\RequireToUses',
           'Visitor\Request',
       ]
    ],
    // [
    //     'target' => 'app/controllers/components',
    //     'exclude' => ['.git', 'vendors', 'plugins'],
    //     'visitors' => [
    //         'Visitor\SomeVisitor'
    //     ]
    // ]
];

try {
    $dir = dirname(__FILE__) . '/../../test/';

    $printer = new PrettyPrinter\Standard;

    $nodeDumper = new NodeDumper;

    // READMEのPrettyPrinterを使うと、空行の削除など余計な整形がかかってしまうので、対応策が提示されており
    // そちら(formatting-preserving-pretty-printing)の書き方を参考にしています
    // @see https://github.com/nikic/PHP-Parser/blob/master/doc/component/Pretty_printing.markdown#formatting-preserving-pretty-printing
    $lexer = new Lexer\Emulative([
        'usedAttributes' => [
            'comments',
            'startLine', 'endLine',
            'startTokenPos', 'endTokenPos',
        ],
    ]);

    $parser = (new ParserFactory)->create(
        ParserFactory::PREFER_PHP7,
        $lexer
    );

    $traverser = new NodeTraverser;
    $traverser->addVisitor(new NodeVisitor\CloningVisitor());

    foreach ($configs as $config) {
        $target = $dir . $config['target'];
        $exclude = $config['exclude'] ?? [];

        // $config['exclude']に記載したディレクトリを変換対象外にするフィルタを定義します
        $filter = function ($file, $key, $iterator) use ($exclude) {
            if ($iterator->hasChildren() && !in_array($file->getFilename(), $exclude)) {
                return true;
            }
            return $file->isFile();
        };
        $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($target));
        $files = new \RecursiveIteratorIterator(
            new RecursiveCallbackFilterIterator(
                new RecursiveDirectoryIterator(
                    $target,
                    RecursiveDirectoryIterator::SKIP_DOTS),
                $filter
            )
        );
        // .php なファイルだけすべて取得します
        $files = new \RegexIterator($files, '/.php$/');

        // $config['visitors']に記載したVisitorを追加します
        foreach ($config['visitors'] as $visitor) {
            $traverser->addVisitor(new $visitor());
        }

        // 先程取得したファイルをぐるぐるします
        foreach ($files as $file) {
            try {
                echo $file . PHP_EOL;

                // 変換元コードの取得
                $code = file_get_contents($file->getPathName());

                // こちらもformatting-preserving-pretty-printingの書き方です
                $oldStmts = $parser->parse($code);
                $oldTokens = $lexer->getTokens();

                // パース済みの元ASTをVisitorが走査して変換したASTを生成します
                $newStmts = $traverser->traverse($oldStmts);
                // AST → PHPのコード
                $newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens);

                ##### Debug #####
                //echo $nodeDumper->dump($oldStmts);
                //echo $nodeDumper->dump($newStmts);
                // pretty print
                //echo $newCode;
                // write the converted file to the target directory
                #################

                // 変換した内容で、ファイルの上書き
                file_put_contents(
                    $file->getPathname(),
                    // RequireToUseで末尾に use〜;;とコロンが2つついてしまうので、修正。(不本意)
                    str_replace(';;', ';', $newCode)
                );
            } catch (PhpParser\Error $e) {
                echo 'Parse Error: ', $e->getMessage();
            }
        }
    }
} catch (Exception $e) {
    echo $e->getMessage();
    echo $e->getTraceAsString();
}

Visitor

肝心なのはこちらで、ASTをいい感じに書き換えて行く必要があります。

requireをuseに変えるVisitor

RequireToUses.php
<?php
namespace Visitor;

use PhpParser\{
    Node,
    NodeFinder,
    NodeDumper,
    NodeVisitorAbstract,
    BuilderFactory
};
use Doctrine\Inflector\InflectorFactory;

class RequireToUses extends NodeVisitorAbstract
{
    public function leaveNode(Node $node)
    {
        $factory = new BuilderFactory;
        $inflector = InflectorFactory::create()->build();

        if ($node instanceof Node\Expr\Include_) {
            $nodeDumper = new NodeDumper;

            $target = [];
            // require_once 'path/to/file.php'
            if ($node->type === Node\Expr\Include_::TYPE_REQUIRE_ONCE || $node->type === Node\Expr\Include_::TYPE_REQUIRE) {
                if ($node->expr instanceof Node\Scalar\String_) {
                    $target[] =  $node->expr->value;
                } else {
                    $target = $this->getTargetRecursive($node->expr);
                }
            }

            $target = $this->formatTarget($target);

            $namespaces = [];
            foreach ($target as $t) {
                $t = $this->handleComponentClass($t);
                $t = $this->removeExtension($t);
                $t = $inflector->singularize($t);
                $t = $inflector->classify($t);
                $namespaces[] = $t;
            }
            $use = implode('\\', $namespaces);

            $newNode = new Node\Stmt\Use_([
                    new Node\Stmt\UseUse(new Node\Name($use))
                ],
                Node\Stmt\Use_::TYPE_UNKNOWN
            );
            // 頻繁に確認するので、残す
            // echo $nodeDumper->dump($node);
            // echo $nodeDumper->dump($newNode);

            return $newNode;
        }

        return $node;
    }

    public function getTargetRecursive($expr, $target = [])
    {
        if ($expr instanceof Node\Expr\BinaryOp\Concat) {
            if ($expr->left instanceof  Node\Expr\BinaryOp\Concat) {
                if ($expr->right instanceof  Node\Expr\ConstFetch) {
                    $target[] = $expr->right->name->toString();
                } else if ($expr->right instanceof  Node\Scalar\String_) {
                    $target[] =  $expr->right->value;
                }
                return $this->getTargetRecursive($expr->left, $target);
            }
            $target[] = $expr->right->value;
            $target[] = $expr->left->name->toString();
            return $target;
        }

        return $target;
    }

    private function formatTarget($targetArray){
        $targetArray = array_filter($targetArray, function($var){
            return $var !== 'DS';
        });

        $targetArray = array_reverse($targetArray);

        $newArray = [];
        foreach ($targetArray as $var) {
            $parsed = explode('/', $var);
            if (count($parsed) >= 2) {
                foreach ($parsed as $p) {
                    $newArray[] = $p;
                }
            } else {
                $newArray[] = $var;
            }
        }

        $newArray = array_map(function ($var) {
            if ($var == 'APP') {
                return 'App';
            } elseif ($var == 'CAKE') {
                return 'Cake';
            } else {
                return $var;
            }
        }, $newArray);

        return $newArray;
    }

    private function removeExtension($str)
    {
        return (str_replace('.php', '', $str));
    }

    private function handleComponentClass($str)
    {
        if (preg_match('/(?<filename>c_[a-z]+)\.php/', $str, $m)){
            if (isset($m['filename'])) {
                $str = $m['filename'] . '_components';
            }
        }
        return $str;
    }
}

$this->request->dataを処理するVisitor

Request.php
<?php
namespace Visitor;

use PhpParser\{
    PrettyPrinter,
    Node,
    NodeFinder,
    NodeDumper,
    NodeVisitorAbstract,
    BuilderFactory
};
use Doctrine\Inflector\InflectorFactory;

/**
 * Class RequireToUses
 * @package Visitor
 */
class Request extends NodeVisitorAbstract
{
    public function leaveNode(Node $node)
    {
        $factory = new BuilderFactory;
        $nodeFinder = new NodeFinder;
        $nodeDumper = new NodeDumper;
        $inflector = InflectorFactory::create()->build();
        $newNode = $node;
        if ($node instanceof Node\Expr\Assign) {
            $leftNode = $node->var;
            $subNode = $nodeFinder->findFirstInstanceOf($leftNode, 'PhpParser\Node\Expr\PropertyFetch');

            $_this = $subNode->var->var->name ?? '';
            $_request = $subNode->var->name->name ?? '';
            $_data = $subNode->name->name ?? '';
            if ($_this === 'this' && $_request === 'request' && $_data === 'data') {

                // $this->request->data['Baz']['bar'] → 'Baz.bar'に。
                $dimNodes = $nodeFinder->findInstanceOf($leftNode, 'PhpParser\Node\Expr\ArrayDimFetch');
                if (!empty($dimNodes)) {
                    $dims = [];
                    foreach ($dimNodes as $dimNode) {
                        $dims[] = $dimNode->dim;
                    }
                    $dims = array_reverse($dims);

                    $dimConcat = '';
                    foreach ($dims as $dim) {
                        if ($dim instanceof Node\Scalar\String_) {
                            $dimConcat .= $dim->value;
                            $dimConcat .= '.';
                        } elseif ($dim instanceof Node\Expr\Variable) {
                            // $this->request->data['Baz'][$bar] のケースは "Baz.{$bar}" とする
                            $dimConcat .= '{$' . $dim->name . '}';
                            $dimConcat .= '.';
                        }
                    }
                    $dimConcat = rtrim($dimConcat, '.');
                    $dimConcatNode = new Node\Scalar\String_(
                        $dimConcat,
                        [
                            'kind' => Node\Scalar\String_::KIND_DOUBLE_QUOTED
                        ]
                    );

                    $methodCall = $factory->methodCall(
                        $factory->propertyFetch(new Node\Expr\Variable('this'), 'request'),
                        'withData',
                        [$dimConcatNode,
                            $node->expr
                        ]
                    );

                }

                $newNode =  new Node\Expr\Assign(
                   $factory->propertyFetch(new Node\Expr\Variable('this'), 'request'),
                   $methodCall
                );
            }
        }

        return $newNode;
    }
}

実行前準備

変換対象のコードを置く

test_controller.php
<?php

require_once APP . 'path/to/app.php';
require_once APP . 'path' . DS . 'to' . DS . 'func.php';
require_once APP . 'controllers' . DS . 'components' . DS . 'c_app.php';
require_once CAKE . 'tests' . DS . 'lib' . DS . 'test_manager.php';

class TestController
{
    public function __construct()
    {
        $this->request->data['url'] = array('data' => 'value');
        $pass = 'pass';
        $this->request->data[$pass]['sub'] = array('data' => 'value');
    }

    public function dummy()
    {
        echo "__constructとの間の空行が削除されないこのと確認";
    }
}

composerに名前空間を追加する

     },
     "autoload-dev": {
         "psr-4": {
-            "PhpParser\\": "test/PhpParser/"
+            "PhpParser\\": "test/PhpParser/",
+            "Visitor\\": "visitors/"
         }
     },
// 忘れずに実施
$ composer dump-autoload

大文字小文字/複数単数変換できるライブラリを追加

$ composer require doctrine/inflector

禁断のライブラリのコアコード修正

  1. ExpressionをStatementに変換は、本来は駄目らしいですがなんとしてもやりたいので書き換えます(自己責任)
    • Trying to replace expression (Expr_Include) with statement (Stmt_Use) がでました
    • 以下の修正を加えました
lib/PhpParser/NodeTraverser.php
         }

         if ($old instanceof Node\Expr && $new instanceof Node\Stmt) {
+            if ($old instanceof Node\Expr\Include_ && $new instanceof Node\Stmt\Use_) {
+                return;
+            }
             throw new \LogicException(
                 "Trying to replace expression ({$old->getType()}) " .
                 "with statement ({$new->getType()})"
  1. "{$pass}.sub" がエスケープされて "{\$pass}.sub" になるので書き換えます(自己責任)
lib/PhpParser/PrettyPrinter/Standard.php
     protected function escapeString($string, $quote) {
         if (null === $quote) {
             // For doc strings, don't escape newlines
-            $escaped = addcslashes($string, "\t\f\v$\\");
+            $escaped = addcslashes($string, "\t\f\v\\");
         } else {
-            $escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\");
+            $escaped = addcslashes($string, "\n\r\t\f\v" . $quote . "\\");
         }

         // Escape other control characters

コツ

  • dumpしたASTの読み方
    • ASTを実装したクラスを見て、どの様なプロパティやメソッドが定義されているかを観察する
    • Gihubのfile検索で、Expr_Assign なら ExprAssign って検索すると、ヒットする
  • 使える機能の知り方
    • やはり、静的解析と候補を自動で提示してくれるIDEなどでないとかなり辛い
    • 私はIntelliJ IDEA(phpstorm)だが、VSCodeのIntelliSenseでは力不足を感じた...(使い方知らないだけ?) スクリーンショット 2020-06-30 23.13.26.png

感想

ツールは便利だけど、泥臭い変換はどのみち大変
Requestの処理はおそらくRector(PHP-Parserの上位互換)で実装したほうが楽だと思います。
Rectorの実装例もいつか書きます。

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

【Laravel】laravel new のあとにやること

環境

  • macOS
  • PHP7.4
  • Laravel7.x
  • SQLite3

やること

新しくアプリケーションを作成する時、laravel new [アプリ名]を実行した後にすることをまとめました
あくまで筆者の場合なので、参考程度に読んでみてください

  • git周辺
  • アプリ名、タイムゾーン、言語の設定
  • データベースの設定
  • メールの設定
  • Auth機能の導入
  • 日本語化

git周辺

はじめに、BitbucketやGitHubのリポジトリを作っておきます
以下のコマンドを実行して、リポジトリの初期化、BitbucketやGitHubへの接続を行います
ちなみに筆者は執筆現在Bitbucketを使っています

# LaravelAppディレクトリに移動
% git init
% git add -A
% git commit -m "Initialize repository"
% git remote add origin [BitbucketやGitHubのリポジトリ名]
% git push -u origin master

アプリ名、タイムゾーン、言語の設定

  • .envファイルのAPP_NAME
  • config/app.phpのname、timezone、local

を設定します

.env
APP_NAME=アプリ名
config/app.php
'name' => env('APP_NAME', 'アプリ名'),
...
'timezone' => 'Asia/Tokyo',
...
'locale' => 'ja',

データベースの設定

今回はSQLiteを使います

  • databaseフォルダにdatabase.sqliteを作成
  • config/database.phpのdefaultを設定
  • .envファイルのDB_変数を設定
% touch database/database.sqlite
config/database.php
'default' => env('DB_CONNECTION', 'sqlite'),
.env
DB_CONNECTION=sqlite

.envにデフォルトで設定されている他のDB_変数は削除します

メールの設定

今回はGメールを使うので、あらかじめGメール側でアプリパスワード(16桁)を生成しておきます
アプリパスワードは基本メモを取ったり他人と共有しませんが、環境変数に設定するので覚えておきましょう

  • config/mail.phpのdefault
  • .envファイルのMAIL_変数

を設定します

config/mail.php
'default' => env('MAIL_MAILER', 'smtp'),
env
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=Gmailアドレス
MAIL_PASSWORD=アプリパスワード(16桁)
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=Gmailアドレス
MAIL_FROM_NAME="${APP_NAME}"

Auth機能の導入

以下のコマンドを実行します
※Laravel5以下はコマンドが異なります

// laravel/uiをインストール
% composer require laravel/ui

// Auth関連ファイル(基本的なスキャフォールド)を生成
% php artisan ui vue --auth

// フロントエンドに必要なパッケージをインストール&必要なファイルをコンパイル・ビルド
% npm install && npm run dev

日本語化

Auth機能を導入したときに自動生成されるファイルがあります
フォームやバリデーションやメールで日本語で表示されるように変更します
日本語化は以下のURLのブログを見たほうがいいです(丸投げ)

https://blog.capilano-fw.com/?p=289#i-3

おわり

現在ポートフォリオ作成中で同時に記録として記事を書いていこうと思います

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

Laravel 1対多のリレーション先の項目の集計値(最小値, 最大値, 平均値, 合計値 など)で親テーブルを並べ替えたい

テーブルの例

たとえば、商品基本情報と詳細が1対多の関係で、データベースを構成したとします。
一覧表示する時に、価格順で並び替えたい時はどんな感じにクエリビルダーを使えばいいか、いろいろ試してこれかなぁというのを投降します。

【商品情報テーブル (products)】
id 商品ID
name 商品名

【商品詳細テーブル (detail)】
id 商品詳細ID
productID 商品詳細ID
size サイズ
price 価格
visible 表示・非表示

コードの例

$aggregates = DB::table('detail')
    ->select('productID',
    DB::raw('MIN(price) as min_price'),
    DB::raw('MAX(price) as max_price')
)
    ->where('visible', 1)
    ->groupBy('itemID');

$db = DB::table('products')
    ->joinSub($aggregates, 'aggregates', function ($join) {
        $join->on('products.id', '=', 'aggregates.itemID');
    })
    ->orderBy('min_price01', 'desc')
    ->get();

これなら、並び替えできます。ここ参考にしました。
https://readouble.com/laravel/6.x/ja/queries.html
最初は、joinでリレーションを作って、groupByでまとめ、価格の最大・最小をselectに入れて、を並び替えてみようとしたのが、できなかったので、こうなりました。

もっと上手な書き方があれば、こんな方法はどうだい?ってコメントいただけると、ありがたいです。
よろしくお願いいたします。

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

Rust は流行りそうもないので、Go を使う。

最近、暇で以下のサービスを作った。
https://deau-project.herokuapp.com/

バックエンドには仕事で使ったことがない、Go にした。
勉強してたのは、Rust で、仕事のCSVを分析するシステムを Rust で作ったけど、
この先、使うことがないのでは? と思っている。

以前、Haskell が流行りそうで、だいぶ勉強したけど、今では話題にすらならない。
関数型も、いまいち浸透していない。

流行る言語は、何かしらの手間を無くす。
Ruby が流行ったのは、Rails がフレームワーク使用時の共通作業を無くしたから。
PHP が未だに廃れないのは、WordPress が共通作業を無くしてるから。

Go は流行る。もう流行っている?
理由は学習コストを減らしたから。

今回、Go Gin を使用した、そして、簡単に実装できた。
情報も充分にあった。

Rust でも、情報に関しては充分にある。
ただ、実装時に面倒だなと感じることが多い。
私のように趣味で実装するなら、尚更、面倒に感じる。

Rust が流行るには、組込み系にターゲットを絞るか、
当時の Rails のような手間を省く何かが必要だと思う。
組込み系で考えれば、C++でよくあるミスを防げる。確認の手間を省ける。

今回、サーバーサイドに Go を使用したが、この先はどうかわからない。
趣味でならいいが、仕事で使うとなると、関数型である必要がある。
もし、今から新しいシステムを作るとして、サーバーサイドに何を使用すればいいのか。

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

【Laravel】任意のExceptionをthrowする。

メモとして残します。

■やり方

throw new \Exception("エラーメッセージ");

<?php

try{
    //処理
    if(!$hoge){
        throw new \Exception("testです");
    }
} catch (\Exception $e) {
    echo $e->getMessage();// testです
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

safari(ios)でMediaRecorderを使う

前提

使用デバイス等の説明

機種

iPad 13.5.1
safari

実現すること

MediaRecorderを使おうと調べたらSafari未対応という記事しかない
が、iosでMediarecorderを使うことができるっぽいことを見つけたので
まとめておく

方法

方法としては非常に簡単である

①設定を開く
②Safariの項目を開く
③詳細の項目を開く
④Experimental Featureを開く
⑤MediaRecorderをオンにする

試用機能なのかな?詳しくはわからないが、これで使えるはずです。

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

php

個人学習用にメモ

標準入力の値を、foreach文で表示する。
※あとでまとめる用

$input_line = fgets(STDIN);
    while($input_line = trim(fgets(STDIN))){
        $array[] = explode(" ",$input_line);//2次元配列で横の行を読み込み
    }

    print_r($array);

    foreach ($array as $key1 => $value1) {
      foreach ($value1 as $key2 => $value2) {
          if ($key2 == 1){
               $value2 = $value2 + 1;//$key2が「1」なら、要素に+1の処理
               print $value2 . "\n"; 
     }else{
          echo $value2." ";//$key2が「0」なら、要素+スペースで表示する
       }
      }
    }

メモ:
外側のforeachが「[0] => Array」の部分をまわす。
内側のforeachが「[0] => tanaka」の部分をまわす。

Array
(
    [0] => Array
        (
            [0] => tanaka
            [1] => 20
        )

    [1] => Array
        (
            [0] => kondo
            [1] => 10
        )

    [2] => Array
        (
            [0] => honda
            [1] => 30
        )

)
tanaka 21
kondo 11
honda 31

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

php 標準入力の値をforeach文で表示

個人学習用にメモ

標準入力の値を、foreach文で表示する。
※あとでまとめる用

$input_line = fgets(STDIN);
    while($input_line = trim(fgets(STDIN))){
        $array[] = explode(" ",$input_line);//2次元配列で横の行を読み込み
    }

    print_r($array);

    foreach ($array as $key1 => $value1) {
      foreach ($value1 as $key2 => $value2) {
          if ($key2 == 1){
               $value2 = $value2 + 1;//$key2が「1」なら、要素に+1の処理
               print $value2 . "\n"; 
     }else{
          echo $value2." ";//$key2が「0」なら、要素+スペースで表示する
       }
      }
    }

メモ:
外側のforeachが「[0] => Array」の部分をまわす。
内側のforeachが「[0] => tanaka」の部分をまわす。

Array
(
    [0] => Array
        (
            [0] => tanaka
            [1] => 20
        )

    [1] => Array
        (
            [0] => kondo
            [1] => 10
        )

    [2] => Array
        (
            [0] => honda
            [1] => 30
        )

)
tanaka 21
kondo 11
honda 31

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

百番煎じくらいの、CentOS8でPHP8環境作るコピペ

前置き

PHP8.0.0α1がリリースされたのでさっそくJITの威力を体感する(した)

これ試したかったんですが、centos8でやりたかった。

CentOS8で、PHP8のインストール。

で、これをコピペしようとしたら、ちょこちょこライブラリでつまずいた。

あれこれ調べた結果、コピペ手順ができたので、載せておこうと思いました。

OPCache/JIT周りは上の記事読んでどうぞ。

諸準備

dnf update -y

dnf install -y git wget tar sqlite* libxslt* gcc gcc-c++ make autoconf automake bison

re2c

最新をチェック

cd /usr/local/src

wget https://github.com/skvadrik/re2c/releases/download/1.3/re2c-1.3.tar.xz

tar Jxfv re2c-1.3.tar.xz

cd re2c-1.3

./configure

make

make install

re2c -v

PHP8

cd /usr/local/src

git clone https://github.com/php/php-src.git

cd php-src

./buildconf

./configure

make

make install

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

【1ヶ月で学ぶ!】Back-end Developperへの道 # 04日目 : Learn a Languageとは

下記の企画の4日目です。
【1ヶ月で学ぶ!】Back-end Developperへの道 #0:導入

下記を参考に項目を作成しました。
Roadmap to becoming a web developer in 2020

Learn a Language

Java

  • クラスベースのオブジェクト指向の、実装の依存関係をできるだけ少なくするように設計された汎用プログラミング言語
  • スマホアプリやWebサービス、業務系システムなど、さまざまな分野の開発に用いられる
  • コンパイルされたJavaコードは、再コンパイルを必要とせずにJavaをサポートするすべてのプラットフォーム上で実行できる
  • 小規模なプログラムやシステムの場合、逆に時間がかかる ## C#
  • Microsoft社が独自に開発した、WindowsやMacなどはもちろん、AndroidはiPhoneなど数多くのプラットフォームで動作するオブジェクト指向の言語
  • 初心者でも簡単に環境構築ができ、特にゲームの開発で需要が高い(ゲーム開発エンジンUnity)
  • Linuxでの開発に適しておらず、OSの開発にも向かない ## PHP
  • サーバーサイドWebアプリケーション構築のための豊富な組み込み関数
  • 多くのWebサーバ、データベースへの標準サポート
  • 弱い動的型付け ## Python
  • 文法を極力単純化してコードの可読性を高め、読みやすく、また書きやすくしてプログラマの作業性とコードの信頼性を高めることを重視してデザインされた、汎用の高水準言語。
  • 初心者でもシンプルに学ぶことができ、多目的な言語でライブラリが豊富。
  • 処理速度の早さが求められるシステムや高度なゲーム開発などにはPythonは不向き。 ## Ruby
  • 日本の国産言語で、可読性を重視したたオブジェクト指向スクリプト言語。
  • Ruby on Railsとセットで採用されることが多い。
  • PHPとPythonに比べて速度が遅い。 ## Golang
  • 静的型付け、C言語の伝統に則ったコンパイル言語、メモリ安全性、ガベージコレクション、構造的型付け、CSPスタイルの並行性などの特徴を持つ、Googleによって開発された言語
  • 個人使いの比較的簡素なAndroidアプリケーションから、YouTubeといった大規模Webサービスまで、GUIを使わなければほとんどなんでもできる
  • 継承がない(代わりに、構造体を埋め込むという趣旨のEmbedがある)

Java - ウィキペディア
Javaとは?初心者でも解るJavaのメリット・デメリット
未経験者必見!C#のメリット・デメリットを専門用語なしで紹介
PHP - ウィキペディア
Pythonのメリット・デメリットを簡潔にまとめてみた
Go (プログラミング言語) - Wikipedia
go言語(golang)の特徴って?メリットとデメリットをまとめてみた

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

GoogleのCustom Search APIでニュースを検索する方法

方法 : 検索エンジンの設定でschema.orgタイプにニュース関連のノードを設定する

  1. Programmable Search - 検索エンジンの編集から作った検索エンジンを選択
  2. [schema.org タイプを使用しているページを制限する]で「schema.orgタイプ」を選択する
  3. 保存は自動でされる

a.gif

やりたいこと : WordPressのページに検索したニュースを表示したい

WordPressもCustom Search APIも超初心者の状態で進めていた。

  1. WordPressにショートコードを使ってPHPを埋め込む方法 - Qiitaを覚えた
  2. WordPressでGoogleのCustom Search APIを使う方法 - ponsuke_tarou’s blogを覚えた
  3. GoogleのCustom Search APIでORとAND検索する方法 - Qiitaを覚えた
  4. で、普通に検索した時に[ニュース]のところに表示されるみたいなのをWordPressのページに表示してみたかった

image.png

API Reference  |  Custom Search  |  Google Developersを読んでもよくわからなかった・・・(英語よくわかんないし)

そんな時に「schema.orgタイプ」でできそうと知った。

You can use schemas from Schema.org to specify if you're only looking for articles/ blog posts.
To specify the Schema, go to Advanced settings on the Control Panel and select the required schema from here.
Google custom search engine for Google News - Stack Overflow

「schema.orgタイプ」が何だかはよくわからないけど使ってみる

schema.org について
schema.org とは、Google など大手の検索プロバイダにより認められた方法で、コンテンツをマークアップするためにウェブマスターに HTML を提供する取り組みです。たとえば、映画に関するサイトのウェブマスターはサイトの HTML で「Movie」というスキーマタイプと、「director」や「genre」などのプロパティを使用できます。検索プロバイダはそのデータを使用して、検索結果に表示されるページのコンテンツを把握することができます
schema.org タイプを使ってトピックの検索エンジンを作成 - Programmable Search Engine ヘルプ

やってみた

  • 環境
    • Windows 10 Pro 64bit バージョン1909
    • Local by Flywheel 5.6.1
      • PHP 7.4.1 / MySQL 8.0.16 / Apach / WordPress 5.4.2
      • テーマ : Lightning

PHPのコードは同じで検索エンジンのschema.org タイプを変えて表示してみた

schema.org タイプを指定しない場合

Qiitaとかブログの検索結果が出てくる
image.png

schema.org タイプを指定した場合

ニュースだけとはいかないけどニュースっぽい検索結果が出た!
image.png

使ったコード

コードでは特段「ニュース」とか関係なく、ただ検索しているだけ。

google-search.php
<?php
// 検索ワード群1
$word_group1 = array('ponsuke','tarou','0531','ぽんすけ','たろう');
// 検索ワード群2
$word_group2 = array('Qiita','hatena');
// 検索ワード群1~2はOR条件で検索したい
$word_or1 = implode(' OR ', $word_group1);
$word_or2 = implode(' OR ', $word_group2);
// 検索ワード群1と2のどちらの検索文言も含んで検索したい : (検索ワード群1) AND (検索ワード群2)
$search_words = '(' . $word_or1 . ') AND (' . $word_or2 . ')';
$param_ary = array(
    // 検索ワード
    'q' => $search_words,
    'key' => {APIキー},
    'cx' => {検索エンジンID},
    // JSON形式で取得する
    'alt' => 'json',
    // 取得開始順位(検索結果1~10位を取得するを取得する)
    'start' => 1
);
$param = http_build_query($param_ary);
$reqest_url = 'https://www.googleapis.com/customsearch/v1?' . $param;
$result_json = file_get_contents($reqest_url, true);
$result = json_decode($result_json, true);
$items = $result['items'];
?>

<h1>今日のぽんすけ検索</h1>
<?php if (count($items) === 0) : ?>
<p>ぽんすけ情報はありません。</p>
<?php else : ?>

<?php foreach ($items as $key => $item) : ?>
<a href="<?php echo $item['link']; ?>"><?php echo $item['title']; ?></a>
<br>
<?php endforeach; ?>

<?php endif; ?>
functions.php
<?php
// ...省略...
function google_search() {
    // 出力バッファリングを有効化する
    ob_start();
    // 外部ファイルを読み込む
    get_template_part('google-search');
    // 出力バッファを削除する
    return ob_get_clean();
}
add_shortcode('google-search', 'google_search');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHPでメッシュコード(最大8分の1)から中心点を取得する

PHPでメッシュコードの中心点を取るコードが以下です。
呼び出すのは coordinateFromMeshCode($mesh_code) で引数にメッシュコードを入れればOKです。
4分の1などを出したい場合は末尾の数字を削っていけばOKです。
その際中心出す距離も適宜変更が必要です。

function coordinateFromMeshCode($mesh_code) {
    $first_coordinate = $this->first($mesh_code);
    $second_coordinate = $this->second($mesh_code);
    $third_coordinate = $this->third($mesh_code);
    $forth_coordinate = $this->forth($mesh_code);
    $fifth_coordinate = $this->fifth($mesh_code);
    $sixth_coordinate = $this->sixth($mesh_code);
    $lat = ($first_coordinate[1] + $second_coordinate[1] + $third_coordinate[1] + $forth_coordinate[1] + $fifth_coordinate[1] + $sixth_coordinate[1]) / 3600;
    $lon = ($first_coordinate[0] + $second_coordinate[0] + $third_coordinate[0] + $forth_coordinate[0] + $fifth_coordinate[0] + $sixth_coordinate[0]) / 3600;

    $center_lat = $lat + 3.75 / 3600 / 2;
    $center_lon = $lon + 5.625 / 3600 / 2;
    return [$center_lat, $center_lon];
}

private function first($mesh_code) {
    $lat = substr($mesh_code, 0, 2);
    $lon = substr($mesh_code, 2, 2);
    return [(intval($lon) + 100) * 3600, $lat / 1.5 * 3600]; // 3600をかけて秒に直す
}

private function second($mesh_code) {
    $lat = substr($mesh_code, 4, 1);
    $lon = substr($mesh_code, 5, 1);
    return [$lon * 7.5 * 60, $lat * 5 * 60]; // 60をかけて秒に直す
}

private function third($mesh_code) {
    $lat = substr($mesh_code, 6, 1);
    $lon = substr($mesh_code, 7, 1);
    return [$lon * 45, $lat * 30];
}

private function forth($mesh_code) {
    $forth = substr($mesh_code, 8, 1);
    if ($this->is_south($forth)) {
        $lat = 0;
    } else {
        $lat = 15;
    }

    if ($this->is_west($forth)) {
        $lon = 0;
    } else {
        $lon = 22.5;
    }

    return [$lon, $lat];
}

private function fifth($mesh_code) {
    $fifth = substr($mesh_code, 9, 1);
    if ($this->is_south($fifth)) {
        $lat = 0;
    } else {
        $lat = 7.5;
    }

    if ($this->is_west($fifth)) {
        $lon = 0;
    } else {
        $lon = 11.25;
    }

    return [$lon, $lat];
}

private function sixth($mesh_code) {
    $sixth = substr($mesh_code, 10, 1);
    if ($this->is_south($sixth)) {
        $lat = 0;
    } else {
        $lat = 3.75;
    }

    if ($this->is_west($sixth)) {
        $lon = 0;
    } else {
        $lon = 5.625;
    }

    return [$lon, $lat];
}

private function is_south($code) {
    if ($code == 1 || $code == 2) {
        return true;
    }
    return false;
}

private function is_west($code) {
    if ($code == 1 || $code == 3) {
        return true;
    }
    return false;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

日付をjson形式で送ってxcodeで日本語形式に表示例「ISO8601形式(iOS10.0以上)」

Json形式

date('Y-m-d',mktime(0, 0, 0, $month, $i, $year))."T00:00:00+00:00"

xcode文

                        let datew = json["calendar"][i]["date"].string
                        print("datew")
                        print(datew)

                        let datewx = datew!
                        // iOS10.0以上ならズバリのフォーマッターが使える
                        let iso8601DateFormatter = ISO8601DateFormatter()
                        // 変換
                        let date = iso8601DateFormatter.date(from: datewx) // 拡張形式


                        print("datew日付変換")
                        print(date)


                        let dateFormatter = DateFormatter()
                        // フォーマット設定
                        dateFormatter.dateFormat = "yyyy'年'M'月'd'日" // 曜日1文字
                        //dateFormatter.dateFormat = "M'月'd'日 ('EEEE')'" // 曜日3文字

                        // ロケール設定(日本語・日本国固定)
                        dateFormatter.locale = Locale(identifier: "ja_JP")

                        // タイムゾーン設定(日本標準時固定)
                        dateFormatter.timeZone = TimeZone(identifier: "Asia/Tokyo")

                        // 変換
                        let str = dateFormatter.string(from: date!)

                        print("datew変換後")
                        // 結果表示
                        print(str) 


                    }

参考リンク先
String ⇒ Date 変換のISO8601形式(iOS10.0以上)を参考
https://www.2nd-walker.com/2020/01/14/swift-date-string-convert/

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

LaravelでCSVする方法

はじめに

FuelPHPには Format:forge($array)->toCsv() という感じで配列からかんたんにCSV用の文字列を生み出すことができたのですが、Laravelにはありません。
http://fuelphp.jp/docs/1.9/classes/format.html

それくらい foreach でぐるぐる回せやってのはあるのでしょうけど、めんどくさいじゃないですか。
そこでいい方法がないかなって探してみました。

soapbox/laravel-formatter を入れる

まあ、だいたいこういうものはpackagist漁ればいいのがありそうなもんなんですよ。
Laravel用じゃなくてもいいのですが、Laravel用だと5.5以降ならServiceProviderにいちいち登録しなくても使えるのがいいですよね!

というわけで今回は soapbox/laravel-formatter というのを使ってみます。
https://packagist.org/packages/soapbox/laravel-formatter

こいつを composer require しましょう。

$ composer require soapbox/laravel-formatter

これでLaravel内で Formatter として使えるようになっているので、あとは以下の感じで行けるはずです。
基本的には連想配列のキーがそのままラベル行として使われるので、DBから引っ張ってくるときにカラム名をas使って日本語とかにすると使いやすいと思います。

use SoapBox\Formatter\Formatter;

class HogeService
{
    public function makeCsvData()
    {
        $list = $this->hogeRepository->listForDownload();
        $formatter = Formatter::make($list->toArray(), Formatter::ARR);

        return $formatter->toCsv();
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ApacheのAddDefaultCharsetを変更しても一部文字化けが解消されない

概要

CentOS5x->CentOS7xへのリプレースを実施したときのことです。
旧環境では文字化けしていなかった一部のhtmlファイルの日本語が新環境では文字化けしていることが確認されました。

調査

対象htmlファイル内ではcharsetがshift-jisに指定されていました。
一方、ApacheのAddDefaultCharset utf-8(旧環境ではOFF)でした。なるほど。
そこで、新環境でも同じくこれをOFFにしたところ、、、なんと文字化けは解消しませんでした。

解決

調査の過程でphp環境の影響が浮上しました。
php.ini内でcharsetがutf-8と指定されており、これがApache起動時の設定パラメータを上書きしていたと思われます。

php.ini
; PHP's default character set is set to UTF-8.
; http://php.net/default-charset
default_charset = ""
; If empty, default_charset is used.

上記のとおり変更を反映させた後、対象htmlファイルの文字化けがなくなったことを確認しました。
※ApacheのAddDefaultCharset はOFF

phpのデフォルト設定でUTF-8が指定されるようになっていたようです。
対象ファイルは静的htmlファイルだったのでphpの設定との関連性になかなか気づけませんでした。

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

WordPressが初めてでさっぱりわからないときに見たいリンク集

管理画面の使い方がわからない

ディレクトリ構成

サイトディレクトリ
├app
|├public
|├public/wp-admin
|├public/wp-content
|├public/wp-content/plugins
|├public/wp-content/themes
|├public/wp-content/themes/各テーマディレクトリ # ここにショートコードで使うfunctions.phpがある
|├public/wp-content/uploads
|└public/wp-includes
├conf
|├apache
|├mysql
|└php
└logs
 ├apache
 └php

PHPを書きたい

with Custom Search API

Parameters 内容
q string 検索ワード
key string APIキー
cx string 検索エンジンID
start integer 検索結果の取得開始順位
safe enum セーフサーチの設定

ツール

ローカル環境を作ってみたい

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

PHP初心者がPHP7技術者認定初級試験を受けてみた。

2019年5月27日に開始された試験ということもあって、記事も少なかったので
自分の備忘録と今後取得される方へ参考になればと思い書かせていただきます!

取得目的

業務では約1年ほどRubyに触れていましたが
就業先でPHPを使用することになるので日々の学習の継続とスキルアップのために
受けさせていただきました...!

初級試験について

  • 試験会場: 全国のオデッセイコミュニケーションズCBTテストセンター
  • 試験日:試験会場にてほぼ一年中実施しています。
  • 設問数:40問
  • 試験時間:1時間
  • 合格ライン:7割正解
  • 出題形式:選択式(複数または単一選択)

公式ページの合格体験を見てる限りですと
初級試験に関しては独学で取得されている方が多い印象でした。

勉強方法と期間

初めてのPHP(PHP7版)(公式主教材)

公式教材として挙がっている入門書ですが
他の言語の経験のない方だと少し堅苦しくて読みにくいかもしれません。
サンプルコードも豊富に掲載されており解説も丁寧なので
一通り読み終われば基礎的なPHPの知識が定着できる良書だと思います
ページ数的には400ページほど。
学習期間は3日ほど(15時間くらい)

徹底攻略PHP7技術者認定[初級]試験問題集

合格者の体験談を見ても大半の方が使用している問題集。
全14章あり、章ごとに問題が各8~12問ほどありまして優しい構成になっています。
1周目は1日3~4章進めていき、4日で1周(10時間くらい)
2周目から1日7章ペースでさらっと読みこんで、試験当日までに4周ほどしてました。
模擬問題が最後にありますが、そこで9割以上とれるようになれば問題ないかなと感じます。

受験を終えて

結果は1000点満点中の875点でした。(合格ラインは700点)
フレームワークの仕様に関する出題や、実際の実務における開発手法の問題(TDD テスト駆動開発)等
初心者の方からするととっつきにくい問題も所々見られました。
トータルの勉強時間としては大体40時間ほど。
これからPHPを始める方には程よい難易度でよいかもしれません。

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

GoogleのCustom Search APIでORとAND検索する方法

GoogleのCustom Search APIをWordPressで使う方法を覚えた

WordPressにショートコードを使ってPHPを埋め込む方法 - Qiita
を覚えたので
WordPressでGoogleのCustom Search APIを使ってみる - ponsuke_tarou’s blog
をやってみたところで思た

ORとAND検索したい

いくつもの検索ワードを検索したいけどAPIでは100検索/日という制限があるので何回も何回も検索したくない

Custom Search JSON API provides 100 search queries per day for free. If you need more, you may sign up for billing in the API Console.
Custom Search JSON API  |  Google Developers

そこで、普通のGoogle検索でも使える「OR」「AND」をAPIでやってみた。

【組み合わせ(優先)検索】(A AND B) OR C
「()」を使うと、「()」内が優先された結果を表示します。
上記検索(AND OR NOT)を、組み合わせて使うときに優先順位を付けられます。
ex.) (google AND yahoo) OR MSN
Google検索を使いこなすためのテクニックまとめ | SONICMOOV LAB

Custom Search APIでORとAND検索する方法

  • 環境
    • Windows 10 Pro 64bit バージョン1909
    • Local by Flywheel 5.6.1
      • PHP 7.4.1 / MySQL 8.0.16 / Apach / WordPress 5.4.2
      • テーマ : Lightning

配列に検索ワードを入れてそれを繋げるだけ。
検索結果は、引用符をつけて表示してみた。

google-search.php
<?php
// 検索ワード群1
$word_group1 = array('ponsuke','tarou','0531','ぽんすけ','たろう');
// 検索ワード群2
$word_group2 = array('Qiita','hatena');
// 検索ワード群1~2はOR条件で検索したい
$word_or1 = implode(' OR ', $word_group1); // >>> 'ponsuke OR tarou OR 0531 OR ぽんすけ OR たろう'
$word_or2 = implode(' OR ', $word_group2); // >>> 'Qiita OR hatena'
// 検索ワード群1と2のどちらの検索文言も含んで検索したい : (検索ワード群1) AND (検索ワード群2)
$search_words = '(' . $word_or1 . ') AND (' . $word_or2 . ')'; // >>> '(ponsuke OR tarou OR 0531 OR ぽんすけ OR たろう) AND (Qiita OR hatena)'
$param_ary = array(
    // 検索ワード
    'q' => $search_words,
    'key' => {APIキー},
    'cx' => {検索エンジンID},
    // JSON形式で取得する
    'alt' => 'json',
    // 取得開始順位(検索結果1~10位を取得するを取得する)
    'start' => 1
);
$param = http_build_query($param_ary);
$reqest_url = 'https://www.googleapis.com/customsearch/v1?' . $param;
$result_json = file_get_contents($reqest_url, true);
$result = json_decode($result_json, true);
$items = $result['items'];
?>

<h1>今日のぽんすけ検索</h1>
<?php if (count($items) === 0) : ?>
<p>ぽんすけ情報はありません。</p>
<?php else : ?>

<?php foreach ($items as $key => $item) : ?>
<blockquote class="wp-block-quote">
<a href="<?php echo $item['link']; ?>"><?php echo $item['title']; ?></a>
<p><?php echo $item['snippet']; ?></p>
</blockquote>
<br>
<?php endforeach; ?>

<?php endif; ?>
functions.php
<?php
// ...省略...
function google_search() {
    // 出力バッファリングを有効化する
    ob_start();
    // 外部ファイルを読み込む
    get_template_part('google-search');
    // 出力バッファを削除する
    return ob_get_clean();
}
add_shortcode('google-search', 'google_search');

image.png

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

Laravel ログの格納場所を変更する

目的

  • Laravelのログの内容によって出力先を変更する第一歩として、デフォルトのログ出力状態で格納場所を任意の場所に変更する方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提条件

  • 下記、または下記に準ずる方法でMacのネイティブ環境(DockerやAWSを使わない)にLaravelの環境構築が完了していること。

読後感

  • Laravelのログファイルの場所をアプリ名ディレクトリ/storage/logs/直下からアプリ名ディレクトリ/storage/my_logsに変更できる。

詳細

  1. .envファイルの編集

    1. アプリ名ディレクトリで下記コマンドを実行して.envファイルを開く。

      $ vi .env
      
    2. LOG_CHANNELの記載を下記の様に修正する。

      アプリ名ディレクトリ/.env
      LOG_CHANNEL=single
      
    3. 修正後の.envファイルの例を下記に記載する。

      アプリ名ディレクトリ/.env
      APP_NAME=Laravel
      APP_ENV=local
      APP_KEY=base64:1IFTNHZwVQ6AwDC+U7XfApuoSf5s02NK49auu7okT/E=
      APP_DEBUG=true
      APP_URL=http://localhost
      
      LOG_CHANNEL=single
      
      DB_CONNECTION=mysql
      DB_HOST=127.0.0.1
      DB_PORT=3306
      DB_DATABASE=laravel_auth_login
      DB_USERNAME=root
      DB_PASSWORD=****
      
      BROADCAST_DRIVER=log
      CACHE_DRIVER=file
      QUEUE_CONNECTION=sync
      SESSION_DRIVER=file
      SESSION_LIFETIME=120
      
      REDIS_HOST=127.0.0.1
      REDIS_PASSWORD=null
      REDIS_PORT=6379
      
      MAIL_MAILER=log
      MAIL_HOST=smtp.mailtrap.io
      MAIL_PORT=2525
      MAIL_USERNAME=null
      MAIL_PASSWORD=null
      MAIL_ENCRYPTION=null
      MAIL_FROM_ADDRESS=null
      MAIL_FROM_NAME="${APP_NAME}"
      
      AWS_ACCESS_KEY_ID=
      AWS_SECRET_ACCESS_KEY=
      AWS_DEFAULT_REGION=us-east-1
      AWS_BUCKET=
      
      PUSHER_APP_ID=
      PUSHER_APP_KEY=
      PUSHER_APP_SECRET=
      PUSHER_APP_CLUSTER=mt1
      
      MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
      MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
      
  2. ログの設定

    1. アプリ名ディレクトリで下記コマンドを実行してログの設定ファイルを開く

      $ vi config/logging.php
      
    2. 開いたファイルを下記の様に修正する。

      アプリ名ディレクトリ/config/logging.php
      <?php
      
      use Monolog\Handler\NullHandler;
      use Monolog\Handler\StreamHandler;
      use Monolog\Handler\SyslogUdpHandler;
      
      return [
      
          /*
          |--------------------------------------------------------------------------
          | Default Log Channel
          |--------------------------------------------------------------------------
          |
          | This option defines the default log channel that gets used when writing
          | messages to the logs. The name specified in this option should match
          | one of the channels defined in the "channels" configuration array.
          |
          */
      
          'default' => env('LOG_CHANNEL', 'stack'),
      
          /*
          |--------------------------------------------------------------------------
          | Log Channels
          |--------------------------------------------------------------------------
          |
          | Here you may configure the log channels for your application. Out of
          | the box, Laravel uses the Monolog PHP logging library. This gives
          | you a variety of powerful log handlers / formatters to utilize.
          |
          | Available Drivers: "single", "daily", "slack", "syslog",
          |                    "errorlog", "monolog",
          |                    "custom", "stack"
          |
          */
      
          'channels' => [
              'stack' => [
                  'driver' => 'stack',
                  'channels' => ['single'],
                  'ignore_exceptions' => false,
              ],
      
              'single' => [
                  'driver' => 'single',
                  'path' => storage_path('my_logs/laravel.log'),
                  'level' => 'debug',
              ],
      
              'daily' => [
                  'driver' => 'daily',
                  'path' => storage_path('logs/laravel.log'),
                  'level' => 'debug',
                  'days' => 14,
              ],
      
              'slack' => [
                  'driver' => 'slack',
                  'url' => env('LOG_SLACK_WEBHOOK_URL'),
                  'username' => 'Laravel Log',
                  'emoji' => ':boom:',
                  'level' => 'critical',
              ],
      
              'papertrail' => [
                  'driver' => 'monolog',
                  'level' => 'debug',
                  'handler' => SyslogUdpHandler::class,
                  'handler_with' => [
                      'host' => env('PAPERTRAIL_URL'),
                      'port' => env('PAPERTRAIL_PORT'),
                  ],
              ],
      
              'stderr' => [
                  'driver' => 'monolog',
                  'handler' => StreamHandler::class,
                  'formatter' => env('LOG_STDERR_FORMATTER'),
                  'with' => [
                      'stream' => 'php://stderr',
                  ],
              ],
      
              'syslog' => [
                  'driver' => 'syslog',
                  'level' => 'debug',
              ],
      
              'errorlog' => [
                  'driver' => 'errorlog',
                  'level' => 'debug',
              ],
      
              'null' => [
                  'driver' => 'monolog',
                  'handler' => NullHandler::class,
              ],
      
              'emergency' => [
                  'path' => storage_path('logs/laravel.log'),
              ],
          ],
      
      ];
      

メモ

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

三項演算子で条件式をスッキリさせる方法

三項演算子とは?

指定した条件で処理を分岐させる条件式の一種です。

条件式にはif文・switch文・三項演算子などがあり、私はいままでif文ばかりを使用してきましたが三項演算子を使うとコードをすっきりとみやすくできることがわかったので今回まとめることにしました。

書き方

条件式 ? 式1 : 式2

条件式を評価して真なら式1、偽なら式2を返します。
if文と違い、指定した処理を実行するのではなく値を返すだけなので注意しましょう。

if文との比較

例としてif文と三項演算子でそれぞれ同じ条件分岐を記述してみたいと思います。

if.php
if ($speed >= 60){
    echo ("スピード違反です!");
} else {
    echo ("制限速度内です");
}
ternary_operator.php
$msg = $speed >= 60 ? "スピード違反です!" : "制限速度内です";
echo $msg;

いかがでしょうか?if文と比べて三項演算子は行数も少なく、みやすいコードになっているかと思います。
簡単な条件であれば三項演算子を積極的に使っていきたいですね。

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

[PHP] YouTube APIの利用(動画情報の取得)

今回の題

PHPでYouTubeのAPIを試してみました。
この記事ではYouTube上にある動画の情報を取得して表示するところまでを解説しています。
まだ触り始めたばかりであまり情報を持っていませんが備忘録として残します。
いつか誰かの参考になれば幸いです😉

GoogleAPIライブラリのインストール

composerを使ってインストールします。

$ composer require google/apiclient

インストールが終わり、以下の画像の様にvendor配下に「google」というディレクトリができていればOKです。
スクリーンショット 2020-06-28 23.13.52.png

APIキーの取得

以下のページからからAPIキーを取得します。
Googleのデベロッパーコンソール
流れとしては、

  1. YouTubeAPIを有効化(同時にプロジェクト作成)
  2. プロジェクトのAPIキーを作成

だけです。

以下、APIキーの取得までの詳しい流れを書きます。
(基本的に画像内の赤枠をクリックしていけばOKです)

YouTubeライブラリを有効化する

画面左側のバーから「ライブラリ」のリンクに飛ぶ。
スクリーンショット 2020-06-29 1.06.06.png

飛んだ先で「YouTube」と検索。
スクリーンショット 2020-06-29 1.10.37.png

「YouTube Data API v3」を選択。
スクリーンショット 2020-06-29 1.15.23.png
「有効にする」をクリック。
しばらくするとページが遷移します。
スクリーンショット 2020-06-29 0.24.44.png

2.APIキーを作成する

ページ遷移後、ページの上部に、「My First Project」 という名前のプロジェクトが作らているのがわかります。(画像黒下線部分)
問題なければ、画面左側のバーから「認証情報」のリンクに飛びます。
スクリーンショット 2020-06-29 5.15.44.png

「認証情報を作成」をクリック。するとモーダルが出てきます。
「APIキー」をクリック。
スクリーンショット 2020-06-29 5.22.39.png

「APIキー」が作成されました。(画像は塗りつぶしています)
あとで使いますのでコピーしておいてください。
スクリーンショット 2020-06-29 5.29.00.png

コード

例として、チャンネルIDで指定したチャンネルが投稿している動画を取得するコードです。
公式のサンプルコードを参考にさせていただきました。
https://developers.google.com/youtube/v3/code_samples/php?hl=ja#search_by_keyword

<?php
//GoogleAPIライブラリを読み込む
require_once (dirname(__FILE__) . '/vendor/autoload.php');
//先ほど取得したAPIキーを定数にセットする
const API_KEY = "XXXXXXXXX";

//APIキーを用いて認証を行う
function getClient() 
{
    $client = new Google_Client();
    $client->setApplicationName("youtube-api-test");
    $client->setDeveloperKey(API_KEY);
    return $client;
}

//動画を取得する.
function searchVideos() 
{
    $youtube = new Google_Service_YouTube(getClient());
    //ここに好きなYouTubeのチャンネルIDを入れる
    $params['channelId'] = 'XXXXXXXXX';
    $params['type'] = 'video';
    $params['maxResults'] = 10;
    $params['order'] = 'date';
    try {
        $searchResponse = $youtube->search->listSearch('snippet', $params);
    } catch (Google_Service_Exception $e) {
        echo htmlspecialchars($e->getMessage();
        exit;
    } catch (Google_Exception $e) {
        echo htmlspecialchars($e->getMessage();
        exit;
    }
    foreach ($searchResponse['items'] as $search_result) {
        $videos[] = $search_result;
    }
    return $videos;
}

$videos = searchVideos();

//取得した動画のサムネを表示してみる
foreach ($videos as $video) {
    echo '<img src="' . $video['snippet']['thumbnails']['high']['url']. '" />';
}

以上。

一言

YouTubeaAPIで一番お手軽にできる動画の取得のみを行ってみました。
リソースの挿入、更新、または削除を実行する操作の場合は、ユーザー認証が別途必要なので興味のある方は調べてみてください。

参考

公式
https://developers.google.com/youtube

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