Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
fuel-opauth controller using fixed-opauth package https://github.com/letsspeak/fuel-opauth
<?php
namespace Fuel\Migrations;
class Create_users
{
public function up()
{
\DBUtil::create_table('users', array(
'id' => array('constraint' => 11, 'type' => 'int', 'auto_increment' => true),
'name' => array('constraint' => 255, 'type' => 'varchar', 'null' => true),
'password' => array('constraint' => 255, 'type' => 'varchar', 'null' => true),
'nickname' => array('constraint' => 255, 'type' => 'varchar', 'null' => true),
'email' => array('constraint' => 255, 'type' => 'varchar', 'null' => true),
'last_login' => array('constraint' => 11, 'type' => 'int'),
'created_at' => array('constraint' => 11, 'type' => 'int', 'null' => true),
'updated_at' => array('constraint' => 11, 'type' => 'int', 'null' => true),
), array('id'),
false, 'InnoDB', 'utf8_unicode_ci');
}
public function down()
{
\DBUtil::drop_table('users');
}
}
<?php
namespace Fuel\Migrations;
class Create_twitterusers
{
public function up()
{
\DBUtil::create_table('twitter_users', array(
'id' => array('constraint' => 11, 'type' => 'int', 'auto_increment' => true),
'uid' => array('constraint' => 255, 'type' => 'varchar'),
'token' => array('constraint' => 255, 'type' => 'varchar'),
'secret' => array('constraint' => 255, 'type' => 'varchar'),
'user_id' => array('constraint' => 11, 'type' => 'int'),
'created_at' => array('constraint' => 11, 'type' => 'int', 'null' => true),
'updated_at' => array('constraint' => 11, 'type' => 'int', 'null' => true),
), array('id'),
false, 'InnoDB', 'utf8_unicode_ci');
}
public function down()
{
\DBUtil::drop_table('twitter_users');
}
}
<?php
namespace Fuel\Migrations;
class Create_facebookusers
{
public function up()
{
\DBUtil::create_table('facebook_users', array(
'id' => array('constraint' => 11, 'type' => 'int', 'auto_increment' => true),
'uid' => array('constraint' => 255, 'type' => 'varchar'),
'token' => array('constraint' => 255, 'type' => 'varchar'),
'expires' => array('constraint' => 11, 'type' => 'int'),
'user_id' => array('constraint' => 11, 'type' => 'int'),
'created_at' => array('constraint' => 11, 'type' => 'int', 'null' => true),
'updated_at' => array('constraint' => 11, 'type' => 'int', 'null' => true),
), array('id'),
false, 'InnoDB', 'utf8_unicode_ci');
}
public function down()
{
\DBUtil::drop_table('facebook_users');
}
}
<?php
class Controller_Auth extends Controller
{
private $_config = null;
private $_salt_length = null;
private $_iteration_count = null;
public function before()
{
if(!isset($this->_config))
{
$this->_config = Config::load('opauth', 'opauth');
}
$this->_salt_length = 32;
$this->_iteration_count = 10;
}
// auth/login/*
public function action_login($_provider = null, $method = null)
{
// 引数無し時は通常ログイン
if (is_null($_provider)) return $this->normal_login();
// http://domainname/auth/login/twitter/oauth_callback?denied=signature
if ($method === 'oauth_callback') {
if (Input::get('denied')){
return $this->login_failed();
}
}
if(array_key_exists(Inflector::humanize($_provider), Arr::get($this->_config, 'Strategy')))
{
$_oauth = new Opauth($this->_config, true);
}
else
{
return $this->login_failed();
}
}
// 通常ログイン
public function normal_login()
{
$username = Input::post('username');
$password = Input::post('password');
// ユーザー名とパスワード空欄時はログインフォームを表示する
if (is_null($username) and is_null($password)) {
return Response::forge(View::forge('auth/form'));
}
// 認証
$query = Model_User::query()->where('name', $username);
if ($query->count() === 0){
// 認証エラー
$this->login_failed();
}
// パスワードのハッシュ化
$user = $query->get_one();
$salt = substr($user->password, 0, $this->_salt_length);
$enc_password = $salt.$password;
for ($i = 0; $i < $this->_iteration_count; $i++)
{
$enc_password = sha1($enc_password);
}
if ($user->password === $salt.$enc_password){
// 認証成功
return $this->login_succeeded($user->id);
}else{
// 認証エラー
$this->login_failed();
}
}
public function action_signup()
{
$username = Input::post('username');
$password = Input::post('password');
// ユーザー名とパスワード空欄時はサインアップフォームを表示する
if (is_null($username) and is_null($password)) {
return Response::forge(View::forge('auth/signup'));
}
// サインアップ処理
// パスワードのハッシュ化
$salt = substr(md5(uniqid(rand(), true)), 0, $this->_salt_length);
$enc_password = $salt.$password;
for ($i = 0; $i < $this->_iteration_count; $i++)
{
$enc_password = sha1($enc_password);
}
// バリデーション
$val = Model_User::validate('create');
$input = array(
'name' => $username,
'password' => $salt.$enc_password,
'last_login' => \Date::time()->get_timestamp(),
);
if ($val->run($input))
{
// バリデーション成功時
$user = Model_User::forge($input);
if ($user and $user->save())
{
// サインアップ成功時
return $this->login_succeeded($user->id);
}
else
{
// サインアップ失敗時
return $this->login_failed();
}
}
else
{
// バリデーション失敗時
$data['errors'] = $val->error();
return Response::forge(View::forge('auth/signup', $data));
}
}
public function action_test1()
{
return Response::forge(View::forge('auth/test'));
}
public function action_test2()
{
return Response::forge(View::forge('auth/test'));
}
public function action_test3()
{
return Response::forge(View::forge('auth/test'));
}
// Twitter / Facebook ログイン成功/失敗時に呼ばれる
public function action_callback()
{
$_opauth = new Opauth($this->_config, false);
switch($_opauth->env['callback_transport'])
{
case 'session':
session_start();
$response = $_SESSION['opauth'];
unset($_SESSION['opauth']);
break;
}
if (array_key_exists('error', $response))
{
return $this->login_failed();
// echo '<strong style="color: red;">Authentication error: </strong> Opauth returns error auth response.'."<br>\n";
}
else
{
if (empty($response['auth']) || empty($response['timestamp']) || empty($response['signature']) || empty($response['auth']['provider']) || empty($response['auth']['uid']))
{
return $this->login_failed();
// echo '<strong style="color: red;">Invalid auth response: </strong>Missing key auth response components.'."<br>\n";
}
elseif (!$_opauth->validate(sha1(print_r($response['auth'], true)), $response['timestamp'], $response['signature'], $reason))
{
return $this->login_failed();
// echo '<strong style="color: red;">Invalid auth response: </strong>'.$reason.".<br>\n";
}
else
{
// Twitter / Facebook ログイン成功
return $this->opauth_login($response);
}
}
}
public function opauth_login($response = null)
{
$provider = $response['auth']['provider'];
if ($provider === 'Twitter') return $this->twitter_login($response);
if ($provider === 'Facebook') return $this->facebook_login($response);
}
public function twitter_login($response = null)
{
$uid = (string) $response['auth']['uid'];
$query = Model_TwitterUser::query()->where('uid', $uid);
if ($query->count() == 0)
{
// TwitterUser未登録の場合はサインアップ
return $this->twitter_signup($response);
}
// TwitterUser登録済みの場合はログイン
$twitter_user = $query->get_one();
return $this->login_succeeded($twitter_user->user_id);
}
public function facebook_login($response = null)
{
$uid = $response['auth']['uid'];
$query = Model_FacebookUser::query()->where('uid', $uid);
if ($query->count() == 0)
{
// FacebookUser未登録の場合はサインアップ
return $this->facebook_signup($response);
}
// FacebookUser登録済みの場合はログイン
$facebook_user = $query->get_one();
return $this->login_succeeded($facebook_user->user_id);
}
public function twitter_signup($response = null)
{
// バリデーション
$val = Model_TwitterUser::validate('create');
$input = array(
'uid' => (string) $response['auth']['uid'],
'token' => $response['auth']['credentials']['token'],
'secret' => $response['auth']['credentials']['secret'],
);
if ($val->run($input))
{
// バリデーション成功時
$user = Model_User::forge(array(
'nickname' => $response['auth']['info']['nickname'],
'last_login' => \Date::time()->get_timestamp(),
));
$twitter_user = Model_TwitterUser::forge($input);
if ($user and $twitter_user)
{
// ユーザー生成成功
try
{
\DB::start_transaction();
if ($user->save() === false)
{
// User保存失敗
throw new \Exception('user save failed.');
}
$twitter_user->user_id = $user->id;
if ($twitter_user->save() === false)
{
// TwitterUser保存失敗
throw new \Exception('twitter_user save failed.');
}
// UserとTwitterUserの保存成功
\DB::commit_transaction();
return $this->login_succeeded($user->id);
}
catch (\Exception $e)
{
\DB::rollback_transaction();
return $this->login_failed();
}
}
else
{
// ユーザー生成失敗
return $this->login_failed();
}
}
else
{
// バリデーション失敗時
return $this->login_failed();
}
}
public function facebook_signup($response = null)
{
// バリデーション
$val = Model_FacebookUser::validate('create');
$expires = strtotime($response['auth']['credentials']['expires']);
$input = array(
'uid' => (string) $response['auth']['uid'],
'token' => $response['auth']['credentials']['token'],
'expires' => $expires,
);
if ($val->run($input))
{
// バリデーション成功時
$user = Model_User::forge(array(
'nickname' => $response['auth']['info']['name'],
'last_login' => \Date::time()->get_timestamp(),
));
$facebook_user = Model_FacebookUser::forge($input);
if ($user and $facebook_user)
{
// ユーザー生成成功
try
{
\DB::start_transaction();
if ($user->save() === false)
{
// User保存失敗
throw new \Exception('user save failed.');
}
$facebook_user->user_id = $user->id;
if ($facebook_user->save() === false)
{
// FacebookUser保存失敗
throw new \Exception('facebook_user save failed.');
}
// UserとFacebookUserの保存成功
\DB::commit_transaction();
return $this->login_succeeded($user->id);
}
catch (\Exception $e)
{
\DB::rollback_transaction();
return $this->login_failed();
}
}
else
{
// ユーザー生成失敗
return $this->login_failed();
}
}
else
{
// バリデーション失敗時
return $this->login_failed();
}
}
public function login_succeeded($user_id)
{
Session::set('user_id', $user_id);
Response::redirect('auth/test1');
}
public function login_failed()
{
return Response::redirect('auth/test1');
}
public function action_logout()
{
Session::delete('user_id');
Response::redirect('auth/test1');
}
}
<?php
/**
* Fuel is a fast, lightweight, community driven PHP5 framework.
*
* @package Fuel
* @version 1.5
* @author Fuel Development Team
* @license MIT License
* @copyright 2010 - 2013 Fuel Development Team
* @link http://fuelphp.com
*/
/**
* If you want to override the default configuration, add the keys you
* want to change here, and assign new values to them.
*/
return array(
/**
* base_url - The base URL of the application.
* MUST contain a trailing slash (/)
*
* You can set this to a full or relative URL:
*
* 'base_url' => '/foo/',
* 'base_url' => 'http://foo.com/'
*
* Set this to null to have it automatically detected.
*/
// 'base_url' => null,
/**
* url_suffix - Any suffix that needs to be added to
* URL's generated by Fuel. If the suffix is an extension,
* make sure to include the dot
*
* 'url_suffix' => '.html',
*
* Set this to an empty string if no suffix is used
*/
// 'url_suffix' => '',
/**
* index_file - The name of the main bootstrap file.
*
* Set this to 'index.php if you don't use URL rewriting
*/
// 'index_file' => false,
// 'profiling' => false,
/**
* Default location for the file cache
*/
// 'cache_dir' => APPPATH.'cache/',
/**
* Settings for the file finder cache (the Cache class has it's own config!)
*/
// 'caching' => false,
// 'cache_lifetime' => 3600, // In Seconds
/**
* Callback to use with ob_start(), set this to 'ob_gzhandler' for gzip encoding of output
*/
// 'ob_callback' => null,
// 'errors' => array(
// Which errors should we show, but continue execution? You can add the following:
// E_NOTICE, E_WARNING, E_DEPRECATED, E_STRICT to mimic PHP's default behaviour
// (which is to continue on non-fatal errors). We consider this bad practice.
// 'continue_on' => array(),
// How many errors should we show before we stop showing them? (prevents out-of-memory errors)
// 'throttle' => 10,
// Should notices from Error::notice() be shown?
// 'notices' => true,
// ),
/**
* Localization & internationalization settings
*/
// 'language' => 'en', // Default language
// 'language_fallback' => 'en', // Fallback language when file isn't available for default language
// 'locale' => 'en_US', // PHP set_locale() setting, null to not set
/**
* Internal string encoding charset
*/
// 'encoding' => 'UTF-8',
/**
* DateTime settings
*
* server_gmt_offset in seconds the server offset from gmt timestamp when time() is used
* default_timezone optional, if you want to change the server's default timezone
*/
// 'server_gmt_offset' => 0,
'default_timezone' => 'Asia/Tokyo',
/**
* Logging Threshold. Can be set to any of the following:
*
* Fuel::L_NONE
* Fuel::L_ERROR
* Fuel::L_WARNING
* Fuel::L_DEBUG
* Fuel::L_INFO
* Fuel::L_ALL
*/
// 'log_threshold' => Fuel::L_WARNING,
// 'log_path' => APPPATH.'logs/',
// 'log_date_format' => 'Y-m-d H:i:s',
/**
* Security settings
*/
'security' => array(
// 'csrf_autoload' => false,
// 'csrf_token_key' => 'fuel_csrf_token',
// 'csrf_expiration' => 0,
/**
* This input filter can be any normal PHP function as well as 'xss_clean'
*
* WARNING: Using xss_clean will cause a performance hit.
* How much is dependant on how much input data there is.
*/
'uri_filter' => array('htmlentities'),
/**
* This input filter can be any normal PHP function as well as 'xss_clean'
*
* WARNING: Using xss_clean will cause a performance hit.
* How much is dependant on how much input data there is.
*/
// 'input_filter' => array(),
/**
* This output filter can be any normal PHP function as well as 'xss_clean'
*
* WARNING: Using xss_clean will cause a performance hit.
* How much is dependant on how much input data there is.
*/
'output_filter' => array('Security::htmlentities'),
/**
* Encoding mechanism to use on htmlentities()
*/
// 'htmlentities_flags' => ENT_QUOTES,
/**
* Wether to encode HTML entities as well
*/
// 'htmlentities_double_encode' => false,
/**
* Whether to automatically filter view data
*/
// 'auto_filter_output' => true,
/**
* With output encoding switched on all objects passed will be converted to strings or
* throw exceptions unless they are instances of the classes in this array.
*/
'whitelisted_classes' => array(
'Fuel\\Core\\Response',
'Fuel\\Core\\View',
'Fuel\\Core\\ViewModel',
'Closure',
),
),
/**
* Cookie settings
*/
// 'cookie' => array(
// Number of seconds before the cookie expires
// 'expiration' => 0,
// Restrict the path that the cookie is available to
// 'path' => '/',
// Restrict the domain that the cookie is available to
// 'domain' => null,
// Only transmit cookies over secure connections
// 'secure' => false,
// Only transmit cookies over HTTP, disabling Javascript access
// 'http_only' => false,
// ),
/**
* Validation settings
*/
// 'validation' => array(
/**
* Wether to fallback to global when a value is not found in the input array.
*/
// 'global_input_fallback' => true,
// ),
/**
* Controller class prefix
*/
// 'controller_prefix' => 'Controller_',
/**
* Routing settings
*/
// 'routing' => array(
/**
* Whether URI routing is case sensitive or not
*/
// 'case_sensitive' => true,
/**
* Wether to strip the extension
*/
// 'strip_extension' => true,
// ),
/**
* To enable you to split up your application into modules which can be
* routed by the first uri segment you have to define their basepaths
* here. By default empty, but to use them you can add something
* like this:
* array(APPPATH.'modules'.DS)
*
* Paths MUST end with a directory separator (the DS constant)!
*/
// 'module_paths' => array(
// //APPPATH.'modules'.DS
// ),
/**
* To enable you to split up your additions to the framework, packages are
* used. You can define the basepaths for your packages here. By default
* empty, but to use them you can add something like this:
* array(APPPATH.'modules'.DS)
*
* Paths MUST end with a directory separator (the DS constant)!
*/
// 'package_paths' => array(
// //PKGPATH
// ),
/**************************************************************************/
/* Always Load */
/**************************************************************************/
'always_load' => array(
/**
* These packages are loaded on Fuel's startup.
* You can specify them in the following manner:
*
* array('auth'); // This will assume the packages are in PKGPATH
*
* // Use this format to specify the path to the package explicitly
* array(
* array('auth' => PKGPATH.'auth/')
* );
*/
'packages' => array(
'orm',
'opauth',
),
/**
* These modules are always loaded on Fuel's startup. You can specify them
* in the following manner:
*
* array('module_name');
*
* A path must be set in module_paths for this to work.
*/
// 'modules' => array(),
/**
* Classes to autoload & initialize even when not used
*/
// 'classes' => array(),
/**
* Configs to autoload
*
* Examples: if you want to load 'session' config into a group 'session' you only have to
* add 'session'. If you want to add it to another group (example: 'auth') you have to
* add it like 'session' => 'auth'.
* If you don't want the config in a group use null as groupname.
*/
// 'config' => array(),
/**
* Language files to autoload
*
* Examples: if you want to load 'validation' lang into a group 'validation' you only have to
* add 'validation'. If you want to add it to another group (example: 'forms') you have to
* add it like 'validation' => 'forms'.
* If you don't want the lang in a group use null as groupname.
*/
// 'language' => array(),
),
);
<?php
class Model_FacebookUser extends \Orm\Model
{
protected static $_properties = array(
'id',
'uid',
'token',
'expires',
'user_id',
'created_at',
'updated_at',
);
protected static $_observers = array(
'Orm\Observer_CreatedAt' => array(
'events' => array('before_insert'),
'mysql_timestamp' => false,
),
'Orm\Observer_UpdatedAt' => array(
'events' => array('before_save'),
'mysql_timestamp' => false,
),
);
public static function validate($factory)
{
$val = Validation::forge($factory);
$val->add_callable('MyRules');
$val->add_field('uid', 'uid', 'required')
->add_rule('unique', 'facebook_users.uid');
$val->add_field('token', 'token', 'required');
$val->add_field('expires', 'expires', 'required');
return $val;
}
}
<html>
<body>
Login Form
<form method="POST" action="">
username<input type="text" name="username">
password<input type="text" name="password">
<input type="submit">
</form>
<?php
echo Html::anchor('auth/test1', 'test1').'<br >';
echo Html::anchor('auth/test2', 'test2').'<br >';
echo Html::anchor('auth/test3', 'test3').'<br >';
echo Html::anchor('auth/login', 'login').'<br >';
echo Html::anchor('auth/signup', 'signup').'<br >';
?>
<?php
class MyRules
{
// note this is a static method
public static function _validation_unique($val, $options)
{
list($table, $field) = explode('.', $options);
$result = DB::select("LOWER (\"$field\")")
->where($field, '=', Str::lower($val))
->from($table)->execute();
return ! ($result->count() > 0);
}
// note this is a non-static method
public function _validation_is_upper($val)
{
return $val === strtoupper($val);
}
}
<?php
/**
* Opauth
* Multi-provider authentication framework for PHP
* FuelPHP Package by Andreo Vieira <andreoav@gmail.com>
*
* @copyright Copyright ツゥ 2012 U-Zyn Chua (http://uzyn.com)
* @link http://opauth.org
* @package Opauth
* @license MIT License
*/
namespace Opauth;
/**
* Opauth configuration file with advanced options.
*
* Copy this file to fuel/app/config and tweak as you like.
*/
return array(
/**
* Path whre Fuel-Opauth is accessed.
*
* Begins and ends with /.
* eg. if Opauth is reached via http://example.org/auth/, path is '/auth/'.
* if Opauth is reached via http://auth.example.org/, path is '/'.
*/
'path' => '/auth/login/',
/**
* Uncoment if you would like to view debug messages.
*/
//'debug' => true,
/**
* Callback URL: redirected to after authentication, successful or otherwise.
*
* eg. if Opauth callback is reached via http://example.org/auth/callback, callback_url id '/auth/callback/'
*/
'callback_url' => '/auth/callback/',
/**
* Callback transport, for sending of $auth response.
* 'session' - Default. Works best unless callback_url is on a different domain than Opauth.
* 'post' - Works cross-domain, but relies on availability of client side JavaScript.
* 'get' - Works cross-domain, but may be limited or corrupted by browser URL length limit
* (eg. IE8/IE9 has 2083-char limit).
*/
//'callback_transport' => 'session',
/**
* A random string used for signing of $auth response.
*/
'security_salt' => 'SECURITY_SALT',
/**
* Higher value, better security, slower hashing.
* Lower value, lower security, faster hashing.
*/
//'security_iteration' => 300,
/**
* Time limit for valid $auth response, starting form $auth response generation to validation.
*/
//'security_timeout' => '2 minutes',
/**
* Directory wher Opauth strategies reside.
*/
//'strategy_dir' => PKGPATH . '/Opauth/classes/Strategy/',
/**
* Strategy
* Refer to individual strategy's documentation on configuration requirements.
*
* eg.
* 'Strategy' => array(
* 'Facebook' => array(
* 'app_id' => 'APP_ID',
* 'app_secret' => 'APP_SECRET'
* ),
* )
*/
'Strategy' => array(
// Define strategies and their respective configs here
'Facebook' => array(
'app_id' => 'FACEBOOK_APP_ID',
'app_secret' => 'FACEBOOK_APP_SECRET'
),
'Twitter' => array(
'key' => 'TWITTER_CONSUMER_KEY',
'secret' => 'TWITTER_CONSUMER_SECRET'
),
),
);
<html>
<body>
<?php
$errors = isset($errors) ? $errors : array();
if (count($errors) > 0) {
foreach($errors as $error) {
echo $error.'<br />'.PHP_EOL;
}
}
?>
Signup
<form method="POST" action="">
username<input type="text" name="username">
password<input type="text" name="password">
<input type="submit">
</form>
<?php
echo Html::anchor('auth/test1', 'test1').'<br >';
echo Html::anchor('auth/test2', 'test2').'<br >';
echo Html::anchor('auth/test3', 'test3').'<br >';
echo Html::anchor('auth/login', 'login').'<br >';
echo Html::anchor('auth/signup', 'signup').'<br >';
?>
<html>
<body>
<?php
if (Session::get('user_id')) {
echo 'Hello user_id = '.Session::get('user_id').'!<br />'.PHP_EOL;
}else{
echo 'not logined <br />'.PHP_EOL;
}
echo Html::anchor('auth/test1', 'test1').'<br />'.PHP_EOL;
echo Html::anchor('auth/test2', 'test2').'<br />'.PHP_EOL;
echo Html::anchor('auth/test3', 'test3').'<br />'.PHP_EOL;
echo Html::anchor('auth/login', 'login').'<br />'.PHP_EOL;
echo Html::anchor('auth/signup', 'signup').'<br />'.PHP_EOL;
echo Html::anchor('auth/login/twitter/', 'twitter').'<br />'.PHP_EOL;
echo Html::anchor('auth/login/facebook/', 'facebook').'<br />'.PHP_EOL;
echo Html::anchor('auth/logout', 'logout').'<br />'.PHP_EOL;
?>
</body>
</html>
<?php
class Model_TwitterUser extends \Orm\Model
{
protected static $_properties = array(
'id',
'uid',
'token',
'secret',
'user_id',
'created_at',
'updated_at',
);
protected static $_observers = array(
'Orm\Observer_CreatedAt' => array(
'events' => array('before_insert'),
'mysql_timestamp' => false,
),
'Orm\Observer_UpdatedAt' => array(
'events' => array('before_save'),
'mysql_timestamp' => false,
),
);
public static function validate($factory)
{
$val = Validation::forge($factory);
$val->add_callable('MyRules');
$val->add_field('uid', 'uid', 'required')
->add_rule('unique', 'twitter_users.uid');
$val->add_field('token', 'token', 'required');
$val->add_field('secret', 'secret', 'required');
return $val;
}
}
<?php
class Model_User extends \Orm\Model
{
protected static $_properties = array(
'id',
'name',
'password',
'nickname',
'email',
'last_login',
'created_at',
'updated_at',
);
protected static $_observers = array(
'Orm\Observer_CreatedAt' => array(
'events' => array('before_insert'),
'mysql_timestamp' => false,
),
'Orm\Observer_UpdatedAt' => array(
'events' => array('before_save'),
'mysql_timestamp' => false,
),
);
public static function validate($factory)
{
$val = Validation::forge($factory);
$val->add_callable('MyRules');
$val->add_field('name', 'ユーザー名', 'required')
->add_rule('unique', 'users.name');
$val->add_field('password', 'パスワード', 'required');
return $val;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.