Phalcon ORM doesn't work - php

I am learning phalcon. I have some problems with models.
Function FindFirst returns nothing, also it doesn't show any errors or exceptions. Here is my code:
public function indexAction()
{
$user = Users::findFirst(1);
var_dump($user);
}
And all what I get - is empty page.
Here is my Users Model:
<?php
namespace Models\User;
use Phalcon\Mvc\Model\Validator\Email as Email;
class Users extends \Phalcon\Mvc\Model
{
/**
*
* #var integer
*/
public $id;
/**
*
* #var string
*/
public $login;
/**
*
* #var string
*/
public $email;
public function initialize()
{
$this->setSource("users");
}
/**
* Validations and business logic
*/
public function validation()
{
$this->validate(
new Email(
array(
'field' => 'email',
'required' => true,
)
)
);
if ($this->validationHasFailed() == true) {
return false;
}
}
/**
* Independent Column Mapping.
* Keys are the real names in the table and the values their names in the application
*
* #return array
*/
public function columnMap()
{
return array(
'id' => 'id',
'login' => 'login',
'email' => 'email'
);
}
}
Some additional information:
I have edited config files.
Phalcon version is 2.0

First you should ensure that the User model you are trying to load is in the right namespace, what means in your case you should use:
$user = \Models\User\Users::findFirst(1);
And to retrieve an output (depending on your index.php but probably this way) you should return "something", otherwise the buffer will be empty and nothing will be displayed.

Related

How to verify if the user is agent or administrator yii2

I have made my table for users : PK - ID, username, password, status, role
For roles i have declared in my model const ROLE_ADMIN = 1, const ROLE_VISITOR = 2;
I want to allow the login if the user is admin, or to reject the login if the user is visitor. I will have all the users imported, but the application will be accessed only by few of them, so i want to deny the login for any other tipe of user.
For this
[
'allow' => true,
'roles' => ['#'],
],
Where should i declare a function to use instead of 'allow' => true, 'allow'=>function and verify if $this->role == 1
THANK YOU IN ADVANCE FOR ANY SUGGESTIONS.
Controller :
<?php
namespace app\controllers;
use Yii;
use app\models\User;
use app\models\UserSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
/**
* UsersController implements the CRUD actions for User model.
*/
class UsersController extends Controller
{
/**
* {#inheritdoc}
*/
public function behaviors()
{
return [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['POST'],
],
],
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
'allow' => true,
'roles' => ['#'],
],
],
],
];
];
}
/**
* Lists all User models.
* #return mixed
*/
public function actionIndex()
{
$searchModel = new UserSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
/**
* Displays a single User model.
* #param integer $id
* #return mixed
* #throws NotFoundHttpException if the model cannot be found
*/
public function actionView($id)
{
return $this->render('view', [
'model' => $this->findModel($id),
]);
}
/**
* Creates a new User model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionCreate()
{
$model = new User();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('create', [
'model' => $model,
]);
}
/**
* Updates an existing User model.
* If update is successful, the browser will be redirected to the 'view' page.
* #param integer $id
* #return mixed
* #throws NotFoundHttpException if the model cannot be found
*/
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
return $this->render('update', [
'model' => $model,
]);
}
/**
* Deletes an existing User model.
* If deletion is successful, the browser will be redirected to the 'index' page.
* #param integer $id
* #return mixed
* #throws NotFoundHttpException if the model cannot be found
*/
public function actionDelete($id)
{
$this->findModel($id)->delete();
return $this->redirect(['index']);
}
/**
* Finds the User model based on its primary key value.
* If the model is not found, a 404 HTTP exception will be thrown.
* #param integer $id
* #return User the loaded model
* #throws NotFoundHttpException if the model cannot be found
*/
protected function findModel($id)
{
if (($model = User::findOne($id)) !== null) {
return $model;
}
throw new NotFoundHttpException('The requested page does not exist.');
}
}
Model:
<?php
namespace app\models;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
/**
* This is the model class for table "user".
*
* #property int $id
* #property string $username
* #property string $password
* #property int $status
* #property int $role
*
* #property Equipment[] $equipments
* #property UserEquipmentMapping[] $userEquipmentMappings
*/
class User extends ActiveRecord implements IdentityInterface
{
/**
* {#inheritdoc}
*/
const STATUS_AVAILABLE = 1;
const STATUS_DISABLED = 2;
const ROLE_ADMIN = 1;
const ROLE_VISITOR = 2;
public static function tableName()
{
return 'user';
}
/**
* {#inheritdoc}
*/
public function rules()
{
return [
[['username', 'password', 'status', 'role'], 'required'],
[['status', 'role'], 'integer'],
[['username', 'password'], 'string', 'max' => 20],
];
}
/**
* {#inheritdoc}
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'username' => 'Username',
'password' => 'Password',
'status' => 'Status',
'role' => 'Role',
];
}
/**
* Gets query for [[Equipments]].
*
* #return \yii\db\ActiveQuery|EquipmentQuery
*/
public function getEquipments()
{
return $this->hasMany(Equipment::className(), ['user_for_id' => 'id']);
}
/**
* Gets query for [[UserEquipmentMappings]].
*
* #return \yii\db\ActiveQuery|UserEquipmentMappingQuery
*/
public function getUserEquipmentMappings()
{
return $this->hasMany(UserEquipmentMapping::className(), ['user_id' => 'id']);
}
/**
* {#inheritdoc}
* #return UserQuery the active query used by this AR class.
*/
public static function find()
{
return new UserQuery(get_called_class());
}
public static function findIdentity($id)
{
return static::findOne($id);
}
public static function findByUsername($username)
{
#$u = static::findOne($username);#
#print_r($username);die;
$u = User::find()
->where(['username' => $username])
->one();
return $u;
}
public function getUsername()
{
#$u = User::find()
# ->where(['username' => $username])
# ->one();
return $this->username;
#return $this->hasOne(User::className(), ['username' => 'username_id']);
}
public function getAuthKey()
{
}
/**
* #inheritdoc
*/
public function getId()
{
return $this->id;
}
public function getRole(){
return $this->role;
}
public static function findIdentityByAccessToken($token, $type = null)
{
}
public function validateAuthKey($authKey)
{
}
public function validatePassword($password)
{
return $this->password === $password;
}
/* public function rules()
{
return array(
array('last_name','first_name','age','username','password', 'safe'),
);
}
*/
}
If you were trying to allow one set of actions for "normal" Users and one for Administrators, then it might be worth checking out the Advanced template, which would allow you to allocate permissions separately in the frontend and backend. However, if I understand your comment in reply to the first version of this answer, then what you are actually intending is to import a complete set of Users from another context (Active Directory) and then give some of them Admin access to your Yii2 application.
You say you want to allow the login if the user is admin, or to reject the login if the user is visitor, which is still a bit ambiguous. Do you not in fact mean that the non-admins are simply Guests (the term Yii uses)?
If the visitors cannot log in, then they cannot be differentiated from one another, and you don't need to - in fact I would suggest that you probably should not - keep them in the same table as your admins. You would probably be better off simply allocating logins to your admins, and controlling those with the code you've posted above: 1) a controller for actions and 2) your identity interface, plus /models/LoginForm.php.
You would then create a separate table for the Active Directory users, complete with its own model, views and controller, within which you would give your Admins permission to perform CRUD functions on those records.
You need an action in one of your controllers to display the login form, and there is a ready-rolled one in the basic template, whose model is models/LoginForm.php, and that is where you can most easily write/alter code to log admins in (and potentially ignore "normal" users / visitors). If you separate the tables though, it will work out of the box - because only your admins will be in the table it references.
Then you can use behaviors in controllers to allow or deny access to different levels of user (roles). There is a good tutorial here. Note that the syntax for behaviors for Guests is a question mark (as opposed to the # symbol for authenticated users). A good tip here is not to mix publicly-visible and admin-only actions in the same controller, because it simplifies the necessary behaviors code.
The last thing I would suggest is that, if you are going to use a permissions hierarchy, you should rearrange your user permission levels (constants) in the opposite order to the one you've used - the higher the value, the greater the permissions. I tend to use a 7-level system (actually 6 + 1, where 0 is no permissions whatsoever and 6 is Super-User), but in combination with a set of systems, so that users have permissions per system. This has been sufficient for every system I've built or worked on, but sounds excessive for your needs.
In summary, it sounds like you can achieve what you want with Admins in the table used by IdentityInterface, Active Directory users in a different table, and a simple Guest / Authenticated User delineation.

Laravel 5.5 conditional form request validation rule on update

I created a validation rule for the image form.
It works fine on store method but I do not want the image field to be required on update because I may only update the title for example.
class ImageRequest extends Request
{
/**
* Rules array
*/
protected $rules = [
'title' => 'required|string|between:3,60',
'alt' => 'sometimes|string|between:3,60',
'image' => 'required|image|max:4000|dimensions:min_width=200,min_height=200',
];
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return $this->rules;
}
}
For unique validation we can add custom query conditions:
'email' => Rule::unique('users')->ignore($user->id, 'user_id')
or
'email' => Rule::unique('users')->where(function ($query) {
return $query->where('account_id', 1);
})
Is it a clean way to achieve something similar for required?
Apply required only for new images.
you Can use switch statement inside rule
public function rules()
{
switch ($this->method()) {
case 'GET':
case 'DELETE': {
return [];
}
case 'POST': {
return [
'first_name'=>'required',
'last_name'=>'required',
'email'=>'required|email|unique:users,email,'.$this->id,
'password'=>'',
'dob'=>'required',
'phone_one'=>'required',
'phone_two'=>'required',
//'user_role'=>'required',
// 'profile_image'=>'required'
];
}
case 'PUT':
case 'PATCH': {
return [
];
}
default:break;
}
Also you can use condtion like on update yuo have id so based on that you can check whether its update or insert since on insert you dont have id so
Create another class that extends the Request class, DI that into your update controller action
class UpdateImageRequest extends Request
{
/**
* Rules array
*/
protected $rules = [
'title' => 'required|string|between:3,60',
'alt' => 'sometimes|string|between:3,60'
];
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return $this->rules;
}
}
much better way is to use nullable in Laravel 5.5 validations
Ref Docs
The field under validation may be null. This is particularly useful
when validating primitive such as strings and integers that can
contain null values.
class ImageRequest extends Request
{
/**
* Rules array
*/
protected $rules = [
'title' => 'required|string|between:3,60',
'alt' => 'nullable|string|between:3,60',
'image' => 'nullable|image|max:4000|dimensions:min_width=200,min_height=200',
];
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return $this->rules;
}
}
However I have used recently with image and it worked like charm for me. Give it a try!
The simplest way in this case in the other way. By default have rules for update and if it's store add required like so:
class ImageRequest extends Request
{
/**
* Rules array
*/
protected $rules = [
'title' => 'required|string|between:3,60',
'alt' => 'sometimes|string|between:3,60',
'image' => 'image|max:4000|dimensions:min_width=200,min_height=200',
];
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$rules = $this->rules;
if ($this->isMethod('POST')) {
$rules['image'] = 'required|' . $rules['image']
}
return $rules;
}
}
I found a solution.
I renamed image into file.
The route is homestead.app/images/1 on update and homestead.app/images on store so the $image property will be $this->image = 1 on update and $this->image = null on store.
class ImageRequest extends Request
{
/**
* Rules array
*/
protected $rules = [
'title'=> 'required|string|between:3,60',
'alt' => 'sometimes|string|between:3,60',
'file' => [
'image',
'max:4000',
'dimensions:min_width=200,min_height=200',
],
];
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$this->rules['file'][] = is_null($this->image) ? 'required' : 'sometimes';
return $this->rules;
}
}

Symfony Doctrine Many to Many insert

I have a problem with my entities and controllers in Symfony. I would insert on my DB value in a many to many table generated.
Entity Requests with only many to many elements
class Requests {
/**
* #ORM\ManyToMany(targetEntity="Tipi", inversedBy="requests")
* #ORM\JoinTable(name="tipi_richieste")
*/
private $tipi;
public function __construct() {
$this->tipi = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add tipi
*
* #param \AppBundle\Entity\Tipi $tipi
*
* #return Requests
*/
public function addTipi(\AppBundle\Entity\Tipi $tipi) {
$this->tipi[] = $tipi;
return $this;
}
/**
* Remove tipi
*
* #param \AppBundle\Entity\Tipi $tipi
*/
public function removeTipi(\AppBundle\Entity\Tipi $tipi) {
$this->tipi->removeElement($tipi);
}
/**
* Get tipi
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTipi() {
return $this->tipi;
}
}
Entity Tipi with only many to many elements
class Tipi {
/**
* #ORM\ManyToMany(targetEntity="Requests", mappedBy="tipi")
*/
private $requests;
/**
* Constructor
*/
public function __construct() {
$this->requests = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add request
*
* #param \AppBundle\Entity\Requests $request
*
* #return Tipi
*/
public function addRequest(\AppBundle\Entity\Requests $request)
{
$this->requests[] = $request;
return $this;
}
/**
* Remove request
*
* #param \AppBundle\Entity\Requests $request
*/
public function removeRequest(\AppBundle\Entity\Requests $request)
{
$this->requests->removeElement($request);
}
/**
* Get requests
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getRequests()
{
return $this->requests;
}
}
The Form type for insert is an EntityType
->add('tipi', EntityType::class, array(
'label' => 'Tipo',
'class' => 'AppBundle:Tipi',
'mapped' => false,
'attr' => array('class' => 'form-control'),
'multiple' => true,
'by_reference' => false,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('t');
},
))
And in my controller I work in this way:
public function indexAction(Request $request) {
$requests = new Requests();
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(RequestsType::class, $requests);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$requests->setCreateAt(new \DateTime('now'));
$request_tipi = $form["tipi"]->getData();
$tipi_array = [];
die($form["tipi"]->getData());
$tipi_array = $em->getRepository('AppBundle:Tipi')->findOneBy(array('codice' => $form["tipi"]->getData()));
$tipi = new Tipi();
$requests->addTipi($form["tipi"]->getData());
$em->persist($requests);
$em->flush();
//return $this->redirectToRoute('immovable_edit', array('id' => $immovables->getId()));
}
return $this->render('AppBundle:Requests:index.html.twig', array(
'requests' => $requests,
'form' => $form->createView(),
));
}
When I put die for return the value of $form["tipi"]->getData() I get an array collection :
Doctrine\Common\Collections\ArrayCollection#000000005b52ae6b00000000731dd0b4
But I get this error
Catchable Fatal Error: Argument 1 passed to AppBundle\Entity\Requests::addTipi() must be an instance of AppBundle\Entity\Tipi, instance of Doctrine\Common\Collections\ArrayCollection given, called in C:\xampp\htdocs\bugaro\src\AppBundle\Controller\RequestsController.php on line 31 and defined
Request::addTipi() fuction adds single tipi to its internal collection. Therefore you can't add whole ArrayCollection in call.
You have two options.
Do a foreach
foreach($form["tipi"]->getData() as $tipi) {
$requests->addTipi($tipi);
}
Create multisetter like Requests::addCollections(ArrayCollection[])
public function addTipis($tipis) {
foreach($tipis as $tipi) {
$this->tipi[] = $tipi;
}
return $this;
}
Anyway there's a lot to fix in your code. Too much for a single post here.
But the most important of these fixes is that you don't need most of your controller code. ;-)
Since you pass $requests object into the form, it's already bined two-way, which means that Symfony's Form Component should automatically fill it's properties with new values. That includes many to many relation's collections.
Also if you wouldn't pass that object, $form->getData() should return a new Resnponses instance, therefore you don't need to create it and pass manually until it's edition of existing instance.

cakephp 3 edit user username and password - Unable to find table class for current entity

I am using cakephp 3.5 (ran the update today) to make my first non tutorial web application. I am trying to set up my app so that I can edit my users account details (username and password).
I am not sure what I have done, but I cannot actually access my edit.ctp (view) while I am logged in. I keep getting a "Unable to find table class for current entity" error.
what I am aiming to ultimately do is enable to the users to be able to edit their username (which is an email address) and change their password when they would like.
Could someone please help me workout where I have gone wrong, and why I keep getting the "Unable to find table class for current entity" error and what I can do to fix it.
I have read as many of the relevant articles on this as I can find. I have tried various "isAuthorised" function versions and I keep getting the same error so I am sure it is something that I just don't know what to look for.
Below is my code:
The User.php file:
<?php
namespace App\Model\Entity;
use Cake\Auth\DefaultPasswordHasher;
use Cake\ORM\Entity;
/**
* User Entity.
*
* #property int $id
* #property string $username
* #property string $password
* #property string $role
* #property \Cake\I18n\Time $created
* #property \Cake\I18n\Time $activity_date
* #property bool $terms_of_service
* #property bool $paid
* #property \Cake\I18n\Time $paid_date
* #property \Cake\I18n\Time $paid_until
* #property bool $verified
* #property \App\Model\Entity\Agent[] $agents
*/
class User extends Entity
{
/**
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* Note that when '*' is set to true, this allows all unspecified fields to
* be mass assigned. For security purposes, it is advised to set '*' to false
* (or remove it), and explicitly make individual fields accessible as needed.
*
* #var array
*/
protected $_accessible = [
'*' => true,
'id' => false,
];
protected function _setPassword($password)
{
return (new DefaultPasswordHasher)->hash($password);
}
}
The UsersTable.php file:
<?php
namespace App\Model\Table;
use App\Model\Entity\User;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Users Model
*
* #property \Cake\ORM\Association\HasMany $Agents
*/
class UsersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('users');
$this->displayField('username');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Agents', [
'foreignKey' => 'user_id'
]);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->notEmpty('username','Please supply your email address')
->add('username', 'valid', ['rule'=>'email']);
$validator
->notEmpty('password','Please supply your password');
$validator
->notEmpty('role','Please select your account type')
->add('role','inList',[
'rule' => ['inList',['agent','buyer']],
'message' => 'Please select a valid role'
]);
$validator
->add('terms_of_service', 'valid', ['rule' => 'boolean'])
->notEmpty('terms_of_service','You must agree to the terms of service to register');
return $validator;
}
public function isOwnedBy($userId)
{
return $this->exists(['id' => $userId]);
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['username']));
return $rules;
}
}
The relevant pieces UsersController.php file:
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\ORM\TableRegistry;
use Cake\Event\Event;
//use Cake\ORM\Entity;
/**
* Users Controller
*
* #property \App\Model\Table\UsersTable $Users
*/
class UsersController extends AppController
{
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
$this->Auth->allow(['signup','logout']);
$this->Auth->deny(['index']);
}
// The owner of an user can edit and delete it
public function isAuthorized($user)
{
if (in_array($this->request->action, ['edit', 'delete','view'])) {
if ($this->Users->isOwnedBy($user['id'])) {
return true;
}
}
return parent::isAuthorized($user);
}
/**
* Edit method
*
* #param string|null $id User id.
* #return void Redirects on successful edit, renders view otherwise.
* #throws \Cake\Network\Exception\NotFoundException When record not found.
*/
public function edit($id = null)
{
$id = $this->Auth->user('id');
$user = $this->Users->get($id, [
'contain' => []
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$user = $this->Users->patchEntity($user, $this->request->data);
if ($this->Users->save($user)) {
$this->Flash->success(__('Your account has been edited.'));
return $this->redirect(['controller','Users','action' => 'edit']);
} else {
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
}
$this->set(compact('user'));
$this->set('_serialize', ['user']);
}
The AppController.php file
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* #copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* #link http://cakephp.org CakePHP(tm) Project
* #since 0.2.9
* #license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace App\Controller;
use Cake\Controller\Controller;
use Cake\Event\Event;
/**
* Application Controller
*
* Add your application-wide methods in the class below, your controllers
* will inherit them.
*
* #link http://book.cakephp.org/3.0/en/controllers.html#the-app-controller
*/
class AppController extends Controller
{
/**
* Initialization hook method.
*
* Use this method to add common initialization code like loading components.
*
* e.g. `$this->loadComponent('Security');`
*
* #return void
*/
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
/*'authenticate' => [
'Form' => [
'fields' => ['username' => 'email', 'password' => 'password']
]
],*/
'authorize' => 'Controller',
'loginRedirect' => [
'controller' => 'Properties',
'action' => 'myproperties'
],
'logoutRedirect' => [
'controller' => '',
'action' => 'index'
],
'unauthorizedRedirect' => $this->referer(),
//'authError' => 'You must be logged in to view that page.',
]);
// Allow the display action so our pages controller continues to work
$this->Auth->allow(['display']);
}
public function beforeFilter(Event $event)
{
$this->Auth->allow(['index', 'view','logout','search']);
}
/**
* Before render callback.
*
* #param \Cake\Event\Event $event The beforeRender event.
* #return void
*/
public function beforeRender(Event $event)
{
if (!array_key_exists('_serialize', $this->viewVars) &&
in_array($this->response->type(), ['application/json', 'application/xml'])
) {
$this->set('_serialize', true);
}
$this -> set('user', $this -> Auth -> user());
}
public function isAuthorized($user) {
$childClass = get_called_class();
if(method_exists($childClass, '_isAuthorized'))
return $childClass::_isAuthorized($user, $this -> request);
return static::_isAuthorized($user, $request);
}
static public function _isAuthorized($user, $request)
{
// Admin can access every action
if (isset($user['role']) && $user['role'] === 'admin') {
return true;
}
// Default deny
return false;
}
}
The problem is that in your AppController in beforeRender method you have this..
$this -> set('user', $this -> Auth -> user());
That's mean you will share the current user logged with all controllers and actions..
And also notice that in your edit action in Users Controller you also called a variable $user and you send it to the view by the set ...
So that is the source of your problem and that's why if you change the name by users it works !! It seems there is a conflict between the $user sent in beforeRender method and the $user sent in your edit action in Users Controller
Ok, after lots of trial and error I have solved the problem. It is a simple fix, although I do not know why it works... so if someone could explain that would be great: I changed the $user variable in my controller file's edit function to $users as well as the $user variable in the edit.ctp file to $users and hey presto it worked.

Symfony entity field : manyToMany with multiple = false - field not populated correctly

I am using symfony2 with doctrine 2.
I have a many to many relationship between two entities :
/**
* #ORM\ManyToMany(targetEntity="\AppBundle\Entity\Social\PostCategory", inversedBy="posts")
* #ORM\JoinTable(
* name="post_postcategory",
* joinColumns={#ORM\JoinColumn(name="postId", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="postCategoryId", referencedColumnName="id", onDelete="CASCADE")}
* )
*/
private $postCategories;
Now I want to let the user only select one category. For this I use the option 'multiple' => false in my form.
My form:
->add('postCategories', 'entity', array(
'label'=> 'Catégorie',
'required' => true,
'empty_data' => false,
'empty_value' => 'Sélectionnez une catégorie',
'class' => 'AppBundle\Entity\Social\PostCategory',
'multiple' => false,
'by_reference' => false,
'query_builder' => $queryBuilder,
'position' => array('before' => 'name'),
'attr' => array(
'data-toggle'=>"tooltip",
'data-placement'=>"top",
'title'=>"Choisissez la catégorie dans laquelle publier le feedback",
)))
This first gave me errors when saving and I had to change the setter as following :
/**
* #param \AppBundle\Entity\Social\PostCategory $postCategories
*
* #return Post
*/
public function setPostCategories($postCategories)
{
if (is_array($postCategories) || $postCategories instanceof Collection)
{
/** #var PostCategory $postCategory */
foreach ($postCategories as $postCategory)
{
$this->addPostCategory($postCategory);
}
}
else
{
$this->addPostCategory($postCategories);
}
return $this;
}
/**
* Add postCategory
*
* #param \AppBundle\Entity\Social\PostCategory $postCategory
*
* #return Post
*/
public function addPostCategory(\AppBundle\Entity\Social\PostCategory $postCategory)
{
$postCategory->addPost($this);
$this->postCategories[] = $postCategory;
return $this;
}
/**
* Remove postCategory
*
* #param \AppBundle\Entity\Social\PostCategory $postCategory
*/
public function removePostCategory(\AppBundle\Entity\Social\PostCategory $postCategory)
{
$this->postCategories->removeElement($postCategory);
}
/**
* Get postCategories
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPostCategories()
{
return $this->postCategories;
}
/**
* Constructor
* #param null $user
*/
public function __construct($user = null)
{
$this->postCategories = new \Doctrine\Common\Collections\ArrayCollection();
}
Now, when editing a post, I also have an issue because it uses a getter which ouputs a collection, not a single entity, and my category field is not filled correctly.
/**
* Get postCategories
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPostCategories()
{
return $this->postCategories;
}
It's working if I set 'multiple' => true but I don't want this, I want the user to only select one category and I don't want to only constraint this with asserts.
Of course there are cases when I want to let the user select many fields so I want to keep the manyToMany relationship.
What can I do ?
If you want to set the multiple option to false when adding to a ManyToMany collection, you can use a "fake" property on the entity by creating a couple of new getters and setters, and updating your form-building code.
(Interestingly, I saw this problem on my project only after upgrading to Symfony 2.7, which is what forced me to devise this solution.)
Here's an example using your entities. The example assumes you want validation (as that's slightly complicated, so makes this answer hopefully more useful to others!)
Add the following to your Post class:
public function setSingleCategory(PostCategory $category = null)
{
// When binding invalid data, this may be null
// But it'll be caught later by the constraint set up in the form builder
// So that's okay!
if (!$category) {
return;
}
$this->postCategories->add($category);
}
// Which one should it use for pre-filling the form's default data?
// That's defined by this getter. I think you probably just want the first?
public function getSingleCategory()
{
return $this->postCategories->first();
}
And now change this line in your form:
->add('postCategories', 'entity', array(
to be
->add('singleCategory', 'entity', array(
'constraints' => [
new NotNull(),
],
i.e. we've changed the field it references, and also added some inline validation - you can't set up validation via annotations as there is no property called singleCategory on your class, only some methods using that phrase.
You can setup you form type to not to use PostCategory by reference (set by_reference option to false)
This will force symfony forms to use addPostCategory and removePostCategory instead of setPostCategories.
UPD
1) You are mixing working with plain array and ArrayCollection. Choose one strategy. Getter will always output an ArrayCollection, because it should do so. If you want to force it to be plain array add ->toArray() method to getter
2) Also I understand that choice with multiple=false return an entity, while multiple=true return array independend of mapped relation (*toMany, or *toOne). So just try to remove setter from class and use only adder and remover if you want similar behavior on different cases.
/** #var ArrayCollection|PostCategory[] */
private $postCategories;
public function __construct()
{
$this->postCategories = new ArrayCollection();
}
public function addPostCategory(PostCategory $postCategory)
{
if (!$this->postCategories->contains($postCategory) {
$postCategory->addPost($this);
$this->postCategories->add($postCategory);
}
}
public function removePostCategory(PostCategory $postCategory)
{
if ($this->postCategories->contains($postCategory) {
$postCategory->removePost($this);
$this->postCategories->add($postCategory);
}
}
/**
* #return ArrayCollection|PostCategory[]
*/
public function getPostCategories()
{
return $this->postCategories;
}
In my case, the reason was that Doctrine does not have relation One-To-Many, Unidirectional with Join Table. In Documentations example is show haw we can do this caind of relation by ManyToMany (adding flag unique=true on second column).
This way is ok but Form component mixes himself.
Solution is to change geters and seters in entity class... even those generated automatically.
Here is my case (I hope someone will need it). Assumption: classic One-To-Many relation, Unidirectional with Join Table
Entity class:
/**
* #ORM\ManyToMany(targetEntity="B2B\AdminBundle\Entity\DictionaryValues")
* #ORM\JoinTable(
* name="users_responsibility",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="responsibility_id", referencedColumnName="id", unique=true, onDelete="CASCADE")}
* )
*/
private $responsibility;
/**
* Constructor
*/
public function __construct()
{
$this->responsibility = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add responsibility
*
* #param \B2B\AdminBundle\Entity\DictionaryValues $responsibility
*
* #return User
*/
public function setResponsibility(\B2B\AdminBundle\Entity\DictionaryValues $responsibility = null)
{
if(count($this->responsibility) > 0){
foreach($this->responsibility as $item){
$this->removeResponsibility($item);
}
}
$this->responsibility[] = $responsibility;
return $this;
}
/**
* Remove responsibility
*
* #param \B2B\AdminBundle\Entity\DictionaryValues $responsibility
*/
public function removeResponsibility(\B2B\AdminBundle\Entity\DictionaryValues $responsibility)
{
$this->responsibility->removeElement($responsibility);
}
/**
* Get responsibility
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getResponsibility()
{
return $this->responsibility->first();
}
Form:
->add('responsibility', EntityType::class,
array(
'required' => false,
'label' => 'Obszar odpowiedzialności:',
'class' => DictionaryValues::class,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('n')
->where('n.parent = 2')
->orderBy('n.id', 'ASC');
},
'choice_label' => 'value',
'placeholder' => 'Wybierz',
'multiple' => false,
'constraints' => array(
new NotBlank()
)
)
)
I know its a pretty old question, but the problem is still valid today.
Using a simple inline data transformer did the trick for me.
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('profileTypes', EntityType::class, [
'multiple' => false,
'expanded' => true,
'class' => ProfileType::class,
]);
// data transformer so profileTypes does work with multiple => false
$builder->get('profileTypes')
->addModelTransformer(new CallbackTransformer(
// return first item from collection
fn ($data) => $data instanceof Collection && $data->count() ? $data->first() : $data,
// convert single ProfileType into collection
fn ($data) => $data && $data instanceof ProfileType ? new ArrayCollection([$data]) : $data
));
}
PS: Array functions are available in PHP 7.4 and above.

Categories