Seeding database - [ErrorException] Trying to get property of non-object - php

I am using "zizaco/confide": "~4.0#dev" and "zizaco/entrust": "1.2.*#dev".
I have set everything up as described in the two tutorials(confide migrations). Furthermore, I have created the following models:
User:
<?php
use Zizaco\Confide\ConfideUser;
use Zizaco\Confide\Confide;
use Zizaco\Confide\ConfideEloquentRepository;
use Zizaco\Entrust\HasRole;
use Carbon\Carbon;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface{
use ConfideUser;
use HasRole;
/**
* Get user by username
* #param $username
* #return mixed
*/
public function getUserByUsername( $username )
{
return $this->where('username', '=', $username)->first();
}
public function joined()
{
return String::date(Carbon::createFromFormat('Y-n-j G:i:s', $this->created_at));
}
public function saveRoles($inputRoles)
{
if(! empty($inputRoles)) {
$this->roles()->sync($inputRoles);
} else {
$this->roles()->detach();
}
}
public function currentRoleIds()
{
$roles = $this->roles;
$roleIds = false;
if( !empty( $roles ) ) {
$roleIds = array();
foreach( $roles as &$role )
{
$roleIds[] = $role->id;
}
}
return $roleIds;
}
public static function checkAuthAndRedirect($redirect, $ifValid=false)
{
// Get the user information
$user = Auth::user();
$redirectTo = false;
if(empty($user->id) && ! $ifValid) // Not logged in redirect, set session.
{
Session::put('loginRedirect', $redirect);
$redirectTo = Redirect::to('user/login')
->with( 'notice', Lang::get('user/user.login_first') );
}
elseif(!empty($user->id) && $ifValid) // Valid user, we want to redirect.
{
$redirectTo = Redirect::to($redirect);
}
return array($user, $redirectTo);
}
public function currentUser()
{
return (new Confide(new ConfideEloquentRepository()))->user();
}
public function getReminderEmail()
{
return $this->email;
}
}
Role:
<?php
use Zizaco\Entrust\EntrustRole;
class Role extends EntrustRole {
public function validateRoles( array $roles )
{
$user = Confide::user();
$roleValidation = new stdClass();
foreach( $roles as $role )
{
// Make sure theres a valid user, then check role.
$roleValidation->$role = ( empty($user) ? false : $user->hasRole($role) );
}
return $roleValidation;
}
}
Permission:
<?php
use Zizaco\Entrust\EntrustPermission;
class Permission extends EntrustPermission
{
public function preparePermissionsForDisplay($permissions)
{
// Get all the available permissions
$availablePermissions = $this->all()->toArray();
foreach($permissions as &$permission) {
array_walk($availablePermissions, function(&$value) use(&$permission){
if($permission->name == $value['name']) {
$value['checked'] = true;
}
});
}
return $availablePermissions;
}
/**
* Convert from input array to savable array.
* #param $permissions
* #return array
*/
public function preparePermissionsForSave( $permissions )
{
$availablePermissions = $this->all()->toArray();
$preparedPermissions = array();
foreach( $permissions as $permission => $value )
{
// If checkbox is selected
if( $value == '1' )
{
// If permission exists
array_walk($availablePermissions, function(&$value) use($permission, &$preparedPermissions){
if($permission == (int)$value['id']) {
$preparedPermissions[] = $permission;
}
});
}
}
return $preparedPermissions;
}
}
Furthermore, I would like to seed my database in the beginning with values, therefore I created several seeders for user, role and permission. However, I get an error in my permission seeder:
UserTableSeeder:
<?php
class UsersTableSeeder extends Seeder {
public function run()
{
DB::table('users')->delete();
$users = array(
array(
'username' => 'admin',
'email' => 'admin#example.org',
'password' => Hash::make('admin'),
'confirmed' => 1,
'confirmation_code' => md5(microtime().Config::get('app.key')),
'created_at' => new DateTime,
'updated_at' => new DateTime,
),
array(
'username' => 'moderator',
'email' => 'moderator#example.org',
'password' => Hash::make('moderator'),
'confirmed' => 1,
'confirmation_code' => md5(microtime().Config::get('app.key')),
'created_at' => new DateTime,
'updated_at' => new DateTime,
),
array(
'username' => 'user',
'email' => 'user#example.org',
'password' => Hash::make('user'),
'confirmed' => 1,
'confirmation_code' => md5(microtime().Config::get('app.key')),
'created_at' => new DateTime,
'updated_at' => new DateTime,
)
);
DB::table('users')->insert( $users );
}
}
RolesTableSeeder:
<?php
class RolesTableSeeder extends Seeder {
public function run()
{
DB::table('roles')->delete();
$adminRole = new Role;
$adminRole->name = 'adminRole';
$adminRole->save();
$standRole = new Role;
$standRole->name = 'userRole';
$standRole->save();
$modRole = new Role;
$modRole->name = 'modRole';
$modRole->save();
$user = User::where('username','=','admin')->first();
$user->attachRole( $adminRole );
$user = User::where('username','=','user')->first();
$user->attachRole( $standRole );
$user = User::where('username','=','moderator')->first();
$user->attachRole( $modRole );
}
}
PermissionsTableSeeder:
<?php
class PermissionsTableSeeder extends Seeder {
public function run()
{
DB::table('permissions')->delete();
$permissions = array(
array( // 1
'name' => 'manage_users',
'display_name' => 'manage users'
),
array( // 2
'name' => 'manage_roles',
'display_name' => 'manage roles'
),
array( // 3
'name' => 'standart_user_role',
'display_name' => 'standart_user_role'
),
);
DB::table('permissions')->insert( $permissions );
DB::table('permission_role')->delete();
$role_id_admin = Role::where('name', '=', 'admin')->first()->id;
$role_id_mod = Role::where('name', '=', 'moderator')->first()->id;
$role_id_stand = Role::where('name', '=', 'user')->first()->id;
$permission_base = (int)DB::table('permissions')->first()->id - 1;
$permissions = array(
array(
'role_id' => $role_id_admin,
'permission_id' => $permission_base + 1
),
array(
'role_id' => $role_id_admin,
'permission_id' => $permission_base + 2
),
array(
'role_id' => $role_id_mod,
'permission_id' => $permission_base + 1
),
array(
'role_id' => $role_id_mod,
'permission_id' => $permission_base + 3
),
array(
'role_id' => $role_id_stand,
'permission_id' => $permission_base + 3
),
);
DB::table('permission_role')->insert( $permissions );
}
}
This is the error I get when running db:seed:
$ php artisan db:seed
**************************************
* Application In Production! *
**************************************
Do you really wish to run this command? Y
Seeded: UsersTableSeeder
Seeded: RolesTableSeeder
[ErrorException]
Trying to get property of non-object
db:seed [--class[="..."]] [--database[="..."]] [--force]
Any recommendations what I am doing wrong in my seeding?
I appreciate your answers!

I think the problem is with:
$role_id_admin = Role::where('name', '=', 'admin')->first()->id;
$role_id_mod = Role::where('name', '=', 'moderator')->first()->id;
$role_id_stand = Role::where('name', '=', 'user')->first()->id;
You want to get id but such records don't exists so you get the error. There are probably no records with admin, moderator or user name because looking at RolesTableSeeder you create roles this way:
$adminRole = new Role;
$adminRole->name = 'adminRole';
$adminRole->save();
$standRole = new Role;
$standRole->name = 'userRole';
$standRole->save();
$modRole = new Role;
$modRole->name = 'modRole';
$modRole->save();
with names adminRole, userRole, modRole. So either change the names inside RolesTableSeeder or change them in PermissionsTableSeeder

Related

yii2 rbac authmanager getRoles() return empty

I'm implementing rbac using yii2. But when i try to get the roles that i previously created i get an empty variable : $authorRole = $auth->getRole('admin');
The rule class, where i put the actual rule logic.
yii/console/controller/UserGroupRule.php
namespace app\rbac;
use Yii;
use yii\rbac\Rule;
/**
* Checks if user group matches
*/
class UserGroupRule extends Rule
{
public $name = 'userGroup';
public function execute($user, $item, $params)
{
if (!Yii::$app->user->isGuest) {
$group = Yii::$app->user->identity->group;
if ($item->name === 'admin') {
return $group == 1;
} elseif ($item->name === 'author') {
return $group == 1 || $group == 2;
}
}
return false;
}
}
Now defining the roles..
yii/console/controller/RbacController.php
namespace console\controllers;
use Yii;
use yii\console\Controller;
class RbacController extends Controller
{
public function actionInit()
{
$auth = Yii::$app->authManager;
$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);
$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
}
}
After this i was able to run ./yii rbac/init to generate the rule files:
console/rbac/items.php
console/rbac/rules.php
This is mostly identical to the documentation
yii/commom/config/main.php
'authManager' => [
'class' => 'yii\rbac\PhpManager',
'defaultRoles' => ['admin', 'author'], // your define roles
],
But in
frontend\models\SignupForm::signup()
I get an empty result when i try to get the admin role :
public function signup()
{
if ($this->validate()) {
$user = new User();
$user->username = $this->username;
$user->email = $this->email;
$user->setPassword($this->password);
$user->generateAuthKey();
$user->save(false);
$auth = Yii::$app->authManager;
$authorRole = $auth->getRole('admin');
$auth->assign($authorRole, $user->getId());
return $user;
}
return null;
}
here is the value of $auth :
yii\rbac\PhpManager#1
(
[itemFile] => '/advanced/frontend/rbac/items.php'
[assignmentFile] => '/advanced/frontend/rbac/assignments.php'
[ruleFile] => '/advanced/frontend/rbac/rules.php'
[*:items] => []
[*:children] => []
[*:assignments] => []
[*:rules] => []
[defaultRoles] => [
0 => 'admin'
1 => 'author'
2 => 'admin'
3 => 'author'
]
[yii\base\Component:_events] => []
[yii\base\Component:_behaviors] => null
)
It's probably because you generate the rbac in "console/rbac/items.php and
console/rbac/rules.php" but your rbac PhpManager is looking this files in advanced/frontend
You could move this files or set the correct paths
'authManager' => [
'class' => 'yii\rbac\PhpManager',
'itemFile' => '#common/rbac/items.php',
'assignmentFile' => '#common/rbac/assignments.php',
'ruleFile' => '#common/rbac/rules.php',
'defaultRoles' => ['admin', 'author'], // your define roles
],
The "#common" is yii2 alias all available aliases listed here: http://www.yiiframework.com/wiki/667/yii-2-list-of-path-aliases-available-with-default-basic-and-advanced-app/
This should help, let me know if there will be still an issue

Phpunit test a method using a service

I'm trying to test a method which is using a service, and apparently it's not possible to test it like a normal method.
Does someone know what to do ?
I have this code for the moment :
namespace PlatformBundle\Tests;
use PlatformBundle\Controller\PaymentController;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class PaymentControllerTest extends WebTestCase
{
private $payment;
public function __construct() { parent::__construct(); $this->payment = new PaymentController(); }
public function testSendEmail()
{
$param = array(
'info' => array(
'email' => 'test#test.com', 'name' => 'test', 'fare' => 'test', 'id' => 'test'
)
);
$this->assertEquals(true, $this->invokeMethod($this->payment, 'sendEmail', $param));
}
/**
* Call protected/private method of a class.
*
* #param object &$object Instantiated object that we will run method on.
* #param string $methodName Method name to call
* #param array $parameters Array of parameters to pass into method.
*
* #return mixed Method return.
*/
public function invokeMethod(&$object, $methodName, array $parameters = array())
{
$reflection = new \ReflectionClass(get_class($object));
$method = $reflection->getMethod($methodName);
$method->setAccessible(true);
return $method->invokeArgs($object, $parameters);
}
}
The controller where the method sendEmail is :
<?php
namespace PlatformBundle\Controller;
use PlatformBundle\Entity\Customer;
use PlatformBundle\Entity\Promocode;
use PlatformBundle\Entity\Transfer;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class PaymentController extends Controller
{
public function checkoutAction(Request $req)
{
if (! $req->isMethod('POST')) throw new AccessDeniedHttpException();
$info = $req->request->all();
$this->container->get('platform.formSecurity')->testAllInformation($info);
$this->saveCustomerIntoDb($info);
$info['payed'] = false;
$session = $req->getSession();
$session->set('info', $info);
$info['date'] = $this->container->get('platform.useful')->reverseDateFormat($info['date']);
return $this->render('PlatformBundle:Payment:checkout.html.twig', array(
'isIndex' => false,
'info' => $info,
'stripe' => $this->stripeConfig()
));
}
public function cancelAction(Request $req)
{
$req->getSession()->invalidate();
return $this->render('PlatformBundle:Payment:cancel.html.twig', array('isIndex' => false));
}
public function successAction(Request $req)
{
$session = $req->getSession();
$info = $session->get('info');
if ($info['payed']) {
$req->getSession()->invalidate();
if ($info === null) throw new Exception('Please contact us to make sure that the payment has been done and that your order has been taken into account.');
$this->saveTransferIntoDb($info);
$customer = $this->getDoctrine()->getManager()->getRepository('PlatformBundle:Customer')->findOneBy(array(
'email' => $info['email']
));
$transfer = $this->getDoctrine()->getManager()->getRepository('PlatformBundle:Transfer')->findOneBy(
array('customer' => $customer->getId()),
array('id' => 'desc'),
1
);
$info['id'] = $transfer->getId();
$info['date'] = $this->container->get('platform.useful')->reverseDateFormat($info['date']);
$this->sendEmail($info);
// if 5 payments done, send a promocode
if (is_int($customer->getPayments() / 5)) {
$this->createAndSendNewPromocode($customer);
}
return $this->render('PlatformBundle:Payment:success.html.twig', array(
'isIndex' => false,
'info' => $info
));
} else return new RedirectResponse('cancel');
}
private function sendEmail($info)
{
$mail = $this->container->get('platform.mail');
$mail->send(
$info['email'],
'You have ordered a transfer for Dublin',
$this->renderView('PlatformBundle:Mail:orderSucceed.html.twig', array('info' => $info)),
'info#dubair.ie'
);
$mail->send(
'info#airportcollections.net, info#dubair.ie, info#365onlineholidays.com',
'A customer ordered a transfer for Dublin',
$this->renderView('PlatformBundle:Mail:report.html.twig', array('info' => $info)),
'info#dubair.ie'
);
}
private function saveCustomerIntoDb($info)
{
// test if the customer already exist
$customersList = $this->getDoctrine()->getManager()->getRepository('PlatformBundle:Customer')
->findByEmail($info['email']);
$customerExists = (sizeof($customersList) == 1 ? true : false);
if ($customerExists) {
$customer = $customersList[0];
} else {
// Create the entity
$customer = new Customer();
// dateRegistration, country and ip are automatically created in the constructor
$customer->setEmail($info['email']);
$customer->setPayments(0);
}
$customer->setName($info['name']);
$customer->setPhone($info['phone']);
$em = $this->getDoctrine()->getManager();
$em->persist($customer);
$em->flush();
}
private function saveTransferIntoDb($info)
{
$customers = $this->getDoctrine()->getManager()->getRepository('PlatformBundle:Customer')
->findByEmail($info['email']);
$customer = $customers[0];
$customer->setPayments($customer->getPayments() + 1);
// make promocode outdated
if ($info['promocode'] != '') {
$promocode = $this->getDoctrine()->getManager()->getRepository('PlatformBundle:Promocode')
->findOneBy(array(
'value' => $info['promocode'],
'outdated' => 0,
'type' => 'short'
));
$promocode->setOutdated(1);
}
// test if transfer already exist
$transferList = $this->getDoctrine()->getManager()->getRepository('PlatformBundle:Transfer')->findBy(
array(
'customer' => $customer,
'pickup' => $info['pickup'],
'destination' => $info['destination'],
'pickupTime' => $info['pickupTime'],
'address' => $info['address']
), // criteria
array('pickup' => 'desc'), // sorting
5, // Limit
0 // Offset
);
// if transfer doesn't already exist, create it
if (sizeof($transferList) == 0) {
$transfer = new Transfer();
$transfer->setPickup($info['pickup']);
$transfer->setDestination($info['destination']);
$dateArray = explode('-', $info['date']);
$transfer->setDate(new \DateTime($dateArray[2].'-'.$dateArray[1].'-'.$dateArray[0]));
$transfer->setAddress($info['address']);
$transfer->setFlightTime($info['flightTime']);
$transfer->setPickupTime($info['pickupTime']);
$transfer->setSeats($info['seats']);
$transfer->setAirline($info['airline']);
$transfer->setFlight($info['flight']);
$transfer->setType($info['type']);
$transfer->setBags($info['bags']);
$transfer->setFare($info['fare']);
// join
$transfer->setCustomer($customer);
$em = $this->getDoctrine()->getManager();
$em->persist($transfer);
$em->flush();
}
}
private function createAndSendNewPromocode($customer)
{
$newPromocode = $this->container->get('platform.useful')->createRandomPassword();
$promocode = new Promocode();
$promocode->setValue($newPromocode);
$promocode->setType('short');
$promocode->setDiscount(10);
$em = $this->getDoctrine()->getManager();
$em->persist($promocode);
$em->flush();
$mail = $this->container->get('platform.mail');
$mail->send(
$customer->getEmail(),
'A promotional code for your next transfer on dubair.ie !',
$this->renderView('PlatformBundle:Mail:promocode.html.twig', array(
'customer' => $customer,
'promocode' => $newPromocode
)),
'info#dubair.ie'
);
}
private function stripeConfig()
{
$stripe = array(
"secret_key" => "xx",
"publishable_key" => "xx"
);
\Stripe\Stripe::setApiKey($stripe['secret_key']);
return $stripe;
}
public function stripeChargeAction(Request $req)
{
$this->stripeConfig();
$info = $req->getSession()->get('info');
$amount = ($info['fare'] * 100);
$info['payed'] = true;
$req->getSession()->set('info', $info);
$token = $req->request->get('stripeToken');
$customer = \Stripe\Customer::create(array(
'email' => $req->request->get('email'),
'card' => $token
));
$charge = \Stripe\Charge::create(array(
'customer' => $customer->id,
'amount' => $amount,
'currency' => 'eur'
));
return new RedirectResponse('success');
}
}
thanks

How to select users except roles with Laravel Eloquent

I need to select only id and name attribute from users table except roles attribute. I tried this:
$school_user = User::select('name', 'id')->get()->toArray();
but when I print it to screen it returns array with his roles. Like this:
Array
(
[0] => Array
(
[name] => Admin
[id] => 1
[roles] => Array
(
[0] => Array
(
[id] => 3
[name] => admin
[pivot] => Array
(
[user_id] => 1
[role_id] => 1
)
)
)
)
)
Any suggestions to get only name and id attributes except roles?
There is my User Model class (a bit cleaned):
<?php
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password', 'remember_token');
protected $fillable = array('email', 'name', 'password', 'block');
protected $guarded = array('id');
/**
* Get the schools or kindergardens a user can moderate
*/
public function schools()
{
return $this->belongsToMany('School', 'school_user');
}
/**
* Get the roles a user has
*/
public function roles()
{
return $this->belongsToMany('Role', 'users_roles');
}
/**
* Find out if User is an employee, based on if has any roles
*
* #return boolean
*/
public function isEmployee()
{
$roles = $this->roles->toArray();
return !empty($roles);
}
/**
* Find out if user has a specific role
*
* $return boolean
*/
public function hasRole($check)
{
return in_array($check, array_fetch($this->roles->toArray(), 'name'));
}
/**
* Get key in array with corresponding value
*
* #return int
*/
private function getIdInArray($array, $term)
{
foreach ($array as $key => $value) {
if ($value['name'] == $term) {
return $key;
}
}
throw new UnexpectedValueException;
}
/**
* Add roles to user to make them a concierge
*/
public function makeEmployee($role_id)
{
$assigned_roles = array();
$roles = Role::all()->keyBy('id')->toArray();
$this->roles()->attach(array($role_id));
}
public $invitation;
protected static function boot()
{
parent::boot();
static::creating(function($model)
{
$data = array(
'invitation' => $model->invitation,
'email' => $model->email,
'name' => $model->name,
'password' => $model->password
);
$model->password = Hash::make($model->password);
$rules = array(
'invitation' => 'required',
'email' => 'unique:users,email|required|email',
'name' => 'required|min:3|max:20',
'password' => 'required|min:8|max:30'
);
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new ValidationException(null, null, null, $validator->messages());
} else {
return $model->validate();
}
});
static::created(function($model)
{
$role_id = Invitation::where('code', '=', $model->invitation)->first()->role_id;
$model->makeEmployee($role_id);
$invitation_code = Invitation::where('code', '=', $model->invitation)->update(array('used_by' => $model->id));
});
}
public function validate()
{
if (is_null(Invitation::where('code', '=', $this->invitation)->where('used_by', '=', '0')->first())) {
throw new ValidationException(null, null, null, array('invitation' => "Грешен код."));
} else {
return true;
}
}
public function updatePass($old_password, $new_password, $repeat_new_password)
{
$data = array(
'old_password' => $old_password,
'new_password' => $new_password,
'repeat_new_password' => $repeat_new_password
);
$rules = array(
'old_password' => 'required',
'new_password' => 'required|min:8|max:30',
'repeat_new_password' => 'required|same:new_password'
);
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new ValidationException(null, null, null, $validator);
} else {
$user = User::find(Auth::user()->id);
if (Hash::check($old_password, $user->password)) {
$user->password = Hash::make($new_password);
if($user->save()) {
return true;
} else {
throw new ValidationException(null, null, null, array('mainError' => "Грешка с базата данни."));
}
} else {
throw new ValidationException(null, null, null, array('old_password' => "Моля въведете правилно страта Ви парола."));
}
}
}
public function login($email, $password, $remember)
{
$data = array(
'email' => $email,
'password' => $password
);
$rules = array(
'email' => 'required|email',
'password' => 'required'
);
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
throw new ValidationException(null, null, null, $validator);
} else {
if (User::where('email', '=', $email)->first()->block == true) {
throw new ValidationException(null, null, null, array('mainError' => "Акаунтът ви е блокиран."));
} else {
$remember = ($remember) ? true : false;
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
return true;
} else {
throw new ValidationException(null, null, null, array('mainError' => 'Имейлът или паролата е грешна.'));
}
}
}
}
}
And Role Model:
<?php
class Role extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'roles';
protected $fillable = array('name');
protected $guarded = array('id');
/**
* Set timestamps off
*/
public $timestamps = false;
/**
* Get users with a certain role
*/
public function users()
{
return $this->belongsToMany('User', 'users_roles');
}
}
I'm sorry for use of bulgarian language in exceptions
Looking at the code it's rather not possible that running just:
$school_user = User::select('name', 'id')->get()->toArray();
make appending roles to result.
You should make sure that you don't add anything to $appends property and you don't load relationship somewhere in your code. You should also make sure that you don't have custom toArray() method implemented that loads this relationship when converting to array. If you are sure you don't you should show the full code and your exact Laravel version.
EDIT
You didn't show where you launch your code with select or lists however you load your roles relationship in many methods - for example isEmployee, isEmployee or hasRole. That's why roles are used when you are converting to array. You might want to write your custom toArray method to remove roles from your result set when converting to array.

laravel 4 many to many relationship integration test not passing

Every entity is being saved and has an ai id. Only the test on line 34 is not passing.
<?php
class AssignmentTest extends TestCase
{
protected $assignment;
public function setUp()
{
parent::setUp();
$this->assignment = new Assignment;
}
public function testAssignmentAssociations()
{
$facility = Facility::create(array(
'name' => 'test facility',
'code' => 'test-facility'
));
$user = new User;
$user->id = 1;
$shift = Shift::create(array(
'name' => 'test shift',
'start_time' => 0,
'end_time' => 600
));
$this->assignment->facilities()->save($facility);
$this->assignment->shift()->associate($shift);
$this->assignment->user()->associate($user);
$this->assignment->save();
$this->assertEquals(1, $facility->id);
$this->assertEquals(1, $this->assignment->facilities()->count());
$this->assertEquals(1, $this->assignment->shift_id);
$this->assertEquals(1, $this->assignment->user_id);
}
}
// Facility model
public function assignments()
{
return $this->belongsToMany('Assignment', 'assignment_facilities');
}
// Assignment model
public function facilities()
{
return $this->belongsToMany('Facility', 'assignment_facilities');
}
Edit
Apparently removing this line will make the test successful. Now I'm interested as to why when saving the assignment the association will no longer return a successful count?
<?php
$this->assignment->save();
I havent tested - but dont you need to 'save' the shift and user records themselves, before you try and associate them against the assignment?
$user = new User;
$user->id = 1;
$shift = Shift::create(array(
'name' => 'test shift',
'start_time' => 0,
'end_time' => 600
));
$shift->id = 1;
$this->assignment->facilities()->save($facility);
$this->assignment->shift()->save($shift);
$this->assignment->user()->save($user);
$this->assignment->shift()->associate($shift);
$this->assignment->user()->associate($user);
$this->assignment->save();

How to create form inputs/elements in ZF2

EDIT : My main question has now become 'How do I get the ServiceManager with the doctrine entity manager into the hands of my form, element, and input classes in some clean way?' Read on to see the full post.
I'm going to try and ask by example here so bear with me. Let me know where I'm going wrong/right or where I could improve
I'm trying to create a registration form. I could use ZfcUser module but I want to do this on my own. I'm using ZF2 with Doctrine2 as well so that leads me away from that module a bit.
My strategy was this,
Create a form class called registration form
Create separate 'element' classes for each element where each element will have an input specification
Since each element is a separate class from the form I can unit test each one separately.
All seemed fine until I wanted to add a validator to my username element that would check that the username is NOT is use yet.
Here is the code thus far
namepsace My\Form;
use Zend\Form\Form,
Zend\Form\Element,
Zend\InputFilter\Input,
Zend\InputFilter\InputFilter,
/**
* Class name : Registration
*/
class Registration
extends Form
{
const USERNAME = 'username';
const EMAIL = 'email';
const PASSWORD = 'password';
const PASS_CONFIRM = 'passwordConfirm';
const GENDER = 'gender';
const CAPTCHA = 'captcha';
const CSRF = 'csrf';
const SUBMIT = 'submit';
private $captcha = 'dumb';
public function prepareForm()
{
$this->setName( 'registration' );
$this->setAttributes( array(
'method' => 'post'
) );
$this->add( array(
'name' => self::USERNAME,
'type' => '\My\Form\Element\UsernameElement',
'attributes' => array(
'label' => 'Username',
'autofocus' => 'autofocus'
)
)
);
$this->add( array(
'name' => self::SUBMIT,
'type' => '\Zend\Form\Element\Submit',
'attributes' => array(
'value' => 'Submit'
)
) );
}
}
I removed a lot that I think isn't necessary. Here is my username element below.
namespace My\Form\Registration;
use My\Validator\UsernameNotInUse;
use Zend\Form\Element\Text,
Zend\InputFilter\InputProviderInterface,
Zend\Validator\StringLength,
Zend\Validator\NotEmpty,
Zend\I18n\Validator\Alnum;
/**
*
*/
class UsernameElement
extends Text
implements InputProviderInterface
{
private $minLength = 3;
private $maxLength = 128;
public function getInputSpecification()
{
return array(
'name' => $this->getName(),
'required' => true,
'filters' => array(
array( 'name' => 'StringTrim' )
),
'validators' =>
array(
new NotEmpty(
array( 'mesages' =>
array(
NotEmpty::IS_EMPTY => 'The username you provided is blank.'
)
)
),
new AlNum( array(
'messages' => array( Alnum::STRING_EMPTY => 'The username can only contain letters and numbers.' )
)
),
new StringLength(
array(
'min' => $this->getMinLength(),
'max' => $this->getMaxLength(),
'messages' =>
array(
StringLength::TOO_LONG => 'The username is too long. It cannot be longer than ' . $this->getMaxLength() . ' characters.',
StringLength::TOO_SHORT => 'The username is too short. It cannot be shorter than ' . $this->getMinLength() . ' characters.',
StringLength::INVALID => 'The username is not valid.. It has to be between ' . $this->getMinLength() . ' and ' . $this->getMaxLength() . ' characters long.',
)
)
),
array(
'name' => '\My\Validator\UsernameNotInUse',
'options' => array(
'messages' => array(
UsernameNotInUse::ERROR_USERNAME_IN_USE => 'The usarname %value% is already being used by another user.'
)
)
)
)
);
}
}
Now here is my validator
namespace My\Validator;
use My\Entity\Helper\User as UserHelper,
My\EntityRepository\User as UserRepository;
use Zend\Validator\AbstractValidator,
Zend\ServiceManager\ServiceManagerAwareInterface,
Zend\ServiceManager\ServiceLocatorAwareInterface,
Zend\ServiceManager\ServiceManager;
/**
*
*/
class UsernameNotInUse
extends AbstractValidator
implements ServiceManagerAwareInterface
{
const ERROR_USERNAME_IN_USE = 'usernameUsed';
private $serviceManager;
/**
*
* #var UserHelper
*/
private $userHelper;
protected $messageTemplates = array(
UsernameNotInUse::ERROR_USERNAME_IN_USE => 'The username you specified is being used already.'
);
public function isValid( $value )
{
$inUse = $this->getUserHelper()->isUsernameInUse( $value );
if( $inUse )
{
$this->error( UsernameNotInUse::ERROR_USERNAME_IN_USE, $value );
}
return !$inUse;
}
public function setUserHelper( UserHelper $mapper )
{
$this->userHelper = $mapper;
return $this;
}
/**
* #return My\EntityRepository\User
*/
public function getUserHelper()
{
if( $this->userHelper == null )
{
$this->setUserHelper( $this->getServiceManager()->get( 'doctrine.entitymanager.orm_default' )->getObjectRepository( 'My\Entity\User') );
}
return $this->userHelper;
}
public function setServiceManager( ServiceManager $serviceManager )
{
echo get_class( $serviceManager );
echo var_dump( $serviceManager );
$this->serviceManager = $serviceManager;
return $this;
}
/**
*
* #return ServiceManager
*/
public function getServiceManager( )
{
return $this->serviceManager;
}
}
Why did this seem like a good idea to me?
It seemed like a good testability/re-use choice to make since I could re-use the elements separately across my application if need be.
I could unit test each Input generated by each element to make sure it correctly accepts/rejects input.
This is the example of my unit test for the element
public function testFactoryCreation()
{
$fac = new Factory();
$element = $fac->createElement( array(
'type' => '\My\Form\Registration\UsernameElement'
) );
/* #var $element \My\Form\Registration\UsernameElement */
$this->assertInstanceOf( '\My\Form\Registration\UsernameElement',
$element );
$input = $fac->getInputFilterFactory()->createInput( $element->getInputSpecification() );
$validators = $input->getValidatorChain()->getValidators();
/* #var $validators \Zend\Validator\ValidatorChain */
$expectedValidators = array(
'Zend\Validator\StringLength',
'Zend\Validator\NotEmpty',
'Zend\I18n\Validator\Alnum',
'My\Validator\UsernameNotInUse'
);
foreach( $validators as $validator )
{
$actualClass = get_class( $validator['instance'] );
$this->assertContains( $actualClass, $expectedValidators );
switch( $actualClass )
{
case 'My\Validator\UsernameNotInUse':
$helper = $validator['instance']->getUserHelper();
//HAVING A PROBLEM HERE
$this->assertNotNull( $helper );
break;
default:
break;
}
}
}
The problem I'm having is that the validator can't fetch the UserHelper properly, which is really a UserRepository from doctrine. The reason this is happening is because the validators only get access to the ValidatorPluginManager as a ServiceManager rather than having access to the application wide ServiceManager.
I get this error for the Validator portion, although if I call the same get method on the general service manager it works with no problems.
1) Test\My\Form\Registration\UsernameElementTest::testFactoryCreation
Zend\ServiceManager\Exception\ServiceNotFoundException: Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for doctrine.entitymanager.orm_default
The var_dump( $serviceManager ) in validator shows me it is of the class ValidatorPluginManager.
I tried putting a factory in the service_manager entry like so
'service_manager' => array(
'factories' => array(
'My\Validator\UsernameNotInUse' => function( $sm )
{
$validator = new \My\Validator\UsernameNotInUse();
$em = $serviceManager->get( 'doctrine.entitymanager.orm_default' );
/* #var $em \Doctrine\ORM\EntityManager */
$validator->setUserHelper( $em->getRepository( '\My\Entity\User' ) );
return $validator;
}
)
but that didn't work because it's not consulting the application level service manager.
So, overall, here are my questions :
Is this strategy of separating the form and elements a good one? Should I keep going this way? What are alternatives? ( I'm for breaking stuff up for the sake of testability ) I was going to test ONLY the form itself originally with a combination of ALL the inputs but it seemed like I'd be trying to do too much.
How do I resolve the issue I have above?
Should I be using the Form/Element/Input parts of Zend in some other way that I'm not seeing?
this is my validator, using a static method to inject the entityManager and working with any doctine entity.
<?php
namespace Base\Validator;
use Traversable;
use Zend\Stdlib\ArrayUtils;
use Zend\Validator\AbstractValidator;
use Doctrine\ORM\EntityManager;
class EntityUnique extends AbstractValidator
{
const EXISTS = 'exists';
protected $messageTemplates = array(
self::EXISTS => "A %entity% record already exists with %attribute% %value%",
);
protected $messageVariables = array(
'entity' => '_entity',
'attribute' => '_attribute',
);
protected $_entity;
protected $_attribute;
protected $_exclude;
protected static $_entityManager;
public static function setEntityManager(EntityManager $em) {
self::$_entityManager = $em;
}
public function getEntityManager() {
if (!self::$_entityManager) {
throw new \Exception('No entitymanager present');
}
return self::$_entityManager;
}
public function __construct($options = null)
{
if ($options instanceof Traversable) {
$options = ArrayUtils::iteratorToArray($token);
}
if (is_array($options)) {
if (array_key_exists('entity', $options)) {
$this->_entity = $options['entity'];
}
if (array_key_exists('attribute', $options)) {
$this->_attribute = $options['attribute'];
}
if (array_key_exists('exclude', $options)) {
if (!is_array($options['exclude']) ||
!array_key_exists('attribute', $options['exclude']) ||
!array_key_exists('value', $options['exclude'])) {
throw new \Exception('exclude option must contain attribute and value keys');
}
$this->_exclude = $options['exclude'];
}
}
parent::__construct(is_array($options) ? $options : null);
}
public function isValid($value, $context = null)
{
$this->setValue($value);
$queryBuilder = $this->getEntityManager()
->createQueryBuilder()
->from($this->_entity, 'e')
->select('COUNT(e)')
->where('e.'. $this->_attribute . ' = :value')
->setParameter('value', $this->getValue());
if ($this->_exclude) {
$queryBuilder = $queryBuilder->andWhere('e.'. $this->_exclude['attribute'] . ' != :exclude')
->setParameter('exclude', $this->_exclude['value']);
}
$query = $queryBuilder->getQuery();
if ((integer)$query->getSingleScalarResult() !== 0) {
$this->error(self::EXISTS);
return false;
}
return true;
}
}
ie. i'm using it for theese form elements which are also tested and working fine:
<?php
namespace User\Form\Element;
use Zend\Form\Element\Text;
use Zend\InputFilter\InputProviderInterface;
class Username extends Text implements InputProviderInterface
{
public function __construct() {
parent::__construct('username');
$this->setLabel('Benutzername');
$this->setAttribute('id', 'username');
}
public function getInputSpecification() {
return array(
'name' => $this->getName(),
'required' => true,
'filters' => array(
array(
'name' => 'StringTrim'
),
),
'validators' => array(
array(
'name' => 'NotEmpty',
'break_chain_on_failure' => true,
'options' => array(
'messages' => array(
'isEmpty' => 'Bitte geben Sie einen Benutzernamen ein.',
),
),
),
),
);
}
}
When creating a new user
<?php
namespace User\Form\Element;
use Zend\InputFilter\InputProviderInterface;
use User\Form\Element\Username;
class CreateUsername extends Username implements InputProviderInterface
{
public function getInputSpecification() {
$spec = parent::getInputSpecification();
$spec['validators'][] = array(
'name' => 'Base\Validator\EntityUnique',
'options' => array(
'message' => 'Der name %value% ist bereits vergeben.',
'entity' => 'User\Entity\User',
'attribute' => 'username',
),
);
return $spec;
}
}
when editin an existing user
<?php
namespace User\Form\Element;
use Zend\InputFilter\InputProviderInterface;
use User\Form\Element\Username;
class EditUsername extends Username implements InputProviderInterface
{
protected $_userId;
public function __construct($userId) {
parent::__construct();
$this->_userId = (integer)$userId;
}
public function getInputSpecification() {
$spec = parent::getInputSpecification();
$spec['validators'][] = array(
'name' => 'Base\Validator\EntityUnique',
'options' => array(
'message' => 'Der name %value% ist bereits vergeben.',
'entity' => 'User\Entity\User',
'attribute' => 'username',
'exclude' => array(
'attribute' => 'id',
'value' => $this->_userId,
),
),
);
return $spec;
}
}

Categories