Odd question but here it goes. I would like to set up multiple arrays of labels for one model and then switch between them.
What i need is:
public function attributeLabels_1(){
array(
'line_1'=>'Authentication Number'
)
}
public function attributeLabels_2(){
array(
'line_1'=>'Receipt Number'
)
}
Is this possible and if so how would you change which array is used when?
Many thanks.
I don't remember if the list returned by attributeLabels() is cached somewhere, if it's not, then this should work:
/** implementation */
private $_currentLabelCollection = null;
public function getCurrentLabelCollection() {
return $this->_currentLabelCollection;
}
public function setCurrentLabelCollection($value) {
if(!$value || array_key_exists($value, $this->_attributeLabelCollections)) {
$this->_currentLabelCollection = $value;
} else {
throw new CException(Yii::t("error", "Model {model} does not have a label collection named {key}.", array(
'{model}' => get_class($this),
'{key}' => $value,
)));
}
}
private $_attributeLabelCollections = array(
'collection1' => array(
'line_1' => 'Authentication Number',
),
'collection2' => array(
'line_1' => 'Receipt Number',
),
);
public function attributeLabels() {
if($this->_currentLabelCollection) {
return $this->_attributeLabelCollections[$this->_currentLabelCollection];
} else {
return reset($this->_attributeLabelCollections);
}
}
/** usage */
// use labels from 'collection2'
$model->currentLabelCollection = 'collection2';
// use labels from the first defined collection
$model->currentLabelCollection = null;
Related
I've got the following model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $guarded = ['id'];
public static function rules()
{
return [
'created_at' => 'nullable',
'updated_at' => 'nullable',
'name' => 'required|string|between:1,255',
'description' => 'required|string|between:1,255',
'date_added' => 'nullable',
'date_edited' => 'nullable',
'unit' => 'required|string|between:1,255',
'unit_type' => 'required|integer',
'stock' => 'nullable|string|between:0,255',
'barcode' => 'nullable|string|between:0,32',
'tax' => 'nullable|float',
'price' => 'nullable|float',
'category_id' => 'required|integer|gt:0'
];
}
}
And there's a controller ProductController that has an action insertProduct.
class class ProductController extends ApiController { // controller class
public function insertProduct(Request $request) {
$inputJson = $request->input('data_json', null);
if(empty($inputJson)) {
$inputJson = $request->getContent();
if(empty($inputJson)) {
return $this->errorResponse(
'Either data_json formdata parameter or request body should contain a JSON string.'
);
}
}
try {
$product = $this->extractProductFromJSON($inputJson);
} catch(\Exception $e) {
return $this->errorResponse($e->getMessage(), 400);
}
// When I dump the Product ($product) instance using dd(), it is just as expected and its
// properties contain the right values.
$validator = Validator::make($product, Product::rules());
/* Above line causes exception:
TypeError: Argument 1 passed to Illuminate\Validation\Factory::make() must be of the type
array, object given,
called in /.../vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
on line 261 in file /.../vendor/laravel/framework/src/Illuminate/Validation/Factory.php
on line 98
*/
if($validator->fails()) {
return $this->errorResponse($validator->errors()->first());
}
// ...
}
// How I extract the data from the JSON string (which is input).
// Although I don't think this has anything to do with my problem.
private function extractProductFromJSON(string $json) {
$data = \json_decode($json);
if(\json_last_error() != JSON_ERROR_NONE) {
throw new \Exception('Error parsing JSON: ' . \json_last_error_msg());
}
try {
$productData = $data->product;
$productId = empty($productData->id) ? null : $productData->id;
// Product id is allowed to be absent
$product = new Product(); // My \App\Product model instance.
$product->id = $productId;
$product->name = $productData->name;
$product->description = $productData->description;
$product->date_added = $productData->date_added;
$product->date_edited = $productData->date_edited;
$product->unit = $productData->unit;
$product->unit_type = $productData->unit_type;
$product->stock = $productData->stock;
$product->barcode = $productData->barcode;
$product->tax = $productData->tax;
$product->price = $productData->price;
$product->category_id = $productData->category_id;
return $product;
} catch(\Exception $e) {
echo 'EXCEPTION...';
}
}
} // end of controller class
It seems pretty clear there's something wrong with the following line:
$validator = Validator::make($product, Product::rules());
The simplest cause I can think of, is that the validator simply does not accept objects and only wants arrays.
If not, what could be the problem?
If Laravel's validation only works with arrays, is it somehow possible to validate an object?
validator = Validator::make($product, Product::rules());
the problem is not Product::rules() but $product. Product::rules() is correct because it's return an array, but $product is an object instead of an array. You should change/convert $product to an array an example:
validator = Validator::make((array)$product, Product::rules());
Yes! You're right. If we look at the make method, we can see that it accepts rules as an array.
* #param array $data
* #param array $rules
* #param array $messages
* #param array $customAttributes
* #return \Illuminate\Validation\Validator
* #static
*/
public static function make($data, $rules, $messages = [], $customAttributes = [])
{
/** #var \Illuminate\Validation\Factory $instance */
return $instance->make($data, $rules, $messages, $customAttributes);
}
I usually validate data directly in the controller
$validator = Validator::make($info, [
'shortDescription' => 'required',
'description' => 'required',
'countryId' => 'required',
'cities' => 'required | array | min:1',
]);
class Cliente extends Eloquent
{
public static $autoValidates = true;
protected static $rules = [];
protected static function boot()
{
parent::boot();
// or static::creating, or static::updating
static::saving(function($model)
{
if ($model::$autoValidates) {
return $model->validate();
}
});
}
public function validate()
{
}
}
I'm creating a Restful application, so I'm recieving a POST request that could seem like this
$_POST = array (
'person' => array (
'id' => '1',
'name' => 'John Smith',
'age' => '45',
'city' => array (
'id' => '45',
'name' => 'London',
'country' => 'England',
),
),
);
I would like to save my person model and set its city_id.
I know that the easiest way is to set it manually with $person->city_id = $request['city']['id]; but this way isn't helping me....this code is only an example, in my real code, my model has 15 relationships
Is there any way to make it in a similar such as $person->fill($request);?
My models look like:
City
class City extends Model {
public $timestamps = false;
public $guarded= ['id'];//Used in order to prevent filling from mass assignment
public function people(){
return $this->hasMany('App\Models\Person', 'city_id');
}
}
Person
class Person extends Model {
public $timestamps = false;
public $guarded= ['id'];//Used in order to prevent filling from mass assignment
public function city(){
return $this->belongsTo('App\Models\City', 'city_id');
}
public static function savePerson($request){//Im sending a Request::all() from parameter
$person = isset($request['id']) ? self::find($request['id']) : new self();
$person->fill($request);//This won't work since my $request array is multi dimentional
$person->save();
return $person;
}
}
This is a bit tricky, but you can override fill method in your model, and set deeplyNestedAttributes() for storing attributes thats will be looking for in the request
class Person extends Model {
public $timestamps = false;
public $guarded= ['id'];//Used in order to prevent filling from mass assignment
public function city(){
return $this->belongsTo('App\Models\City', 'city_id');
}
public static function savePerson($request){//Im sending a Request::all() from parameter
$person = isset($request['id']) ? self::find($request['id']) : new self();
$person->fill($request);//This won't work since my $request array is multi dimentional
$person->save();
return $person;
}
public function deeplyNestedAttributes()
{
return [
'city_id',
// another attributes
];
}
public function fill(array $attributes = [])
{
$attrs = $attributes;
$nestedAttrs = $this->deeplyNestedAttributes();
foreach ($nestedAttrs as $attr) {
list($relationName, $relationAttr) = explode('_', $attr);
if ( array_key_exists($relationName, $attributes) ) {
if ( array_key_exists($relationAttr, $attributes[$relationName]) ) {
$attrs[$attr] = $attributes[$relationName][$relationAttr];
}
}
}
return parent::fill($attrs);
}
}
AR model Player:
public function scopes()
{
return array(
'proleague' => array(
'condition' => 'mode = "proleague"',
),
'main' => array(
'condition' => 'mode = "main"',
),
);
}
Using model Player:
Player::model()->
proleague()->
with('startposition')->
findAllByAttributes(... here some condition ...);
^^^ That's all ok. Scope-condition will be executed. But...
In my project I have many places where any scope for Player model doesn't specified and in this cases I need use this scope-condition as default:
'main' => array(
'condition' => 'mode = "main"',
)
If I add defaultScope() method to Player model like this
public function defaultScope()
{
return array(
'condition' => 'mode = "main"',
);
}
the next code
Player::model()->
proleague()->
with('startposition')->
findAllByAttributes(... here some condition ...);
won't run correct. I won't get mode = "proleague" condition, becouse I'll use defaultScope() with mode = "main".
Any suggestions? How can I resolve the problem?
You should just use the resetScope(true) method. It "removes" the defaultScope filter.
$model = Player::model()->resetScope(true)->proleague();
create a new Class for this.
<?php
## e.g. protected/models/
class MyCoreAR extends CActiveRecord
{
/**
* Switch off the default scope
*/
private $_defaultScopeDisabled = false; // Flag - whether defaultScope is disabled or not
public function setDefaultScopeDisabled($bool)
{
$this->_defaultScopeDisabled = $bool;
}
public function getDefaultScopeDisabled()
{
return $this->_defaultScopeDisabled;
}
public function noScope()
{
$obj = clone $this;
$obj->setDefaultScopeDisabled(true);
return $obj;
}
// see http://www.yiiframework.com/wiki/462/yii-for-beginners-2/#hh16
public function resetScope($bool = true)
{
$this->setDefaultScopeDisabled(true);
return parent::resetScope($bool);
}
public function defaultScope()
{
if(!$this->getDefaultScopeDisabled()) {
return array(
'condition' => 'mode = "main"',
);
} else {
return array();
}
}
}
In your code:
// no default scope
$model = Player::model()->noScope()->proleague();
// with default scope
$model = Player::model()->proleague();
I'm trying to implement an answer from the topic as covered here, but I'm having issues!
Q
I am getting the following error when loading the registration form
Notice: Undefined index: roles
with reference to
in src/Ampisoft/Bundle/etrackBundle/Form/Type/RegistrationFormType.php at line 21
RegistrationFormType::RegisterAction
Ive tested the output of $roles on the 2nd line of this snippet and I have a full array as expected.
//.......
$roles = $this->container->getParameter('security.role_hierarchy.roles');
var_dump($roles);
return $this->container->get('templating')->renderResponse('FOSUserBundle:Registration:register.html.'.$this->getEngine(), array(
'form' => $form->createView(
array('roles' => $roles,
)),
));
// .......
The problem seems to be the way Im passing (or retrieving from) the $roles array to the Type class.
RegistrationFormType
class RegistrationFormType extends AbstractType
{
protected $roles;
public function __construct($options = array())
{
$this->roles = $options['roles']; // <--------------- THIS IS line 21
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// custom form fields
$builder->add('firstname');
$builder->add('lastname');
$builder->add('roles', 'choice', array(
'required' => true,
'multiple' => true,
'choices' => $this->refactorRoles($this->roles)
));
}
public function getDefaultOptions()
{
return array(
'roles' => null
);
}
private function refactorRoles($originRoles)
{
$roles = array();
$rolesAdded = array();
// Add herited roles
foreach ($originRoles as $roleParent => $rolesHerit) {
$tmpRoles = array_values($rolesHerit);
$rolesAdded = array_merge($rolesAdded, $tmpRoles);
$roles[$roleParent] = array_combine($tmpRoles, $tmpRoles);
}
// Add missing superparent roles
$rolesParent = array_keys($originRoles);
foreach ($rolesParent as $roleParent) {
if (!in_array($roleParent, $rolesAdded)) {
$roles['-----'][$roleParent] = $roleParent;
}
}
return $roles;
}
public function getParent()
{
return 'fos_user_registration';
}
public function getName()
{
return 'etrack_user_registration';
}
}
I've tried various ways of passing the contents of the security.hierarchy.roles array through to the Type class but to no avail.
I have a base model that I extend from.
In it, I have defined two validation filters. One checks if a record is unique, the other checks if a record exists. They work the exact same way except ones return value will be the opposite of the other.
So, it doesn't sound right to write the same code twice to only return a different value.
I'd like to know how I can call one custom validator from another.
Here's my code for the unique validator:
<?php
Validator::add('unique', function($value, $rule, $options) {
$model = $options['model'];
$primary = $model::meta('key');
foreach ($options['conditions'] as $field => $check) {
if (!is_numeric($field)) {
if (is_array($check)) {
/**
* array(
* 'exists',
* 'message' => 'You are too old.',
* 'conditions' => array(
*
* 'Users.age' => array('>' => '18')
* )
* )
*/
$conditions[$field] = $check;
}
} else {
/**
* Regular lithium conditions array:
* array(
* 'exists',
* 'message' => 'This email already exists.',
* 'conditions' => array(
* 'Users.email' //no key ($field) defined
* )
* )
*/
$conditions[$check] = $value;
}
}
/**
* Checking to see if the entity exists.
* If it exists, record exists.
* If record exists, we make sure the record is not checked
* against itself by matching with the primary key.
*/
if (isset($options['values'][$primary])) {
//primary key value exists so it's probably an update
$conditions[$primary] = array('!=' => $options['values'][$primary]);
}
$exists = $model::count($conditions);
return ($exists) ? false : true;
});
?>
exists should work like this:
<?php
Validator::add('exists', function($value, $rule, $options) {
$model = $options['model'];
return !$model::unique($value, $rule, $options);
});
?>
But obviously, it can't be done that way. Would I have to define the validation function as an anonymous function, assign it to a variable and pass that in instead of the closure?
Or is there a way I can call unique from within exists?
The anonymous function method would work. And then you could use that variable in another anonymous function you define for the 'exists' validator. Here's another idea that incorporates it into your base model class:
<?php
namespace app\data\Model;
use lithium\util\Validator;
class Model extends \lithium\data\Model {
public static function __init() {
static::_isBase(__CLASS__, true);
Validator::add('unique', function($value, $rule, $options) {
$model = $options['model'];
return $model::unique(compact('value') + $options);
});
Validator::add('exists', function($value, $rule, $options) {
$model = $options['model'];
return !$model::unique(compact('value') + $options);
});
parent::__init();
}
// ... code ...
public static function unique($options) {
$primary = static::meta('key');
foreach ($options['conditions'] as $field => $check) {
if (!is_numeric($field)) {
if (is_array($check)) {
/**
* array(
* 'exists',
* 'message' => 'You are too old.',
* 'conditions' => array(
*
* 'Users.age' => array('>' => '18')
* )
* )
*/
$conditions[$field] = $check;
}
} else {
/**
* Regular lithium conditions array:
* array(
* 'exists',
* 'message' => 'This email already exists.',
* 'conditions' => array(
* 'Users.email' //no key ($field) defined
* )
* )
*/
$conditions[$check] = $options['value'];
}
}
/**
* Checking to see if the entity exists.
* If it exists, record exists.
* If record exists, we make sure the record is not checked
* against itself by matching with the primary key.
*/
if (isset($options['values'][$primary])) {
//primary key value exists so it's probably an update
$conditions[$primary] = array('!=' => $options['values'][$primary]);
}
$exists = $model::count($conditions);
return ($exists) ? false : true;
}
}
?>
I ended up creating a separate method that contains the functionality I need and then calling it from my validation filter.
I've trimmed down my base model to hold only the relevant data in it. Hope it helps someone who has a similar problem.
<?php
namespace app\extensions\data;
class Model extends \lithium\data\Model {
public static function __init() {
parent::__init();
Validator::add('unique', function($value, $rule, $options) {
$model = $options['model'];
return ($model::exists($value, $rule, $options, $model)) ? false : true;
});
Validator::add('exists', function($value, $rule, $options) {
$model = $options['model'];
return ($model::exists($value, $rule, $options, $model)) ? true : false;
});
}
public static function exists($value, $rule, $options, $model) {
$field = $options['field'];
$primary = $model::meta('key');
if (isset($options['conditions']) && !empty($options['conditions'])) {
//go here only of `conditions` are given
foreach ($options['conditions'] as $field => $check) {
if (!is_numeric($field)) {
if (is_array($check)) {
/**
* 'conditions' => array(
* 'Users.age' => array('>' => 18) //condition with custom operator
* )
*/
$conditions[$field] = $check;
}
} else {
/**
* Regular lithium conditions array:
* 'conditions' => array(
* 'Users.email' //no key ($field) defined
* )
*/
$conditions[$check] = $value;
}
}
} else {
//since `conditions` is not set, we assume
$modelName = $model::meta('name');
$conditions["$modelName.$field"] = $value;
}
/**
* Checking to see if the entity exists.
* If it exists, record exists.
* If record exists, we make sure the record is not checked
* against itself by matching with the primary key.
*/
if (isset($options['values'][$primary])) {
//primary key value exists so it's probably an update
$conditions[$primary] = array('!=' => $options['values'][$primary]);
}
return $model::count($conditions);
}
}
?>