symfony assert for entity type variable - forms

how validate entity variable, because in my point it's valid for empty select.
/**
* #Assert\NotBlank(message = "education level cannot be empty")
* #var EducationLevel[]|ArrayCollection
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\EducationLevel")
*/
private $educationLevel;
form type
->add('educationLevel', EntityType::class, [
'class' => 'AppBundle:EducationLevel',
'multiple' => true,
'choice_label' => function ($educationLevel) {
return $educationLevel->getName();
},
])

NotBlank won't work, because it checks if value is not null or not empty string or not false
NotBlank manual
What you have to do is to write custom Constraint and a validator:
Custom validator manual

You can create a validation method in the entity which can verify if $educationLevel is null or collection of EducationLevel instances.
/**
* #Assert\IsTrue(message="Education level has to be blank or...")
*/
public function isEducationLevelValid()
{
if ($this->educationLevel->isEmpty()) {
foreach ($this->educationLevel as $edulevel) {
if (!$edulevel instanceof EducationLevel) {
return false;
}
}
return true;
} else {
return false;
}
}
The method is used automatically during entity bound form submission and of course you can use it as a normal entity's method.

Related

Octobercms, redirect to home from component not working

i'm trying to redirect from component if id from slug is wrong.
Running from layout
function onBeforePageStart(){ $this->Contentloader->getMeta(); }
In component i have:
public function getMeta(){
//id checking logic goes here
if ($id == null) return Redirect::to('/'); }
Inspecting the dd(Redirect::to('/')) object I see
But it's not redirecting.
Please advice
Thanks
try this
in your component :
public function getMeta()
{
if ($id == null) return false;
}
in your layout :
function onBeforePageStart()
{
$meta = $this->Contentloader->getMeta();
if(!$meta)
return Redirect::to('/');
}
I hope help you :)
Components should be able to handle redirects without having onBeforePageStart(). This is just a quick example. Here I am checking to see if a component field is null. If it is null then return to '/'.
You can do this in a component: Make sure to utilize the Redirect class use Redirect;
public function defineProperties()
{
return [
'useSomething' => [
'title' => 'Something',
'description' => 'Testing Testing',
'default' => '',
'type' => 'text',
]
];
}
public function onRun()
{
if ($this->property('useSomething') == null) {
return Redirect::to('/');
} else {
$this->page['something'] = $this->property('useSomething');
}
}

Documenting Object with methods and their return values in JSDoc

I have a paging controller factory, that returns a paging controller Object with a bunch of methods (for the view to interact with, especially when an end-user does an action like navigate to a different page or enter some search text). It is defined something like:
/**
* Returns a paging controller object with data
* #param {Object[]} data
* #param {string} prop the property containing that data. If it's a function, it should be no-args.
* #param {filterFunc} filterer a callback that filters the data
*/
function pagingControllerFor(data, prop, filterer) {
let _currentPage = 0
let _filterFunc = filterer
let _stateChange = false
let _data;
const _ITEMS_PER_PAGE = 50
let _selectAllChecked = [];
/**
* Getter for all the data. Useful for debugging.
*/
function getAllData() {
if (prop) {
if (typeof data[prop] === 'function') {
return data[prop]()
}
return data[prop]
}
return data
}
/**
* Always returns fresh data for the controller
*/
function getData() {
let data = getAllData()
if (_filterFunc) {
if ((_stateChange) || (!_data)) {
_data = data.filter(_filterFunc)
_selectAllChecked = Array(Math.ceil(_data.length / _ITEMS_PER_PAGE)).fill(false)
_stateChange = false
}
return _data
}
return data
}
return {
/* a whole bunch of methods irrelevant to my use case on here */
getCurrentPageData : () => getData().slice(_currentPage * _ITEMS_PER_PAGE, (_currentPage + 1) * _ITEMS_PER_PAGE),
// get/set current "Select All" checkbox state
isCurrentSelectAllChecked : () => _selectAllChecked[_currentPage],
setCurrentSelectAllChecked : (checked) => _selectAllChecked[_currentPage] = checked
}
}
I am writing an event-binder for the "Select/Deselect All" checkboxes on the view being paginated. It is, as of the time I wrote this, defined to be:
/**
* Binds clicks on the current "Select/Deselect All" checkbox to the controller
* #param {string} modalType
* #param {{ getCurrentPageData : () => Array<{IsSelectedOnModal : boolean}>, setCurrentSelectAllChecked : () => boolean }} controller
* #param {Function} callback
*/
function bindToggleSelectAllEvent(modalType, controller, callback) {
callback = callback || bindToggleSelectAllEvent
const modalSelector = `#${modalType}-selector-modal`
$(`#toggle-all-${(modalType === ITEM) ? 'items' : 'categories'}-selected`)
.off('change')
.on('change', function() {
// get the state of this
let isChecked = $(this).prop('checked')
// change the selection state of all current items/categories in the controller to that state
controller.getCurrentPageData().forEach((data) => {
data.IsSelectedOnModal = isChecked
})
// tell the controller the new state of this "Select All" checkbox
controller.setCurrentSelectAllChecked(isChecked)
// Re-render modal?!
// TODO: implement this
})
}
VSCode knows what I'm doing, as it detects the relevant methods of controller, which I have specified.
However, JSDoc doesn't, for some reason:
ERROR: Unable to parse a tag's type expression for source file [my-project-path]\static\js\menu\edit\index.js in line 433 with tag title "param" and text "{{ getCurrentPageData : () => Array<{IsSelectedOnModal : boolean}>, setCurrentSelectAllChecked : () => boolean }} controller": Invalid type expression "{ getCurrentPageData : () => Array<{IsSelectedOnModal : boolean}>, setCurrentSelectAllChecked : () => boolean }": Expected "," or "}" but "=" found.
ERROR: Unable to parse a tag's type expression for source file [my-project-path]\static\js\menu\edit\index.js in line 439 with tag title "param" and text "{{ getCurrentPageData : () => Array<{IsSelectedOnModal : boolean}>, setCurrentSelectAllChecked : () => boolean }} controller": Invalid type expression "{ getCurrentPageData : () => Array<{IsSelectedOnModal : boolean}>, setCurrentSelectAllChecked : () => boolean }": Expected "," or "}" but "=" found.
What should I do about this?
VS Code support TypeScript types in JS Docs but the JS Doc tool only supports Closure types.
I believe that the arrow function type expressions that you are using are valid TypeScript types but cannot be understood by the JSDoc tool. Try using the function(): function type syntax instead
#param {{ getCurrentPageData : function(): Array<{IsSelectedOnModal : boolean}> }} controller

How to order custom attribute in Datatable?

Say my User model's balance attribute from getBalanceAttribute() returns the sum of amount from user's Transaction model, how can this be orderable in the Datatable?
User.php
public function transactions()
{
return $this->hasMany(\App\Transaction::class);
}
public function getBalanceAttribute()
{
return $this->transactions()->sum('amount');
}
Transaction.php
public function user()
{
return $this->belongsTo(\App\User::class);
}
UserCrudController.php
...
public function setup()
{
...
$this->crud->addColumn(
[
'name' => "balance",
'label' => "Balance",
'type' => 'number',
// Here the column is clickable but is not actually sorted.
'orderable' => true,
],
...
}
Thank you in advance!
Unfortunately, Backpack cannot make a model_function column orderable, since model functions are called after the SQL has already gotten back.
Sorry.

Subdocuments in MongoDB and Yii2

How should I declare the attributes public function of a class (model) that extends from ActiveRecord if I'm willing to use subdocuments?
Take for example this simple MongoDB structure:
_id: 'a34tfsert89w734y0tas9dgtuwn034t3',
name: 'Bob',
surnames: {
'first': 'Foo',
'second': 'Bar'
},
age: 27,
preferences: {
lang: 'en',
currency: 'EUR'
}
How should my attributes function look like?
public function attributes() {
return [
'_id',
'name',
'surnames',
'surnames.first', <--- like this?
.....
]
}
The MongoDb Extension for Yii 2 does not provide any special way to work with embedded documents (sub-documents). To do that you will need to first deal with custom validations. You could try the following approach: The general pattern is to first build a custom validator, say \common\validators\EmbedDocValidator.php
namespace common\validators;
use yii\validators\Validator;
class EmbedDocValidator extends Validator
{
public $scenario;
public $model;
/**
* Validates a single attribute.
* Child classes must implement this method to provide the actual validation logic.
*
* #param \yii\mongodb\ActiveRecord $object the data object to be validated
* #param string $attribute the name of the attribute to be validated.
*/
public function validateAttribute($object, $attribute)
{
$attr = $object->{$attribute};
if (is_array($attr)) {
$model = new $this->model;
if($this->scenario){
$model->scenario = $this->scenario;
}
$model->attributes = $attr;
if (!$model->validate()) {
foreach ($model->getErrors() as $errorAttr) {
foreach ($errorAttr as $value) {
$this->addError($object, $attribute, $value);
}
}
}
} else {
$this->addError($object, $attribute, 'should be an array');
}
}
}
and model for the embedded document \common\models\Preferences.php
namespace common\models;
use yii\base\Model;
class Preferences extends Model
{
/**
* #var string $lang
*/
public $lang;
/**
* #var string $currency
*/
public $currency;
public function rules()
{
return [
[['lang', 'currency'], 'required'],
];
}
}
And setup the validator in the top-level model
In common\models\User.php:
public function rules()
{
return [
[['preferences', 'name'], 'required'],
['preferences', 'common\validators\EmbedDocValidator', 'scenario' => 'user','model'=>'\common\models\Preferences'],
];
}
The general recommendation is avoiding use of embedded documents moving their attributes at the top level of the document. For example: instead of
{
name: 'Bob',
surnames: {
'first': 'Foo',
'second': 'Bar'
},
age: 27,
preferences: {
lang: 'en',
currency: 'EUR'
}
}
use following structure:
{
name: 'Bob',
surnames_first: 'Foo',
surnames_second: 'Bar'
age: 27,
preferences_lang: 'en',
preferences_currency: 'EUR'
}
which you can then declare as an ActiveRecord class by extending yii\mongodb\ActiveRecord and implement the collectionName and 'attributes' methods:
use yii\mongodb\ActiveRecord;
class User extends ActiveRecord
{
/**
* #return string the name of the index associated with this ActiveRecord class.
*/
public static function collectionName()
{
return 'user';
}
/**
* #return array list of attribute names.
*/
public function attributes()
{
return ['_id', 'name', 'surnames_first', 'surnames_second', 'age', 'preferences_lang', 'preferences_currency'];
}
}

Change dynamically validated_file_class in symfony 1.4

I have this model:
Banner:
columns:
filename: string(255)
url: string(255)
position:
type: enum
values: [top, right]
default: right
and this form:
class BannerForm extends BaseBannerForm
{
public function configure()
{
$this->widgetSchema['filename'] = new sfWidgetFormInputFileEditable(array(
'file_src' => $this->getObject()->getThumbURL(),
'is_image' => true,
'edit_mode' => $this->getObject()->exists()
));
$validated_file_class = $this->getObject()->position === 'right' ? 'bannerRightValidatedFile' : 'bannerTopValidatedFile';
$this->validatorSchema['filename'] = new sfValidatorFile(array(
'path' => sfConfig::get('sf_upload_dir'),
'mime_types' => 'web_images',
'validated_file_class' => $validated_file_class',
'required' => $this->getObject()->isNew()
));
}
}
I use different validate classes because inside it i incapsulate thumbnail operations, and the sizes of banners depends on it position field.
The problem is that $validated_file_class is always bannerRightValidatedFile class.
How i can achieve this thing ?
I can suggest 4 solutions which you can choose from:
Option 1:
You should add a update$fieldNameColumn method to the form class. In your case it should look like this:
// change validated file instance before calling save
protected function updateFilenameColumn($value)
{
if ($value instanceof sfValidatedFile)
{
$class = 'right' == $this->getValue('position') ? 'bannerRightValidatedFile' : 'bannerTopValidatedFile';
// this will not work as I thought at first time
// $this->getValidator('filename')->setOption('validated_file_class', $class);
$this->values['filename'] = new $class(
$value->getOriginalName(),
$value->getType(),
$value->getTempName(),
$value->getSize(),
$value->getPath()
);
return $this->processUploadedFile('filename');
}
return $value;
}
I think it's kind of hacky.
Option 2:
You should add a doctrine hook method to the model:
/**
* #param Doctrine_Event $event
*/
public function postSave($event)
{
$record = $event->getInvoker();
if (array_key_exists('filename', $record->getLastModified()))
{
// get the full path to the file
$file = sfConfig::get('sf_upload_dir') . '/' . $record->getFilename();
if (file_exists($file))
{
// resize the file e.g. with sfImageTransformPlugin
$img = new sfImage($file);
$img
->resize(100, 100)
->save();
}
}
}
This will work when creating records whitout a form e.g. when using fixtures.
Option 3:
Use the admin.save_object event.
public static function listenToAdminSaveObject(sfEvent $event)
{
$record = $event['object'];
if ($event['object'] instanceof Banner)
{
// use the same code as in the `postSave` example
}
}
Option 4:
Use the sfImageTransformExtraPlugin
It's kind of hard to setup and configure (and it's code is a mess :), but it makes possible to modify the size of the image whithout regenerating all the already resized ones.
You could add a sfCallbackValidator as a post-validator, and set the property accordingly.
Pseudo code (I don't have the exact function signatures at hand).
public function configure() {
// ...
$this->mergePostValidator(new sfCallbackValidator(array('callback' => array($this, 'validateFile'))));
}
public function validateFile($values) {
$realValidator = new sfValidatorFile(...);
return $realValidator->clean($values['field']);
}
If you can modify the call to the form class, you can do that:
$form = new BannerForm(array(), array('validated_file_class' => 'bannerRightValidatedFile');
$form2 = new BannerForm(array(), array('validated_file_class' => 'bannerTopValidatedFile');
And then in your form:
class BannerForm extends BaseBannerForm
{
public function configure()
{
$this->widgetSchema['filename'] = new sfWidgetFormInputFileEditable(array(
'file_src' => $this->getObject()->getThumbURL(),
'is_image' => true,
'edit_mode' => $this->getObject()->exists()
));
$this->validatorSchema['filename'] = new sfValidatorFile(array(
'path' => sfConfig::get('sf_upload_dir'),
'mime_types' => 'web_images',
'validated_file_class' => $this->options['validated_file_class'],
'required' => $this->getObject()->isNew()
));
}
}
Edit:
Since you are playing inside the admin gen, I think the best way is to use a postValidator like #Grad van Horck says.
Your validate class depend on an extra field. With a postvalidator, you can access any field inside the form. Then, you just need to create a little switch to handle the case for each position / validated class.
public function configure()
{
// ...
$this->mergePostValidator(new sfValidatorCallback(array('callback' => array($this, 'validateFile'))));
}
public function validateFile($validator, $values, $arguments)
{
$default = array(
'path' => sfConfig::get('sf_upload_dir'),
'mime_types' => 'web_images',
'required' => $this->getObject()->isNew()
);
switch ($values['position'] ) {
case 'right':
$validator = new sfValidatorFile($default + array(
'validated_file_class' => 'bannerRightValidatedFile',
));
break;
case 'top':
$validator = new sfValidatorFile($default + array(
'validated_file_class' => 'bannerTopValidatedFile',
));
default:
# code...
break;
}
$values['filename'] = $validator->clean($values['filename']);
return $values;
}