Symfony 3 のインストールと基本構成

この記事では Symfony 3.2 のインストールと重要なフォルダの階層、ルーティングの基本などについて説明します。

インストーラーの準備

Mac, Linux

下記のようにして Symfony のインストーラーをダウンロード、インストールします。

 $ sudo mkdir -p /usr/local/bin
 $ sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony
 $ sudo chmod a+x /usr/local/bin/symfony
 

プロジェクトの作成は次のようにします。my_project_name の部分はプロジェクトのフォルダ名になりますので好きな名前をつけて下さい。

$ symfony new my_project_name

Windows

Windows の場合次のようにして実行ファイルをダウンロードします。

php -r "readfile('https://symfony.com/installer');" > symfony

ダウンロードした symfony というファイルをプロジェクトのフォルダに移動し「php symfony」のようにして使います。

プロジェクトのルートフォルダに移動しプロジェクトを作成します。
my_project_name の部分はプロジェクトのフォルダ名になりますので好きな名前をつけて下さい。

C:¥> cd projects
C:¥projects¥> php symfony new my_project_name

Composer を使う

PHP のバージョンが古かったり、Symfony のインストーラーが使えない場合は Composer を使ってインストールします。

composer create-project symfony/framework-standard-edition my_project_name

インストールされる主要コンポーネント

Symfony 3 にはデータベースまわりを扱う Doctrine ORM、メール送信のための Swift Mailer、テンプレートエンジンの Twig などが予めインストールされています。

主要ファイル構成

各種設定ファイルは app/config にまとめられています。
設定は YAML 形式のファイルで保存されており、ロケールやメールサーバー情報、データベースの接続情報などはここで編集します。

コントローラーは src/AppBundle/Controller にあります。
ホームページを表示するための DefaultController.php が見本として用意されています。

テンプレートファイルは app/Resources/views にあります。
テンプレートエンジンには Twig が使われています。

パブリックフォルダの名前は web です。独自ドメインもこのフォルダに割当てます。
画像や CSS、JS などのアセットはここに配置します。

ルーティング

ルーティングの指定方法は app/config/routing.yml で変更できますが、デフォルトではコントローラーでのアノテーション(コメント)が利用されます。

	/**
     * @Route("/api/posts/{id}")
     * @Method({"GET","HEAD"})
     */
    public function showAction($id)
    {
        //
    }

それぞれのビューコントローラーのメソッドの上にアノテーションとしてルーティングを記述します。
@Route にはパスを定義します {id} のように括弧で囲むと引数として受け取れるようになります。
@Method は HTTP メソッドを指定するために使います。デフォルトでは全てのメソッドが受け入れられる状態です。
@Method(“PUT”) のようにすると PUT メソッドのみに反応するようにできます。複数のメソッドに対応する場合 {“GET”,”HEAD”} のように波括弧を用いて GET か HEAD のときにみに限定できます。
コントローラーメソッドの命名は indexAction(), showAction(), editAction() のように 役割 + Action() の形にします。

パスの書式を指定したり、ルートに名前を付ける場合は次のようにします。

/**
 * @Route("/blog/{page}", name="blog_list", requirements={"page": "\d+"})
 */

name はルートの名前です。アプリケーション全体でユニークな名前にして下さい。
名前をつけておくと URL の出力が簡単になります。
requirements は正規表現を使ったルールです。
上記の例では {page} が数字であることが条件となっています。

デバッグ

web/app_dev.php にアクセスするとデバッグモードになります。
IPアドレスによる制限がかかっているため、Vagrant を使用している場合などでアクセス元が 127.0.0.1 でない場合 app_dev.php 内の in_array() に許可する IP を追加します。

!(in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])

ビューのレンダリング

コントローラーで直接レスポンスを出力するには次のようにします。

return new Response( "Hello, World!" );

Twig テンプレートを使ってレンダリングする場合はこのようになります。

return $this->render('default/index.html.twig', ['key' => 'value']);

Twig の詳細に関しては割愛しますが render() の第1引数にテンプレートファイル名を指定し、第2引数にテンプレートに渡す変数とその内容を連想配列の形で記述します。配列のキー名がそのまま変数名として渡されます。

[PHP]クラスメソッドをキャメルケース、スネークケースのどちらでも呼び出せるようにする

PHP のクラスメソッドは基本的にキャメルケース(camelCase)で命名されることが多いのですが、プロジェクトによっては単語をアンダーバーで区切るスネークケース(snake_case)が使われることもあります。
どちらにするかは自由ですが、プロジェクト全体で統一するのが望ましいとされています。

自分で作ったクラスをほかのプロジェクトでも再利用する場合、必要に応じて命名記法を書き換える事がありますが、マジックメソッドの __call(), __callStatic() を利用すると存在しない存在しない名前のメソッドが呼び出されようとしたときの処理を定義できるため、キャメルケース、スネークケースのどちらで呼び出されても同じメソッドが実行される仕組みを作ることができます。

<?php
class Example
{
    public function __call($name, $args) {
        $name = str_replace('_', '', ucwords($name, '_'));
	    $name[0] = strtolower($name[0]);
        return call_user_func_array([$this, $name], $args);
    }

    public static function __callStatic($name, $args) {
        $name = str_replace('_', '', ucwords($name, '_'));
	    $name[0] = strtolower($name[0]);
        return call_user_func_array([self::class, $name], $args);
    }

    public function exampleMethod($str) {
        return "hello, " . $str;
    }

    static public function staticMethod($str){
        return "hello, " . $str;
    }
}

メソッドの呼び出し

$example = new Example();

echo $example->exampleMethod("World");
echo $example->example_method("World");

echo Example::staticMethod("World");
echo Example::static_method("World");
実行結果: Hello, World

[PHP]画像ファイルのアップロードとGDを使ったリサイズ

GD を使って画像のリサイズを行う場合は imagecopyresampled() を使うのですが、PNG や GIF などで透過情報を保つ場合は工夫が必要です。
そこでアップロードされた画像を指定したサイズに収まるようにリサイズする関数を作ってみました。

<?php
function uploadImage($tmpName, $dir, $maxWidth, $maxHeight){

    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = $finfo->file($tmpName);

    if($mime == 'image/jpeg' || $mime == 'image/pjpeg'){
        $ext = '.jpg';
        $image1 = imagecreatefromjpeg($tmpName);
    } elseif($mime == 'image/png' || $mime == 'image/x-png'){
        $ext = '.png';
        $image1 = imagecreatefrompng($tmpName);
    } elseif($mime == 'image/gif'){
        $ext = '.gif';
        $image1 = imagecreatefromgif($tmpName);
    } else {
        return false;
    }
    
    list($width1, $height1) = getimagesize($tmpName);

    if($width1 <= $maxWidth && $height1 <= $maxHeight){
        $scale = 1.0;
    } else {
        $scale = min($maxWidth / $width1, $maxHeight / $height1);
    }

    $width2 = $width1 * $scale;
    $height2 = $height1 * $scale;

    $image2 = imagecreatetruecolor($width2, $height2);

    if($ext == '.gif'){
        $transparent1 = imagecolortransparent($image1);
        if($transparent1 >= 0){
            $index = imagecolorsforindex($image1, $transparent1);
            $transparent2 = imagecolorallocate($image2, $index['red'], $index['green'], $index['blue']);
            imagefill($image2, 0, 0, $transparent2);
            imagecolortransparent($image2, $transparent2);
        }
    } elseif($ext == '.png'){
        imagealphablending($image2, false);
        $transparent = imagecolorallocatealpha($image2, 0, 0, 0, 127);
        imagefill($image2, 0, 0, $transparent);
        imagesavealpha($image2, true);
    }

    imagecopyresampled($image2, $image1, 0, 0, 0, 0, $width2, $height2, $width1, $height1);

    if(!file_exists($dir)){
        mkdir($dir, 0777, true);
    }

    $filename = sha1(microtime() . $_SERVER['REMOTE_ADDR'] . $tmpName) . $ext;
    $saveTo = rtrim($dir, '/\\') . '/' . $filename;

    if($ext == '.jpg'){
        $quality = 80;
        imagejpeg($image2, $saveTo, $quality);
    } else if($ext == '.png'){
        imagepng($image2, $saveTo);
    } else if($ext == '.gif'){
        imagegif($image2, $saveTo);
    }

    imagedestroy($image1);
    imagedestroy($image2);

    return $saveTo;
}

if($_SERVER["REQUEST_METHOD"] === 'POST'
    && !empty($_FILES['image']['tmp_name']))
{
    $now = new DateTime();

    $maxWidth = 300;    // 最大幅
    $maxHeight = 300;   // 最大高さ

    // 一時ファイルの場所
    $tmpName = $_FILES['image']['tmp_name'];

    // 保存先のディレクトリ
    $dir = __DIR__ . '/files/' . $now->format('Y/m/d');
    $path = uploadImage($tmpName, $dir, $maxWidth, $maxHeight);
    var_dump($path);
    exit;
}
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>image</title>
    </head>
    <body>
        <form method="POST" enctype="multipart/form-data">
            <input type="file" name="image">
            <input type="submit" value="submit">
        </form> 
    </body>
</html>

リサイズに成功すると保存先のファイルパスを返します。失敗した場合は false を返します。
拡張子偽装の可能性を考慮して finfo で MIME type を調べるようにしています。