2012年10月11日木曜日

Zend Framework 2 でのログイン機能

前回に引き続き、ログイン機能を実装します。

まずはログインフォームを作成します。前回同様に、まずはフォームを作成します。

<?php

//module/Auth/src/Auth/Form/Login.php

namespace Auth\Form;

use Zend\Form\Form;
use Zend\Form\Element;

class LoginForm extends Form
{
    public function __construct()
    { 
        parent::__construct();
        $this->add(array(
            'name' => 'email',
            'attributes' => array(
                'type' => 'text',
            ),
            'options' => array(
                'label' => 'email',
            ),
        ));
        $this->add(array(
            'name' => 'password',
            'attributes' => array(
                'type' => 'password',
            ),
            'options' => array(
                'label' => 'password',
            ),
        ));
        $this->add(array(
            'name' => 'submit',
            'attributes' => array(
                'type' => 'submit',
                'value' => 'login',
            ),
        ));
        $this->add(new Element\Csrf('csrf'));
    }

}

続いてこちらも前回を踏襲し、コントローラーを以下のようにします。

<?php
//module/Auth/src/Auth/Controller/AuthController.php

//追加
use Auth\Form\LoginForm;

    //追加
    public function loginAction()
    { 
        $form = new LoginForm();
        $request = $this->getRequest();

        if($request->isPost()){
            $user = new User();
            $form->setInputFilter($user->getInputFilter());
            $form->setData($request->getPost());

            if($form->isValid()) {
                exit('ログインが試みられました');
            }
        }

        return array('form' => $form);
    }




最後にビューを変更します。



 <?php

$title = 'signup form';
$this->headTitle($title);

$form = $this->form;
echo $this->form()->openTag($form);
echo $this->formRow($form->get('email'));
echo $this->formRow($form->get('password'));
echo $this->formHidden($form->get('csrf'));
echo $this->formSubmit($form->get('submit'));
echo $this->form()->closeTag($form);
?>

これでログインフォームが出現すると思います。

それではログインフォームを実装しましょう。Zend Framework 2 の認証機能は前作からほぼ変わらず、インターフェイスを継承したアダプタークラスが
実行します。LDAPを用いる場合と同様に、データベースでの認証もAdapterInterfaceを実装したDbTableクラスのAuthenticationメソッドで実行されます。これは認証機能なので、実際にデータベースに接続するにはデータベースと接続するアダプターを利用することになります。

ということで、まずはデータベースと接続するアダプターを取得出来るようにしましょう。コントローラーの中にgetDbAdapterメソッドを実装します。

<?php
 //module/Auth/src/Auth/Controller/AuthController.php

//追加
protected $dbAdapter;

//追加
    public function getDbAdapter()
    {
        if(!$this->dbAdapter){
            $sm = $this->getServiceLocator();
            $this->dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
        }
        return $this->dbAdapter;
    }



このメソッドを用いてデータベースとの接続を行い、認証メソッドを実行します。
以下、利用法です。

<?php
//module/Auth/src/Auth/Controller/AuthController.php

 //追加
use Zend\Authentication\Adapter\DbTable as AuthAdapter;
use Zend\Authentication\AuthenticationService;

//追加
     public function loginAction()
    {
        $form = new LoginForm();
        $request = $this->getRequest();

        if($request->isPost()){
            $user = new User();
            $form->setInputFilter($user->getInputFilter());
            $form->setData($request->getPost());

            if($form->isValid()) {
                $dbAdapter = $this->getDbAdapter();
                $authAdapter = new AuthAdapter($dbAdapter);

                $userData = $form->getData();
                $authAdapter->setTableName('user') //対応するテーブル名                            ->setIdentityColumn('email') //ユニークなカラム名
                           ->setCredentialColumn('password'); //パスワードカラム名
                $authAdapter->setIdentity($userData['email']) //受け取ったデータ
                            ->setCredential($userData['password']);
                $auth = new AuthenticationService(); //必須ではないがAuthenticationServiceを利用
                $result = $auth->authenticate($authAdapter);
                exit(var_dump($result->isValid()));
            }
        }

こんな感じで実装出来ます。

2012年10月9日火曜日

Zend Framework 2 でのユーザー登録機能の実装

前回の投稿に引き続き、今回はユーザー登録機能の実装を行います。前回の投稿で以下のようなフォルダ階層となっていると思います。

 Auth/
|-- Module.php
|-- config
|   `-- module.config.php
|-- src
|   `-- Auth
|       `-- Controller
|           `-- AuthController.php
`-- view
    `-- auth
        `-- auth
            |-- login.phtml
            `-- signup.phtml

ここから登録機能の実装=>ログイン機能の実装という流れで行っていきます。

まずは登録機能です。ここではメールアドレス・パスワードでの登録を行っていきます。

それでは登録フォームの作成です。登録フォームはFormディレクトリを作成し、src/Authフォルダの中に登録フォルダを作成してその中で実装していきます。以下のファイルをFormディレクトリの中にRegisterForm.phpとして保存します。

<?php
//module/Auth/src/Auth/Form/RegisterForm.php

namespace Auth\Form;

use Zend\Form\Form;
use Zend\Form\Element;  //csrf用のタグの出力に利用

class RegisterForm extends Form
{
        public function __construct()
        {
                parent::__construct();
                $this->add(array(
                        'name' => 'email',
                        'attributes' => array(
                                'type' => 'text',
                        ),
                        'options' => array(
                                'label' => 'email',
                        ),
                ));
                $this->add(array(
                        'name' => 'password',
                        'attributes' => array(
                                'type' => 'password',
                        ),
                        'options' => array(
                                'label' => 'password',
                        ),
                ));
                $this->add(array(
                        'name' => 'submit',
                        'attributes' => array(
                                'type' => 'submit',
                                'value' => 'signup'
                        ),
                ));
                $this->add(new Element\Csrf('csrf'));
        }

}

これをコントローラーで呼び出し、ビューに渡します。

まずはコントローラーです。

<?php
//module/Auth/src/Auth/Controller/AuthController.php

namespace Auth\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Auth\Form\RegisterForm;

class AuthController extends AbstractActionController
{

    public function signupAction()
    {
        $form = new RegisterForm();

        return array('form' => $form);
    }

    public function loginAction()
    {

    }
}

次にビューです。

<?php
//module/Auth/view/auth/auth/signup.phtml

$title = 'signup form';
$this->headTitle($title);

$form = $this->form;
echo $this->form()->openTag($form);
echo $this->formRow($form->get('email'));
echo $this->formRow($form->get('password'));
echo $this->formHidden($form->get('csrf'));
echo $this->formSubmit($form->get('submit'));
echo $this->form()->closeTag($form);
?>

これでdomain.com/signup.phtmlにアクセスすると登録フォームが表示されると思います。これをモデルで受け取り、保存していきます。

まずはコントローラーの処理を加え、リクエストメソッドがポストされている場合にはDBに保存しましょう。

 Zend Framework 2でも、以前のバージョンのように実体を表すクラスとそれをオブジェクト表現するマッパーを組み合わせてモデルを実装することが出来ます。いずれDoctrine等のORMを用いるとして、今回はこれで実装します。

DBですが、今回は簡易に以下のカラムを利用します。DBはこのように作成してください。
  id: mediumint(9) NOT NULL AUTO_INCREMENT
  email: varchar(255) DEFAULT NULL
  password: varchar(20) DEFAULT NULL

コントローラーのsignupアクションを以下のように変更します。

<?php
//module/Auth/src/Auth/Controller/AuthController.php

//メンバ変数を加えます
protected $userTable;

//signupAction, getUserTableメソッドを実装します
    public function signupAction()
    {
        $form = new RegisterForm();
        $request = $this->getRequest();

        if($reqiest->isPost()){
            $user = new User();
            $user->exchangeArray($form->getData());
            $this->getUserTable()->registerUser($user);
        }

        return array('form' => $form);
    }
    private function getUserTable()
    {
        if(!$this->userTable) {
            $sm = $this->getServiceLocator();
            $this->userTable = $sm->get('Auth\Model\UserTable');
        }
        return $this->userTable;
    }


ここで、新しいクラスを2つ利用しています。UserクラスとUserTableクラスです。Userクラスは具体的なデータに対応するクラス、UserTableクラスは実際にDBとオブジェクトをつなぐクラスです。

上記のexhangeArrayメソッドでユーザークラスのメンバ変数に受け取ったデータを格納し、ユーザークラスを引数として、UserTableクラスのregisterUserメソッドでデータをDBに格納します。

それではUserクラスを作成します。以下のコードをUser.phpとしてsrc以下にmodelフォルダを作成して保存してください。

<?php
 // module/Auth/src/Auth/Model/User.php

namespace Auth\Model;

class User implements InputFilterAwareInterface
{
    public $email;
    public $password;

    public function exchangeArray(array $data)
    {
        $this->email = (isset($data['email'])) ? $data['email'] : null;
        $this->password = (isset($data['password'])) ? $data['password'] : null;
    }
}

ここで、exhangeArrayメソッドは配列型のデータを受け取り、メンバ変数に格納しています。
このクラスのインスタンスをUserTableに渡し、UserTableのregisterUserメソッドが実際にDBに保存します。

それではUserTableクラスの作成に移りましょう。ここで、上記のコントローラーではUserTableクラスを$this->getUserTable()として呼び出しています。getUserTableメソッドはサービスマネージャーに登録されたAuth\Model\UserTableというサービスを呼び出しています。

このままだとサービスマネージャーはこのサービスを呼び出せません。Module.phpにサービスあらかじめ登録します。

 <?php
//module/Auth/Module.php

//以下を追加します
use Auth\Model\UserTable;

//以下を追加します
    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'Auth\Model\UserTable' => function($serviceManager) {
                    $dbAdapter = $serviceManager->get('Zend\Db\Adapter\Adapter');
                    $table = new UserTable($dbAdapter);
                    return $table;
                }
            )
        );
    }

UserTableクラスを実装します。
<?php
//module/Auth/src/Auth/Model/UserTable.php

namespace Auth\Model;

use Zend\Db\Adapter\Adapter;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\AbstractTableGateway;

class UserTable extends AbstractTableGateway
{
    protected $table = 'user';

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;

        $this->resultSetPrototype = new ResultSet();
        $this->resultSetPrototype->setArrayObjectPrototype(new User());

        $this->initialize();
    }

    public function registerUser(User $user)
    {
        $data = array(
            'email'     => $user->email,
            'password'  => $user->password,
        );

        $this->insert($data);
    }
}

詳細は割愛しますが、AbstractTableGatewayクラスを継承することでinsertメソッドからDBにデータを挿入できます。

最後にデータベースとの接続情報をファイルに書き込みます。この設定はモジュールディレクトリの外でグローバルに設定します。

configディレクトリ以下のautoloadディレクトリ内にあるglobal.phpファイルを以下のように書き換えます。

//config/autoload/global.php
    'db' => array(
        'driver'            => 'Pdo',
        'dsn'               => 'mysql:dbname=sukina_databasename;host=localhost',
        'driver_options'    => array(
            PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
        ),
    ),

また、local.phpファイルも加えます。

<?php

//config/autoload/local.php
return array(
    'db' => array(
        'username' => 'root',
        'password' => 'YesYourMajesty',
    ),
);

これで、一通り作成が完了しますが、この時点ではコントローラー内でのgetDataメソッドの使用方法が正しくありません。終わりにバリデーションを付けて完成させましょう。

コントローラーの修正です。
//module/Auth/src/Auth/Controller/AuthController.php
    public function signupAction()
    {
        $form = new RegisterForm();
        $request = $this->getRequest();

        if($request->isPost()){
            $user = new User();
            $form->setInputFilter($user->getInputFilter());
            $form->setData($request->getPost());

            if($form->isValid()) {
                $user->exchangeArray($form->getData());
                $this->getUserTable()->registerUser($user);
                exit('登録完了!以下の処理保留');
            }
        }

        return array('form' => $form);
    }

実体を表しているモデルのUserクラスのなかにバリデーションルールを書き込みます。

//module/Auth/src/Auth/Model/User.php

//クラスの利用
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\Factory as InputFactory;

    //メンバ変数の追加
    private $inputFilter;
    public function setInputFilter(InputFilterInterface $inputFilter)
    {
        throw new \Excepion("Not used");
    }
    public function getInputFilter()
    {
        if(!$this->inputFilter) {
            $inputFilter = new InputFilter();
            $factory = new InputFactory();

            $inputFilter->add($factory->createInput(array(
                'name'      => 'email',
                'required'  => true,
                'filters'   => array(
                    array('name'    => 'StripTags'),
                    array('name'    => 'StringTrim'),
                ),
                'validators'    => array(
                    array(
                        'name'  => 'StringLength',
                        'options'   => array(
                            'encoding'  => 'UTF-8',
                            'min'   => '7',
                            'max'   => '255',
                        ),
                    ),
                ),
            )));

            $inputFilter->add($factory->createInput(array(
                'name'      => 'password',
                'required'  => true,
                'filters'   => array(
                    array('name'    => 'StripTags'),
                    array('name'    => 'StringTrim'),
                ),
                'validators'    => array(
                    array(
                        'name'  => 'StringLength',
                        'options'   => array(
                            'encoding'  => 'UTF-8',
                            'min'   => '6',
                            'max'   => '20',
                        ),
                    ),
                ),
            )));

            $this->inputFilter = $inputFilter;
        }

        return $this->inputFilter;
    }

これでポストするとDBへの登録が出来ます。パスワードのハッシュ化等は好きなタイミングで行ってください。

Zend Framework 2 でのモジュールの追加

Zend Framework 2 が正式にリリースされましたが、日本語ドキュメントが全くないため、とりあえずモジュールの追加・ルーティング・認証機能(Authentication)の実装までまとめます。

ベースはSkelton Applicationを使用しているので、 https://github.com/zendframework/ZendSkeletonApplication からダウンロードしてください。

まずはモジュールの追加(ここに追加されたモジュール名と一致するモジュールがmoduleディレクトリから読み込みまれます)

<?php
//config/application.config.php
return array(
    'modules' => array(
        'Application',
        'Auth', //モジュール名を追加
    ),
    'module_listener_options' => array(
        'config_glob_paths' => array(
            'config/autoload/{,*.}{global,local}.php',
        ),
        'module_paths' => array(
            './module',
            './vendor',
        ),
    ),
);


moduleディレクトリのなかに先ほど追加したモジュール名のディレクトリを作成し、その中にModule.phpを作成します。
また、モジュールディレクトリの中はModule.phpというファイルを除き、ある程度自由に配置出来ますが、今回は公式ドキュメントと同様に以下のようにディレクトリを配置します。また、この構造はフレームワークによって決められているものではないため、このように配置するための設定も書き込んでいくことになります。

Auth/
|-- Module.php
|-- config <= 設定ファイルのフォルダ
|-- src <= controller, model等を格納するフォルダ
`-- view <= viewファイルを格納するフォルダ


まずは Module.phpです
<?php
//module/Auth/Module.php

namespace Auth; //モジュール名を名前空間とする

class Module
{
        public function getAutoloaderConfig()
        {
                return  array(
                        //srcフォルダ(公式ドキュメントと同様にsrcフォルダ内にモジュール名のフォルダを作成し、その中をオートロードするように設定)
                        'Zend\Loader\StandardAutoloader' => array(
                                'namespaces' => array(
                                        __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                                ),
                        ),
                );
        }


        public function getConfig()
        {
                //configファイルの配置
                return include __DIR__ . '/config/module.config.php';
        }
}

Module.phpで設定したconfigファイルに具体的な設定を書き込んでいきます。

<?php
//module/Auth/config/module.config.php
return array(
    //Controllerを利用可能にする
    'controllers' => array(
        'invokables' => array(
            'Auth\Controller\Auth' => 'Auth\Controller\AuthController'
        ),
    ),
    //viewフォルダを登録する
    'view_manager' => array(
        'template_path_stack' => array(
            'auth' => __DIR__ . '/../view',
        ),
    ),
);

ここまででフレームワークに上述したフォルダ構造を伝えきり、コントローラも1つ利用できるようになったと思います。

ここから、signupとloginページの作成に入ります。

まずはdomain.com/signupとdomain.com/loginというURLでアクセス出来るようルーティングの設定をします。
module.config.phpに追記します。

<?php
//module/Auth/config/module.config.php
return array(
    'controllers' => array(
        'invokables' => array(
            'Auth\Controller\Auth' => 'Auth\Controller\AuthController'
        ),
    ),
    //以下を追記
    'router' => array( 
        'routes' => array(
            'signup' => array( //自由にルーティング名を記述
                'type'  => 'literal', //ルーティングタイプ(完全一致で指定する場合はliteralと記述)
                'options' => array(
                    'route' => '/signup', //一致するURL
                    'defaults' => array(
                        'controller' => 'Auth\Controller\Auth', //Controller名
                        'action'     => 'signup', //アクション名
                    ),
                ),
            ),
            'login' => array( //同様
                'type'  => 'literal',
                'options' => array(
                    'route' => '/login',
                    'defaults' => array(
                        'controller' => 'Auth\Controller\Auth',
                        'action'     => 'login',
                    ),
                ),
            ),
        ),
    ),
    //ここまで
    'view_manager' => array(
        'template_path_stack' => array(
            'auth' => __DIR__ . '/../view',
        ),
    ),
);

この状態でルーティングが機能するため、以下の指定されたコントローラーのメソッド(アクション)が呼び出されます。上記で利用を宣言したコントローラーを作成し、以下のように記述


<?php
//module/Auth/src/Auth/Controller/AuthController.php

namespace Auth\Controller;

use Zend\Mvc\Controller\AbstractActionController;

class AuthController extends AbstractActionController
{

    public function signupAction()
    {

    }

    public function loginAction()
    {

    }
}

メソッドを実装すれば、あとはviewファイルを作成することでとりあえずアクセスできるようになります。

<?php
//module/Auth/view/auth/auth/login.phtml, module/Auth/view/auth/auth/signup.php
ehcho 'hello zf2!';

これでdomain.com/signup, domain.com/loginにアクセスし、ビューファイルの内容を表示できると思います。

続いてログイン機能の実装です。少し長くなったのでページを改めます。