ゆきばた

ゆきばたの果てしない戯言

PHP7 で Symfony2.7 のプロジェクトを持ってきたらエラー(Attempted to load class "DOMDocument" from the global namespace)

既存のSymfony2.7のプロジェクトを持ってきて、
動かそうとしたら、こんなErrorが発生

/{SYMFONY PATH}/app
PHP Fatal error:  Uncaught Symfony\Component\Debug\Exception\ClassNotFoundException: Attempted to load class "DOMDocument" from the global namespace.
Did you forget a "use" statement? in /{SYMFONY PATH}/vendor/symfony/symfony/src/Symfony/Component/Config/Util/XmlUtils.php:52
Stack trace:
#0 /{SYMFONY PATH}/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php(261): Symfony\Component\Config\Util\XmlUtils::loadFile('{SYMFONY PATH}', Array)
#1 /{SYMFONY PATH}/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php(41): Symfony\Component\DependencyInjection\Loader\XmlFileLoader->parseFileToDOM('{SYMFONY PATH}')
#2 /{SYMFONY PATH}/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php(57): Symfony\Component\DependencyInje in /{SYMFONY PAHT}/vendor/symfony/symfony/src/Symfony/Component/Config/Util/XmlUtils.php on line 52

なんてこったい・・・・

原因は、PHPをyumでインストールした際に忘れ物をしたこと

php-xml がない。

ということでインストール

# yum install --enablerepo=remi-php70 php-xml

f:id:yukibata:20170618132954p:plain


解決、幸せ。

Centos7とPHP7 の LAMP環境を構築する(ApacheのPathは /usr/local/apache/ で)

技術系記事です

yum を使って簡単に開発環境が構築できる現代で、
毎回2,3か所くらい詰まって解決するのが面倒だったので、手順をまとめました。

これで1発OKなはず。


◆前提◆
vagrant で centosが立ち上がっていることを前提としています

◆ゴール◆
local.xxxxx.com などの独自ドメインで phpinfo() が動くこと
apache のパスが /usr/local/apache/ であること
(ソースからapacheをインストールしていた時代のパスのが未だに慣れているため)


f:id:yukibata:20170618132954p:plain


0.事前準備

yumのアップデートを行います

# sudo su root
# yum -y update

1.Apacheのインストール

centOS7 では、2.4がデフォルトみたいです

### apache のインストール
# yum -y install httpd

## vesionの確認
# httpd -v

## サービスの設定と起動
# systemctl enable httpd
# systemctl restart httpd

2. MySQL のインストール

### 事前にリポジトリの追加
# yum -y localinstall http://dev.mysql.com/get/mysql57-community-release-el7-7.noarch.rpm

### MySQL のインストール
# yum -y install mysql-community-server

### version確認
# mysqld --version

### サービス設定と起動
# systemctl enable mysqld.service
# systemctl restart mysqld.service

### passwordの取得
# cat /var/log/mysqld.log | grep "temporary password"
-----------------------------------------------------------------------
2017-06-17T07:05:30.855638Z 1 [Note] A temporary password is generated for root@localhost: {初期パスワード}
-----------------------------------------------------------------------

### passwordの設定(上の{初期パスワード}を使用)
# mysql_secure_installation

### passwordの有効期限設定(デフォルト1年なので、以下を追記して無期限にします)
# vi /etc/my.cnf
-----------------------------------------------------------------------
default_password_lifetime=0
-----------------------------------------------------------------------

### 再起動
# systemctl restart mysqld
 

3. PHP7インストール

### リポジトリインストール
# yum -y install epel-release
# rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

### PHPのインストール
# yum install --enablerepo=remi,remi-php70 php php-devel php-mbstring php-pdo php-gd php-mysql php-xml

### version確認
# php -version


基本はここまでで完了です。
以下、/usr/local/apache/ のパスで動かしたいので、シムリンクで対応します

4. ドメイン設定

### httpd.conf の編集
# cd /etc/httpd/conf
# cp httpd.conf httpd.conf_bk
# vi httpd.conf


編集内容

### ServerNameの設定(編集)
------------------------------------------------------
# ServerName www.example.com:80
ServerName yukibata.com
------------------------------------------------------


### アクセス許可(編集)
------------------------------------------------------
<Directory />
    AllowOverride none
    # Require all denied
    Require all granted
</Directory>
------------------------------------------------------

### ドメイン設定(追記)
------------------------------------------------------
<VirtualHost *:80>
    DocumentRoot /usr/local/apache/vhosts/local.yukibata.com/htdocs
    ServerName local.yukibata.com.com
    ErrorLog vhosts/local.yukibata.com.com/logs/error_log
    TransferLog vhosts/local.yukibata.com.com/logs/access_log
    <Directory />
        Options FollowSymLinks
        AllowOverride All
    </Directory>
    <Directory "/usr/local/apache/vhosts/local.yukibata.com/htdocs">
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>
------------------------------------------------------

5. ドメインに合わせてディレクトリ設定

### ディレクトリ作成と権限
# mkdir -p /usr/local/apache/vhosts/local.yukibata.com/htdocs
# mkdir -p /usr/local/apache/vhosts/local.yukibata.com/logs
# touch /usr/local/apache/vhosts/local.yukibata.com/logs/error_log
# touch /usr/local/apache/vhosts/local.yukibata.com/logs/access_log
# chmod -R 777 /usr/local/apache/vhosts/

### シムリンクを張る
# cd /etc/httpd/
# ln -s /usr/local/apache/vhosts vhosts
# ls -la | grep vhost
---------------------------------------------------------
lrwxrwxrwx   1 root root   24 Jun 18 04:12 vhosts -> /usr/local/apache/vhosts
---------------------------------------------------------

### apache再起動
# systemctl restart httpd.service

6. htdocsにファイルを置いて確認

# echo "<?php phpinfo();" > /usr/local/apache/vhosts/local.yukibata.com/htdocs/test.php

ブラウザから local.yukibata.com/test.php へアクセスして、phpinfo を確認



以上です。
シンプルですが、機械的にやっていけば構築は完了になります。

Apacheが起動しないと思ったら... (98)Address already in use: AH00072: make_sock: could not bind to address [::]:80

技術系です

結論:Apacheの再起動ができないと思ったら、Listenを2回していたという話


f:id:yukibata:20170618124754p:plain

windows アップデートで、開発環境がぶっとんだので、再構築中のことでした

apache 入れて、php 入れて、mysql 入れて、python 入れて、
動作確認OK

バーチャルホスト設定をしていたところ、
Apache再起動時に以下のようなエラー

[root@localhost httpd]# systemctl restart httpd.service
Job for httpd.service failed. See 'systemctl status httpd.service' and 'journalctl -xn' for details.


いわれた通りに、'systemctl status httpd.service' を実行

Jun 18 04:31:42 localhost.localdomain systemd[1]: Starting The Apache HTTP Server...
Jun 18 04:31:42 localhost.localdomain httpd[4077]: (98)Address already in use: AH00072: make_sock: could not bind to address [::]:80
Jun 18 04:31:42 localhost.localdomain systemd[1]: httpd.service: main process exited, code=exited, status=1/FAILURE
Jun 18 04:31:42 localhost.localdomain kill[4079]: kill: cannot find process ""
Jun 18 04:31:42 localhost.localdomain systemd[1]: httpd.service: control process exited, code=exited status=1
Jun 18 04:31:42 localhost.localdomain systemd[1]: Failed to start The Apache HTTP Server.
Jun 18 04:31:42 localhost.localdomain systemd[1]: Unit httpd.service entered failed state.

どうやら、「80ポートが使われている」らしい。


ポートをチェックします

[root@localhost httpd]# netstat -lnp | grep :80
[root@localhost httpd]#

・・・・ない


lsofコマンドで、古いプロセスがないか確認

[root@localhost httpd]# sudo lsof -i | grep http
[root@localhost httpd]#

・・・・ない


プロセスがない、でも「既に使われています」
ということは、1回の起動で2回ロードしようとしていないか疑う

[root@localhost httpd]# cat /etc/httpd/conf/httpd.conf | grep "Listen"                                                                 
# Listen: Allows you to bind Apache to specific IP addresses and/or                                                                     
# Change this to Listen on specific IP addresses as shown below to                                                                      
#Listen 12.34.56.78:80                                                                                                                  
Listen 80                                                                                                                               
Listen 80

いました。 Listen 80 が2個
昔の httpd.conf からコピペした際に、余分に入っちゃった系

1行消して解決しました。

Apacheのマルチドメインが効いていない原因は、httpd.conf だった件

 

Apacheのマルチドメインに関してハマった点を書きます。

 

困ったこと、

「マルチドメイン設定をしたのに、同じページが表示される」

です。

 

f:id:yukibata:20170323224921p:plain

 

元々、

  A:local.yukibata.com/home/

があった状態で、

  B:local.test.yukibata.com/home/

を追加してBにアクセスしてみました。

が、なぜがAが表示されるという問題に遭遇しました。

 

現象を詳しく見てみる

 

まずは、Aである local.yukibata.com/home/ にアクセス。

すると、ちゃんとアクセスログは

/usr/local/apache2/vhosts/local.yukibata.com/logs/access_log ⇒ 来ている

/usr/local/apache2/vhosts/local.test.yukibata.com/logs/access_log ⇒ 来てない

これはOK

 

次に、Bである local.test.yukibata.com/home/ にアクセス。

すると

/usr/local/apache2/vhosts/local.yukibata.com/logs/access_log ⇒ 来ている

/usr/local/apache2/vhosts/local.test.yukibata.com/logs/access_log ⇒ 来てない

これはNG

 

原因は、やっぱり httpd.conf 

 

思い当たる節はほとんどないので、

新鮮な目で httpd.conf を見ていったら、やっぱり原因は httpd.conf だった。

 

f:id:yukibata:20170323223819p:plain

 

(誤)

  ServerName yukibata.com

  ServerName test.yukibata.com

(正)

  ServerName local.yukibata.com

  ServerName local.test.yukibata.com

 

でした。 local が抜けていました。

 

Apacheの挙動として、Virtualhost が複数設定されている場合、

振り分けが不明な場合に、先頭の振り分けに従う

というルールがあるようです。

 

ってことは、

いままで、

local.yukibata.com/home/ にアクセスして、

/usr/local/apache2/vhosts/local.yukibata.com/logs/access_log に来ているからOK

だと思っていた思っていたのは、

実は、全て不明だったから、たまたま先頭の Virtualhost 設定に従っていた

という状態だったんですね。。。

 

 

おわり

 

 

[ImageFrom]

http://manifoldcf.apache.org/ja_JP/

 

 

 

Symfony2.7 カンマ区切りの数値をバリデーションする

Symfony2のお話です。

Symfony2には、デフォルトでバリデーションクラスがあり
「空チェック」や「数値チェック」「emailチェック」などができますが、

どうしても "1,3,4" のようなカンマ区切りでPOSTされた値に
バリデーションをかけたい場合はどうすればよいのか。

今回は、この例を挙げてSymfony2のカスタムバリデーションについてまとめます。


f:id:yukibata:20170129133355j:plain

まずは、既存のバリデーションから


FormのEntityクラスでアノテーションによるバリデーションをかけたい場合、
以下のように記述すると思います。

<?php
namespace BlogBundle\Form\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class ProfileEntity
{
    /**
     * @Assert\NotBlank(
     *     message = "年齢が空です",
     *     groups = {"profile"}
     * )
     * @Assert\Type(
     *     type = "numeric",
     *     message = "年齢が不正です",
     *     groups = {"profile"}
     * )
     * @Assert\Range(
     *     min = 0,
     *     max = 120,
     *     minMessage = "年齢が不正です",
     *     maxMessage = "年齢が不正です",
     *     groups = {"profile"}
     * )
     */
    private $profileAge;

これはユーザが入力した年齢にバリデーションをかける場合の例ですが、

"NotBlank" とか "Type" とか "Range" などは、
既に存在するバリデーションにオプションを渡す感じで実施できるものです。


既に備わっているバリデーションの種類は

 バリデータリファレンス | Symfony2日本語ドキュメント

をご覧ください。


実態はソース的には

 /vendor/symfony/symfony/src/Symfony/Component/Validator/Constraints

にあります。


Constraintsは「制約」という意味ですが、
まぁ「バリデーションの種類」って捉えるくらいでOKです。

アノテーションで @Assert\XXXXX って書くと

/vendor/symfony/symfony/src/Symfony/Component/Validator/Constraints

の下にある XXXXX って名前のバリデーションクラスを探しに行くんですね。


以上が、バリデーションの基本的な仕組みですが

Symfony2が元々用意していたバリデーション以外を実施したい場合は
独自で実装する必要があります。

独自でバリデーションを設定する方法は2つ


どのように独自でバリデーションを用意するかですが
2パターンあります。

1つ目は、callback によるものです。
上記で指定したEntityクラスに callbackメソッドを準備して、
その中で、バリデーションを実施するものです。

ですが、
このやり方は、2つ以上の変数に対してバリデーションを実施するものと
捉えておいた方がいいかもしれません。

ケースとしては

 $foods, $drink の2つに対して
 ⇒ "フードとドリンクは、合わせて3つまでです"

とかです。

2つ目は、カスタムバリデーションクラスを作成するものです
この記事のメイントピックです。

ケースとしては

 $hobbyList に "1,3,4" が入ってくる。

これが、「カンマ区切りの数値であるバリデーションを作成したい」です。

カスタムバリデーションを作成する


カスタムバリデーションの作成ですが、
やることはシンプルです。

Symfony2が元々持っている
Constraint「制約(バリデーションの種類)」に1つ追加してあげる
ということです。

/vendor/symfony/symfony/src/Symfony/Component/Validator/Constraints

に追加してもいいですが、
vendor配下を追加すると、面倒なこともあるので
「明示的に作ったよ」ということがわかるように、
自分で作ったバンドル配下に設置するのがオススメです。(必須じゃない)


では、
いよいよ「カンマ区切りの数値をバリデーションする」クラスを設定してみます。

1. ディレクトリ作成

ここでは、
自分で作った BlogBundle の配下に作ることで進めます。

mkdir -p ./BlogBundle/Component/Validator/Constraints

  BlogBundle
    ∟Component
       ∟Validator
          ∟Constraints

のように、ディレクトリを作ります。
あえて、本家のvendor配下に似せて作ってあります。

あとは、「制約クラス」と「バリデーションクラス」の作成です

2. 制約クラスを作成

vi ./BlogBundle/Component/Validator/Constraints/CommaSeparateInteger.php

<?php

namespace BlogBundle\Component\Validator\Constraints;

use Symfony\Component\Validator\Constraint as BaseConstraint;

/**
 * @Annotation
 * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
 *
 * @author
 **/

class CommaSeparateInteger extends BaseConstraint
{
    public $emptyMessage = '不正な入力があります'; 
    public $message = '不正な入力があります';
}

ここでやっていることは2つです。

1つ目は、Symfony2の Constraint クラスを継承していること

  use Symfony\Component\Validator\Constraint as BaseConstraint;
  class CommaSeparateInteger extends BaseConstraint

の部分ですね。これは必須の作業です。


2つ目は、エラーメッセージを定義していること。
これは、任意ですが、とりあえず

  public $message = '不正な入力があります';

くらいを設定して、後で変更しましょう。

3. バリデーションクラスを作成

vi ./BlogBundle/Component/Validator/Constraints/CommaSeparateIntegerValidator.php

<?php

namespace BlogBundle\Component\Validator\Constraints;

use Symfony\Component\Validator\Constraint as BaseConstraint;
use Symfony\Component\Validator\ConstraintValidator as BaseConstraintValidator;

class CommaSeparateIntegerValidator extends BaseConstraintValidator
{

    public function validate($value, BaseConstraint $constraint)
    {
        // 空っぽの場合
        if (empty($value)) {
            $this->context->addViolation($constraint->emptyMessage);
        }

        // カンマ区切りの数値であるか
        $trimmedValue  = preg_replace("/( | )/", "", $value);
        $values = explode(',', $trimmedValue);
        foreach ($values as $oneValue) {
            if (preg_match("/^[0-9]+$/", $oneValue)) continue;
            $this->context->addViolation($constraint->message);
        }
    }
}

ここでもやっていることは2つです。

1つ目は、ConstraintValidator クラスを継承していること

 use Symfony\Component\Validator\ConstraintValidator as BaseConstraintValidator;
 class CommaSeparateIntegerValidator extends BaseConstraintValidator

の部分ですね。必須作業です。
これを指定しないと、Entityで読み込んでくれません。


2つ目は、validate メソッドを作成していること

 public function validate($value, BaseConstraint $constraint)
 {
 }

の部分です。これも必須作業です。

validate メソッドは未実装メソッドですので、この名前でメソッドを生成してください。
で、実際のバリデーションの内容をこのメソッドに記載していきます。

ここまでで作成は一通り完成ですが、
「バリデーションがOKかNGかは、どのように表現するのか」も合わせて書いておきます。

Symfony2.3とかであれば、true false を返していたようですが、
Symfony2.5以上の場合、

 $this->context->addViolation($constraint->message);

によって「バリデーションに引っかかったよ」という信号を送ることができます。
つまり、validate メソッドが空っぽであれば「常にバリデーションOK」ということになります。


私の例では

 空判定  ⇒ addViolation($constraint->emptyMessage);
 数値判定 ⇒ addViolation($constraint->message);

のようにしています。

4. バリデーションクラスを使うように設定

あとは、作成したバリデーションクラスを実際に Entity から呼び出せばOKです。

<?php
namespace BlogBundle\Form\Entity;

use Symfony\Component\Validator\Constraints as Assert;
use BlogBundle\Component\Validator\Constraints as BlogAssert;

class ProfileEntity
{
    /**
     * @BlogAssert\CommaSeparateInteger(
     *     groups = {"profile"}
     * )
     */
    private $hobbyList;

上記のように
 
 use BlogBundle\Component\Validator\Constraints as BlogAssert;

を指定し、

 @BlogAssert\CommaSeparateInteger(

のような形式で利用することができます。


Symfony2.7の動きとしては

@Assert\xxxxx()
というアノテーションに出くわした際に、
Constraint配下にある「制約クラス名+Validator」という名前を見つけて、
その中の validate メソッドを実行という動きになっているんですね。



今回は、あくまで、どうしてもできない場合のカスタムバリデーションという話ですが
既存のバリデーションクラスも非常に豊富ですので、
やりたいバリデーションをしてくれるクラスが無いか、一度チェックしてみるといいですね。




おわり


[Image From]
http://loveendures.me/validation/