まったり技術ブログ

Technology is power.

『Goutte』を使って文字列からHTML解析

“通信を発生させずにHTMLを解析"をしたい・・・

 今までPHPでHTML解析するときは「Simple HTML DOM Parser」を利用しており、数年ぶりの利用で使い方を確認しているとこのような記事が。

localdisk.hatenablog.com

「Simple HTML DOM Parser」はパフォーマンス面に優れておらず、今では「Goutte」を使った方がいいとか。

github.com

いざ「Goutte」の使い方を簡単に調べてみると「Simple HTML DOM Parser」のようにHTML文字列を引数に与えて解析対象のオブジェクトを作成することができなさそう。 - Simple HTML DOM Parser

// Simple HTML DOM Parserを使った文字列からオブジェクトを生成
$html = str_get_html( '<html><body>Hello!</body></html>' );
  • Goutte
    オブジェクトを生成するためにはリクエスト通信を発生させないといけなさそう。
$client = new Client();
$crawler = $client->request('GET', 'https://www.symfony.com/blog/');
// 通信で取得したオブジェクトが解析対象になる

通信を発生させずにHTMLを解析!!

Goutteでもファイル内に記載されているHTMLを解析できそう。

ja.stackoverflow.com

下記のようにすると、通信を発生せずに引数に与えたHTML文字列を解析することができます。localhost:80に対して、通信を発生させているため、該当のサーバ上で80番ポートをListenさせている必要があります。

<?php
use Goutte\Client;

$client = new Client();
$crawler = $client->request('HEAD', null);
$crawler->clear();

// addHtmlContentの引数にHTML文字列
$crawler->addHtmlContent('<html><body>Hello!</body></html>');

echo $crawler->filter('html')->html();
  //=> <body>Hello!</body>
  
echo $crawler->filter('body')->text();
  //=> Hello!

『Laravel Collective』でのHTML生成を簡単にまとめてみる

f:id:motikan2010:20170128223017p:plain LaravelでRailsの"link_to"のようにHTMLを生成できないのかと思い、探してみたら『Laravel Collective』というものがありました。
導入方法などは下記を参照。

Laravel Collective

HTML出力のイメージができないところがありましたので、実際に出力させて、まとめてみました。

Formタグ

基本

Form::open([‘url’ => ‘home’])

自動的にCSRFトークンが追加されて出力されます。

<form method="POST" action="http://localhost:3000/home" accept-charset="UTF-8">
<input name="_token" type="hidden" value="eAFowzR2efsBNQYrVzQ4VymRbYQB2afVyEDijzqd">

Form::close()

</form>

送信先にルーティング名

Form::open([‘route’ => ‘users.index’])

action属性にルーティングを適用することができます。

<form method="POST" action="http://localhost:3000/users" accept-charset="UTF-8">
<input name="_token" type="hidden" value="eAFowzR2efsBNQYrVzQ4VymRbYQB2afVyEDijzqd">

送信先にURLパラメータ

Form::model($user, [‘route’ => [‘users.update’, $user->id]])

ユーザIDといったURLパラメータをaction属性に含めることもできます。

<form method="POST" action="http://localhost:3000/users/1" accept-charset="UTF-8">
<input name="_token" type="hidden" value="eAFowzR2efsBNQYrVzQ4VymRbYQB2afVyEDijzqd">

PUTメソッド

Form::open([‘url’ => ‘home’, ‘method’ => ‘put’])

メソッドを区別するため「name=“_method"」属性を持っているinputタグが一緒に出力されます。

<form method="POST" action="http://localhost:3000/home" accept-charset="UTF-8">
<input name="_method" type="hidden" value="PUT">
<input name="_token" type="hidden" value="eAFowzR2efsBNQYrVzQ4VymRbYQB2afVyEDijzqd">

マルチパートフォーム

Form::open([‘url’ => ‘home’, ‘files’ => true])

<form method="POST" action="http://localhost:3000/home" accept-charset="UTF-8" enctype="multipart/form-data">
<input name="_token" type="hidden" value="eAFowzR2efsBNQYrVzQ4VymRbYQB2afVyEDijzqd">

ラベル

Form::label(‘email’, ‘E-Mail Address’)

<label for="email">E-Mail Address</label>

入力フォーム

テキスト

Form::text(‘username’)

第1引数にname属性の値を指定します。

<input name="username" type="text">

Form::text(‘email’, ‘example@example.com’)

第2引数に初期値(value属性の値)を指定します。

<input name="email" type="text" value="example@example.com" id="email">

パスワード

Form::password(‘password’, [‘class’ => ‘awesome’])

<input class="awesome" name="password" type="password" value="">

メールアドレス

Form::email(‘mail’‘, $value = null, $attributes = [])

<input name="mail" type="email">

数値

Form::number(‘name’, ‘value’)

<input name="name" type="number" value="value">

日付

Form::date(‘name’, \Carbon\Carbon::now())

<input name="name" type="date" value="2017-01-28">

ファイル

Form::file(‘name’, $attributes = [])

<input name="name" type="file">

チェックボックス

Form::checkbox(‘name’, ‘value’)

<input checked="checked" name="name" type="checkbox" value="value">

ラジオボタン

Form::radio(‘name’, ‘value’)

<input name="name" type="radio" value="value">

セレクトボックス

基本

Form::select(‘size’, [‘L’ => ‘Large’, ’S' => ‘Small’])

<select name="size">
  <option value="L">Large</option>
  <option value="S">Small</option>
</select>

初期値を指定

Form::select(‘size’, [‘L’ => ‘Large’, ’S' => ‘Small’], ’S')

第3引数に初期値を指定することができます。

<select name="size">
  <option value="L">Large</option>
  <option value="S" selected="selected">Small</option>
</select>

空の初期値を指定

Form::select(‘size’, [‘L’ => ‘Large’, ’S' => ‘Small’], null, [‘placeholder’ => ‘Pick a size…’])

空の初期値を設定することもできます。

<select name="size">
  <option selected="selected" value="">Pick a size...</option>
  <option value="L">Large</option>
  <option value="S">Small</option>
</select>

multiple属性を付与

Form::select(‘size’, [‘L’ => ‘Large’, ’S' => ‘Small’], null, [‘multiple’ => true])

multiple属性を付与するし、複数選択することができます。

<select multiple="1" name="size">
  <option value="L">Large</option>
  <option value="S">Small</option>
</select>

選択肢をグループ化

Form::select(‘animal’, [ ‘Cats’ => [‘leopard’ => ‘Leopard’], ‘Dogs’ => [‘spaniel’ => ‘Spaniel’], ])

<select name="animal">
  <optgroup label="Cats">
    <option value="leopard">Leopard</option>
  </optgroup>
  <optgroup label="Dogs">
    <option value="spaniel">Spaniel</option>
  </optgroup>
</select>

連続した値の選択肢

Form::selectRange(‘number’, 10, 20)

10 〜 20の値を指定することができます。

<select name="number">
  <option value="10">10</option>
  <option value="11">11</option>
  <option value="12">12</option>
  <option value="13">13</option>
  <option value="14">14</option>
  <option value="15">15</option>
  <option value="16">16</option>
  <option value="17">17</option>
  <option value="18">18</option>
  <option value="19">19</option>
  <option value="20">20</option>
</select>

月の選択肢

Form::selectMonth(‘month’)

<select name="month">
  <option value="1">January</option>
  <option value="2">February</option>
  <option value="3">March</option>
  <option value="4">April</option>
  <option value="5">May</option>
  <option value="6">June</option>
  <option value="7">July</option>
  <option value="8">August</option>
  <option value="9">September</option>
  <option value="10">October</option>
  <option value="11">November</option>
  <option value="12">December</option>
</select>

ボタン

Form::submit(‘Click Me!’)

<input type="submit" value="Click Me!">

オリジナルのフィールド

Form::myField()

フィールドを作成し、任意の名前で定義するこができます。

Form::macro('myField', function()
{
    return '<input type="awesome">';
});
<input type="awesome">

リンク

基本

<a href="http://localhost:3000/foo/bar">http://localhost:3000/foo/bar</a>

リンク文字列を指定

<a href="http://localhost:3000/home">Home</a>

リンク先をHTTPS

<a href="https://localhost:3000/foo/bar">https://localhost:3000/foo/bar</a>
<a href="http://localhost:3000/foo/bar.zip">http://localhost:3000/foo/bar.zips</a>

リンク先にルーティング名を使用

<a href="http://localhost:3000/login">http://localhost:3000/login</a>

URLパラメータを付与

“$parameters"引数に配列を与えることで、リンク先にURLパラメータを付与することができます。

<a href="http://localhost:3000/login?id=1&name=motikan2010">http://localhost:3000/login?id=1&name=motikan2010</a>

リンク先に"コントローラ@アクション"形式

<a href="http://localhost:3000/login">http://localhost:3000/login</a>

ScaffoldとAuthを使ってアプリケーションを作る

f:id:motikan2010:20170124011356p:plain
PHP製のWebフレームワークである『Laravel』を使って"認証あり"のアプリケーションを作成していきます。
今回はLaravelバージョン5.3系を使います。

プロジェクトの作成

$ laravel new auth_scaffold
$ cd auth_scaffold
$ php artisan --version
Laravel Framework version 5.3.29

Scaffoldの導入

github.com

下記のコマンドで容易に導入することができます。

$ composer require 'laralib/l5scaffold' --dev

サービスプロバイダーの追加

“config/app.php"内の"providers"に対して、
「Laralib\L5scaffold\GeneratorsServiceProvider::class」を追加します。
$ vim config/app.php

<?php
(省略)
    'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,
(省略)
        Laralib\L5scaffold\GeneratorsServiceProvider::class,
    ],
(以下省略)

上記の追加が完了したら、Scaffoldの準備完了です。

scaffoldの実行

下記のコマンドで コントローラ・モデル・ビュー の作成が完了します。
相変わらず便利すぎ!

$ php artisan make:scaffold Tweet --schema="title:string:default('Tweet #1'), body:text"

Configuring Tweet...
Migration created successfully
Seed created successfully.
Model created successfully.
Controller created successfully.
Layout created successfully.
Error created successfully.
Views created successfully.
Dump-autoload...
Route::resource("tweets","TweetController"); // Add this line in routes.php

予定通りに作成されたのかを確認してみます。

// コントローラ
$ ls app/Http/Controllers/TweetController.php
app/Http/Controllers/TweetController.php

// モデル
$ ls app/Tweet.php
app/Tweet.php

// ビュー
$ ls resources/views/tweets/
create.blade.php  edit.blade.php  index.blade.php  show.blade.php

しっかりと作成されているようです。
相変わらず早い!!

ルーティングの設定

作成されたコントローラを呼び出せないと意味がありませんので、 ルーティングの設定を行っていきます。
“routes/web.php"の末尾に「Route::resource("tweets”,“TweetController”);」を記述します。
$ vim routes/web.php

<?php
(省略)

Route::get('/', function () {
    return view('welcome');
});

Route::resource("tweets","TweetController"); // 追記

ルーティングの設定が反映されているか確認してみます。

$ php artisan route:list

+--------+-----------+---------------------+----------------+----------------------------------------------+--------------+
| Domain | Method    | URI                 | Name           | Action                                       | Middleware   |
+--------+-----------+---------------------+----------------+----------------------------------------------+--------------+
|        | GET|HEAD  | /                   |                | Closure                                      | web          |
|        | GET|HEAD  | api/user            |                | Closure                                      | api,auth:api |
|        | POST      | tweets              | tweets.store   | App\Http\Controllers\TweetController@store   | web          |
|        | GET|HEAD  | tweets              | tweets.index   | App\Http\Controllers\TweetController@index   | web          |
|        | GET|HEAD  | tweets/create       | tweets.create  | App\Http\Controllers\TweetController@create  | web          |
|        | DELETE    | tweets/{tweet}      | tweets.destroy | App\Http\Controllers\TweetController@destroy | web          |
|        | PUT|PATCH | tweets/{tweet}      | tweets.update  | App\Http\Controllers\TweetController@update  | web          |
|        | GET|HEAD  | tweets/{tweet}      | tweets.show    | App\Http\Controllers\TweetController@show    | web          |
|        | GET|HEAD  | tweets/{tweet}/edit | tweets.edit    | App\Http\Controllers\TweetController@edit    | web          |
+--------+-----------+---------------------+----------------+----------------------------------------------+--------------+

データベースの作成

ここの説明は簡単に流します。
データベースの名前は「scaffold_app」にしておきます。 mysql> create database scaffold_app; データベースの設定は下記のファイルに行います。 - ユーザ名:laravelusr - パスワード:himitsu
にしています。
$ vim .env

(省略)

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=scaffold_app
DB_USERNAME=laravelusr
DB_PASSWORD=himitsu

(省略)

上記設定が完了したらテーブルの作成を行います。

マイグレーション

下記のコマンドを実行することによって、テーブルが作成されます。
ここでは、tweetsテーブル以外にも作成されていますが、無視しておきます。

$ php artisan migrate

Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrated: 2017_01_23_143819_create_tweets_table

Webアプリケーションへアクセス

これで、Webアプリケーションとして利用することが可能になりました。

$ php artisan serve --host 0.0.0.0 --port 3000

http://127.0.0.1:3000/tweets"にアクセス f:id:motikan2010:20170124004456p:plain
上記のような画面が表示されたら成功です。
試しになにか投稿してみましょう。「Create」を押して投稿画面へ遷移できます。
f:id:motikan2010:20170124004459p:plain
投稿することができました。
「View」を押すことで詳細画面へ遷移することもできます。

f:id:motikan2010:20170124004502p:plain

これはこれで良いアプリケーションとなっていますが、これから認証機能を実装してきます。

認証機能(Auth)の導入

魔法の呪文『make:auth』

下記のコマンドを実行します。
$ php artisan make:auth
認証に必要な コントローラ・モデル・ビュー の作成が完了します。
Scaffold同様、作成されているかを確認します。

// コントローラ
$ ls app/Http/Controllers/Auth/
ForgotPasswordController.php  LoginController.php  RegisterController.php  ResetPasswordController.php

// モデル
$ ls app/User.php
app/User.php

// ビュー
$ ls resources/views/auth/
login.blade.php     passwords/      register.blade.php

作成されているのが確認できます。
次にルーティングの設定ですが、Authの場合は自動的に記述されます。
念のために確認してみます。
$ cat routes/web.php

<?php
(省略)

Route::get('/', function () {
    return view('welcome');
});

Route::resource("tweets","TweetController");

Auth::routes();    // 自動的に追加されている

Route::get('/home', 'HomeController@index');

ルーティングが反映されているか改めて確認してみます。

$ php artisan route:list

+--------+-----------+------------------------+----------------+------------------------------------------------------------------------+--------------+
| Domain | Method    | URI                    | Name           | Action                                                                 | Middleware   |
+--------+-----------+------------------------+----------------+------------------------------------------------------------------------+--------------+
|        | GET|HEAD  | /                      |                | Closure                                                                | web          |
|        | GET|HEAD  | api/user               |                | Closure                                                                | api,auth:api |
|        | GET|HEAD  | home                   |                | App\Http\Controllers\HomeController@index                              | web,auth     |
|        | POST      | login                  |                | App\Http\Controllers\Auth\LoginController@login                        | web,guest    |
|        | GET|HEAD  | login                  | login          | App\Http\Controllers\Auth\LoginController@showLoginForm                | web,guest    |
|        | POST      | logout                 | logout         | App\Http\Controllers\Auth\LoginController@logout                       | web          |
|        | POST      | password/email         |                | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail  | web,guest    |
|        | GET|HEAD  | password/reset         |                | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm | web,guest    |
|        | POST      | password/reset         |                | App\Http\Controllers\Auth\ResetPasswordController@reset                | web,guest    |
|        | GET|HEAD  | password/reset/{token} |                | App\Http\Controllers\Auth\ResetPasswordController@showResetForm        | web,guest    |
|        | POST      | register               |                | App\Http\Controllers\Auth\RegisterController@register                  | web,guest    |
|        | GET|HEAD  | register               | register       | App\Http\Controllers\Auth\RegisterController@showRegistrationForm      | web,guest    |
|        | GET|HEAD  | tweets                 | tweets.index   | App\Http\Controllers\TweetController@index                             | web          |
(省略)
|        | GET|HEAD  | tweets/{tweet}/edit    | tweets.edit    | App\Http\Controllers\TweetController@edit                              | web          |
+--------+-----------+------------------------+----------------+------------------------------------------------------------------------+--------------+

問題なく認証機能へアクセスできそうです。

認証の確認

認証を行っていないユーザはログイン画面へ遷移させるようにしましょう。 $ vim app/Http/Controllers/TweetController.php

<?php namespace App\Http\Controllers;

use App\Http\Requests;
use App\Http\Controllers\Controller;

use App\Tweet;
use Illuminate\Http\Request;

class TweetController extends Controller {

        public function __construct(){
            $this->middleware('auth');
        }
(省略)

これで認証を行っていないユーザはTweetControllerのアクションへアクセスできないようになります。

認証後の遷移先を変更

実は認証に必要なアカウントの「登録後」や「認証後」には"/home"へ遷移されるように、Authのコントローラで設定されています。 “/home"には特になにもアプリケーションがありませんので、そこへ遷移されても楽しくありません。  事前に「登録後」や「認証後」には”/tweets"へ遷移されるように設定します。
行う作業は非常に簡単です。
下記2つのファイルを修正するだけです。
$ vim app/Http/Controllers/Auth/RegisterController.php

<?php

namespace App\Http\Controllers\Auth;

use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;

class RegisterController extends Controller
{

(省略)

    protected $redirectTo = '/tweets'; // "home" を "tweets" に変更
(省略)

$ vim app/Http/Controllers/Auth/LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{

(省略)
    
    protected $redirectTo = '/tweets'; // "home" を "tweets" に変更
(省略)

これで完成です。

アプリケーションを確認

下記のコマンドでアプリケーションを起動しましょう。
$ php artisan serve –host 0.0.0.0 –port 3000

http://127.0.0.1:3000/」にアクセスすると下記のような画面が表示されます。
f:id:motikan2010:20170124004509p:plain
ためしに「http://127.0.0.1:3000/tweets」に直接アクセスしてみましょう。
当然ながら認証を行っていませんので、ログイン画面へ遷移されますf:id:motikan2010:20170124004523p:plain
まだ、ユーザは存在していないので「Register」からユーザの作成を行ってみます。 f:id:motikan2010:20170124004517p:plain
ユーザの作成が完了すると、認証処理が行われTweets画面へ遷移されます。 f:id:motikan2010:20170124004526p:plain

こんな短時間でこのようなアプリケーションが作成できるとは驚きです。
今回は誰でもがアカウントを作成することができ、認証後のページにアクセスすることが可能となっていますが、コードをいじってみることで自分だけがアクセスできるサイトを作れそうですね。