I am using Laravel Tastyigniter system in which I want to show the locations name in dropdown in Menus module according to locations added by admin which is currently logged in.
For Example, If admin A added two locations such as location A and location B and
admin B added two locations such as location C and location D resp.
Note - The locations are getting saved in database with created_by column which is id of admin adding the location.
A) What supposed to happen -
If I logged in as admin A then in location dropdown Location A and Location B should get display
If I logged in as admin B then in location dropdown Location C and Location D should get display.
B) What is happening currently -
For both the admins all the 4 locations are getting displayed.
C) Following is the Code -
Here is Menus_model.php
<?php namespace Admin\Models;
use Admin\Traits\Locationable;
use Igniter\Flame\Database\Traits\Purgeable;
class Menus_model extends Model
{
use Purgeable;
use Locationable;
const LOCATIONABLE_RELATION = 'locations';
public $relation = [
'morphToMany' => [
'locations' => ['Admin\Models\Locations_model', 'name' =>
'locationable'],
],
];
protected $purgeable = ['locations'];
}
Here is menus_model.php which is present under models->config
<?php
$config['form']['tabs'] = [
'fields' => [
'locations' => [
'label' => 'Location',
'type' => 'relation',
'span' => 'right',
'valueFrom' => 'locations',
'nameFrom' => 'location_name',
'locationAware' => 'hide',
],
],
];
return $config;
Here is the Locations_model.php file code under models folder
<?php namespace Admin\Models;
use Admin\Traits\HasDeliveryAreas;
use Admin\Traits\HasWorkingHours;
use Igniter\Flame\Database\Attach\HasMedia;
use Igniter\Flame\Database\Traits\HasPermalink;
use Igniter\Flame\Database\Traits\Purgeable;
use Igniter\Flame\Location\Models\AbstractLocation;
use DB;
/**
* Locations Model Class
*
* #package Admin
*/
class Locations_model extends AbstractLocation
{
use HasWorkingHours;
use HasDeliveryAreas;
use HasPermalink;
use Purgeable;
use HasMedia;
const LOCATION_CONTEXT_SINGLE = 'single';
const LOCATION_CONTEXT_MULTIPLE = 'multiple';
protected $appends = ['location_thumb'];
protected $hidden = ['options'];
public $casts = [
'location_country_id' => 'integer',
'location_lat' => 'double',
'location_lng' => 'double',
'offer_delivery' => 'boolean',
'offer_collection' => 'boolean',
'delivery_time' => 'integer',
'collection_time' => 'integer',
'last_order_time' => 'integer',
'reservation_time_interval' => 'integer',
'reservation_stay_time' => 'integer',
'location_status' => 'boolean',
'options' => 'serialize',
'location_city' => 'integer',
'region_id'=>'integer',
];
public $relation = [
'hasMany' => [
'working_hours' => ['Admin\Models\Working_hours_model', 'delete' =>
TRUE],
'delivery_areas' => ['Admin\Models\Location_areas_model', 'delete'
=> TRUE],
'reviews' => ['Admin\Models\Reviews_model', 'delete' => TRUE],
],
'belongsTo' => [
'country' => ['System\Models\Countries_model', 'otherKey' =>
'country_id', 'foreignKey' => 'location_country_id'],
'city' => ['Admin\Models\City_model', 'otherKey' => 'city_id', 'foreignKey' => 'location_city'],
'region' => ['Admin\Models\Region_model', 'otherKey' => 'region_id', 'foreignKey' => 'region_id'],
],
'belongsToMany' => [
'tables' => ['Admin\Models\Tables_model', 'table' => 'location_tables'],
'cuisines' => ['Admin\Models\Cuisines_model', 'table' => 'location_cuisines'],
],
];
protected $purgeable = ['tables', 'delivery_areas','cuisines'];
public $permalinkable = [
'permalink_slug' => [
'source' => 'location_name',
'controller' => 'local',
],
];
public $mediable = [
'thumb',
'gallery' => ['multiple' => TRUE],
];
protected static $allowedSortingColumns = [
'distance asc', 'distance desc',
'reviews_count asc', 'reviews_count desc',
'location_id asc', 'location_id desc',
'location_name asc', 'location_name desc',
];
public $url;
protected static $defaultLocation;
public static function onboardingIsComplete()
{
if (!$defaultId = params('default_location_id'))
return FALSE;
if (!$model = self::isEnabled()->find($defaultId))
return FALSE;
return isset($model->getAddress()['location_lat'])
AND isset($model->getAddress()['location_lng'])
AND ($model->hasDelivery() OR $model->hasCollection())
AND isset($model->options['hours'])
AND $model->delivery_areas->where('is_default', 1)->count() > 0;
}
public function getWeekDaysOptions()
{
return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
}
//
// Events
//
protected function afterFetch()
{
$this->parseOptionsValue();
}
protected function beforeSave()
{
$this->parseOptionsValue();
}
protected function afterSave()
{
$this->performAfterSave();
}
protected function beforeDelete()
{
Location_tables_model::where('location_id', $this->getKey())->delete();
Location_cuisines_model::where('location_id', $this->getKey())->delete();
}
//
// Scopes
//
/**
* Scope a query to only include enabled location
*
* #return $this
*/
public function scopeIsEnabled($query)
{
return $query->where('location_status', 1);
}
public function scopeListFrontEnd($query, array $options = [])
{
extract(array_merge([
'page' => 1,
'pageLimit' => 20,
'sort' => null,
'search' => null,
'latitude' => null,
'longitude' => null,
], $options));
if ($latitude AND $longitude)
$query->selectDistance($latitude, $longitude);
$searchableFields = ['location_name', 'location_address_1', 'location_address_2', 'location_city',
'location_state', 'location_postcode', 'description'];
if (!is_array($sort)) {
$sort = [$sort];
}
foreach ($sort as $_sort) {
if (in_array($_sort, self::$allowedSortingColumns)) {
$parts = explode(' ', $_sort);
if (count($parts) < 2) {
array_push($parts, 'desc');
}
[$sortField, $sortDirection] = $parts;
$query->orderBy($sortField, $sortDirection);
}
}
$search = trim($search);
if (strlen($search)) {
$query->search($search, $searchableFields);
}
return $query->paginate($pageLimit, $page);
}
//
// Accessors & Mutators
//
public function getLocationThumbAttribute()
{
return $this->hasMedia() ? $this->getThumb() : null;
}
public function getDeliveryTimeAttribute($value)
{
return (int)$value;
}
public function getCollectionTimeAttribute($value)
{
return (int)$value;
}
public function getFutureOrdersAttribute($value)
{
return (bool)$value;
}
public function getReservationTimeIntervalAttribute($value)
{
return (int)$value;
}
//
// Helpers
//
public function setUrl($suffix = null)
{
if (is_single_location())
$suffix = '/menus';
$this->url = site_url($this->permalink_slug.$suffix);
}
public function hasGallery()
{
return $this->hasMedia('gallery');
}
public function getGallery()
{
$gallery = array_get($this->options, 'gallery');
$gallery['images'] = $this->getMedia('gallery');
return $gallery;
}
public function parseOptionsValue()
{
$value = #unserialize($this->attributes['options']) ?: [];
$this->parseHoursFromOptions($value);
$this->parseAreasFromOptions($value);
$this->attributes['options'] = #serialize($value);
return $value;
}
public function listAvailablePayments()
{
$result = [];
$payments = array_get($this->options, 'payments', []);
$paymentGateways = Payments_model::listPayments();
foreach ($paymentGateways as $payment) {
if ($payments AND !in_array($payment->code, $payments)) continue;
$result[$payment->code] = $payment;
}
return collect($result);
}
public function performAfterSave()
{
$this->restorePurgedValues();
if (array_key_exists('hours', $this->options)) {
$this->addOpeningHours($this->options['hours']);
}
if (array_key_exists('delivery_areas', $this->attributes)) {
$this->addLocationAreas($this->attributes['delivery_areas']);
}
if (array_key_exists('tables', $this->attributes)) {
$this->addLocationTables($this->attributes['tables']);
}
if (array_key_exists('cuisines', $this->attributes)) {
$this->addLocationCuisines($this->attributes['cuisines']);
}
}
public static function getDefault()
{
if (self::$defaultLocation !== null) {
return self::$defaultLocation;
}
$defaultLocation = self::isEnabled()->where('location_id', params('default_location_id'))->first();
if (!$defaultLocation) {
$defaultLocation = self::isEnabled()->first();
if ($defaultLocation) {
params('default_location_id', $defaultLocation->getKey());
params()->save();
}
}
return self::$defaultLocation = $defaultLocation;
}
/**
* Create a new or update existing location tables
*
* #param array $tables
*
* #return bool
*/
public function addLocationTables($tables = [])
{
return $this->tables()->sync($tables);
}
public function addLocationCuisines($cuisines = [])
{
return $this->cuisines()->sync($cuisines);
}
}
Here is locations_model.php which is present under models->config folder
<?php
$config['form']['tabs'] = [
'defaultTab' => 'lang:admin::lang.locations.text_tab_general',
'fields' => [
'location_name' => [
'label' => 'lang:admin::lang.label_name',
'type' => 'text',
'span' => 'left',
],
'location_email' => [
'label' => 'lang:admin::lang.label_email',
'type' => 'text',
'span' => 'right',
],
'location_telephone' => [
'label' => 'lang:admin::lang.locations.label_telephone',
'type' => 'text',
'span' => 'left',
],
'location_status' => [
'label' => 'lang:admin::lang.label_status',
'type' => 'switch',
'default' => 1,
'span' => 'right',
],
'created_by' => [
'type' => 'hidden',
'default' => isset($_SESSION['user_id']) ? $_SESSION['user_id'] : '',
],
],
];
return $config;
UPDATED
Basically I want to diaply locations in menus form , Currently in menus form all the locations are getting display and the code for this is mentioned below
This is Menus.php controller
<?php namespace Admin\Controllers;
use Admin\Classes\AdminController;
use Admin\Models\Menu_options_model;
use AdminMenu;
use ApplicationException;
class Menus extends AdminController
{
public $implement = [
'Admin\Actions\ListController',
'Admin\Actions\FormController',
'Admin\Actions\LocationAwareController',
];
public $listConfig = [
'list' => [
'model' => 'Admin\Models\Menus_model',
'title' => 'lang:admin::lang.menus.text_title',
'emptyMessage' => 'lang:admin::lang.menus.text_empty',
'defaultSort' => ['menu_id', 'DESC'],
'configFile' => 'menus_model',
],
];
protected $requiredPermissions = 'Admin.Menus';
public function __construct()
{
parent::__construct();
AdminMenu::setContext('menus');
}
public function edit_onChooseMenuOption($context, $recordId)
{
$menuOptionId = post('Menu._options');
if (!$menuOption = Menu_options_model::find($menuOptionId))
throw new ApplicationException('Please select a menu option to
attach');
$model = $this->asExtension('FormController')->formFindModelObject($recordId);
$menuItemOption = $model->menu_options()->create(['option_id' => $menuOptionId]);
$menuOption->option_values()->get()->each(function ($model) use ($menuItemOption) {
$menuItemOption->menu_option_values()->create([
'menu_option_id' => $menuItemOption->menu_option_id,
'option_value_id' => $model->option_value_id,
'new_price' => $model->price,
]);
});
$model->reload();
$this->asExtension('FormController')->initForm($model, $context);
flash()->success(sprintf(lang('admin::lang.alert_success'), 'Menu item option attached'))->now();
$formField = $this->widgets['form']->getField('menu_options');
return [
'#notification' => $this->makePartial('flash'),
'#'.$formField->getId('group') => $this->widgets['form']->renderField($formField, [
'useContainer' => FALSE,
]),
];
}
}
Below is Locaations.php controller
<?php namespace Admin\Controllers;
use Admin\Facades\AdminLocation;
use Admin\Models\Locations_model;
use AdminMenu;
use Exception;
use Geocoder;
class Locations extends \Admin\Classes\AdminController
{
public $implement = [
'Admin\Actions\ListController',
'Admin\Actions\FormController',
];
public $listConfig = [
'list' => [
'model' => 'Admin\Models\Locations_model',
'title' => 'lang:admin::lang.locations.text_title',
'emptyMessage' => 'lang:admin::lang.locations.text_empty',
'defaultSort' => ['location_id', 'DESC'],
'configFile' => 'locations_model',
],
];
protected $requiredPermissions = 'Admin.Locations';
public function __construct()
{
parent::__construct();
AdminMenu::setContext('locations', 'restaurant');
}
public function remap($action, $params)
{
if ($action != 'settings' AND AdminLocation::check())
return $this->redirect('locations/settings');
return parent::remap($action, $params);
}
public function settings($context = null)
{
if (!AdminLocation::check())
return $this->redirect('locations');
$this->asExtension('FormController')->edit('edit', $this-
>getLocationId());
}
public function index_onSetDefault($context = null)
{
$defaultId = post('default');
if (Locations_model::updateDefault(['location_id' => $defaultId])) {
flash()->success(sprintf(lang('admin::lang.alert_success'),
lang('admin::lang.locations.alert_set_default')));
}
return $this->refreshList('list');
}
public function settings_onSave($context = null)
{
try {
$this->asExtension('FormController')->edit_onSave('edit',
params('default_location_id'));
return $this->refresh();
}
catch (Exception $ex) {
$this->handleError($ex);
}
}
public function listOverrideColumnValue($record, $column, $alias = null)
{
if ($column->type != 'button')
return null;
if ($column->columnName != 'default')
return null;
$attributes = $column->attributes;
$column->iconCssClass = 'fa fa-star-o';
if ($record->getKey() == params('default_location_id')) {
$column->iconCssClass = 'fa fa-star';
}
return $attributes;
}
public function formExtendQuery($query)
{
if ($locationId = $this->getLocationId())
$query->where('location_id', $locationId);
}
public function formAfterSave($model)
{
if (post('Location.options.auto_lat_lng')) {
if ($logs = Geocoder::getLogs())
flash()->error(implode(PHP_EOL, $logs))->important();
}
}
}
Views
Now the n views folder there is folder names menus and under that folder there is create.php file for displaying create menu form
The code in views->menus->create.php file is below
<div class="row-fluid">
<?= form_open(current_url(),
[
'id' => 'edit-form',
'role' => 'form',
'method' => 'POST',
]
); ?>
<?= $this->renderForm(); ?>
<?= form_close(); ?>
</div>
FormController
Now the renderForm() function is present at path app/admin/actions/FormController.php which we have defined in Locations and Menus controller under public $implement = ['Admin\Actions\FormController'];
Ther renderForm() function is as follow
public function renderForm($options = [])
{
if (!$this->formWidget) {
throw new Exception(lang('admin::lang.form.not_ready'));
}
if (!is_null($this->toolbarWidget)) {
$form[] = $this->toolbarWidget->render();
}
$form[] = $this->formWidget->render($options);
return implode(PHP_EOL, $form);
}
Widgets
At last the there are widgets for input fields like select, text, radio, checkbox etc. In our case we have widget name field_selectlist, which is present at path app/admin/widgets/form/field_selectlist.php
The field_selectlist.php file has code as below
<?php
$fieldOptions = $field->options();
//print_r($fieldOptions);die; All the locations are displaying here.
$isCheckboxMode = $field->config['mode'] ?? 'checkbox';
$selectMultiple = $isCheckboxMode == 'checkbox';
$checkedValues = (array)$field->value;
$enableFilter = (count($fieldOptions) > 20);
?>
<div class="control-selectlist">
<select
data-control="selectlist"
id="<?= $field->getId() ?>"
name="<?= $field->getName() ?><?= $selectMultiple ? '[]' : '' ?>"
<?php if ($field->placeholder) { ?>data-non-selected-text="<?=
e(lang($field->placeholder)) ?>"<?php } ?>
<?= $selectMultiple ? 'multiple="multiple"' : '' ?>
data-enable-filtering="<?= $enableFilter; ?>"
data-enable-case-insensitive-filtering="<?= $enableFilter; ?>"
<?= $field->getAttributes() ?>>
<?php if ($field->placeholder) { ?>
<option value=""><?= e(lang($field->placeholder)) ?></option>
<?php } ?>
<?php
foreach ($fieldOptions as $value => $option) { ?>
<?php
if (!is_array($option)) $option = [$option];
if ($field->disabled AND !in_array($value, $checkedValues)) continue;
?>
<option
<?= in_array($value, $checkedValues) ? 'selected="selected"' : '' ?>
value="<?= $value ?>">
<?= e(is_lang_key($option[0]) ? lang($option[0]) : $option[0]) ?>
<?php if (isset($option[1])) { ?>
<span><?= e(is_lang_key($option[1]) ? lang($option[1]) :
$option[1]) ?></span>
<?php } ?>
</option>
<?php } ?>
I have token based authorization, for which i have did below changes.
In User model, override findIdentityByAccessToken() method as below.
public static function findIdentityByAccessToken($token, $type = null)
{
$userlogin = Userdevices::find()->where(['access_token' => $token])->one();
if ($userlogin == array()) {
return null;
} else {
$User = Users::findOne(['id' => $userlogin->user_id]);
if (!count($User))
{
return null;
}
else {
$dbUser = [
'id' => $User->id,
];
return new static($dbUser);
}
}
}
In Controller, I add behaviors() as below.
public function behaviors()
{
$behaviors[] = [
'class' => \yii\filters\ContentNegotiator::className(),
'formats' => [
'application/json' => \yii\web\Response::FORMAT_JSON,
],
];
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
];
return $behaviors;
}
When API does not get token or token is not valid it gives below response
{
"name": "Unauthorized",
"message": "You are requesting with an invalid credential.",
"code": 0,
"status": 401,
"type": "yii\\web\\UnauthorizedHttpException"
}
I want to change response as per my requirement as below.
{
"code": 401,
"name": "Unauthorized",
"is_logout": "Y",
"status": "error",
"message": "logout"
}
You can change format of response using beforeSend event of yii\web\Response.
For example add following methods in your api controller:
public function init()
{
parent::init();
\Yii::$app->response->on(
\yii\web\Response::EVENT_BEFORE_SEND,
[$this, 'beforeResponseSend']
);
}
public function beforeResponseSend(\yii\base\Event $event)
{
/**
* #var \yii\web\Response $response
*/
$response = $event->sender;
if ($response->data['status'] == 401) {
$response->data = [
'code' => 401,
'name' => 'Unauthorized',
'is_logout' => 'Y',
'status' => 'error',
'message' => 'logout',
];
}
}
The init method of controller registers the beforeSend event. The beforeResponseSend method handles the event and changes the response format.
If you want to format response in multiple controller it might be better to put the event handler into own class for example
namespace app\components;
class ErrorResponseHelper
{
public static function beforeResponseSend(Event $event)
{
// ... formating code ...
}
}
And register the event in config/web.php
return [
// ...
'components' => [
'response' => [
'class' => 'yii\web\Response',
'on beforeSend' => [
\app\components\ErrorResponseHelper::class,
'beforeResponseSend',
],
],
],
];
But be careful with this solution because this way the \app\components\ErrorResponseHelper::beforeResponseSend will be called during each request.
The user has to pass in the URL a query string which will return different pagination, if he entered none, one or both queries, the result will change. The code below works in CakePHP, but it looks horrible, is there a better way to achieve what I'm trying to do without those if, else if conditions?
if($this->request->query('date'))
{
$this->paginate = [
'conditions' => [
'Reservations.id_venue' => $this->Auth->user('id_venue_manager'),
'Reservations.date' => $this->request->query('date'),
],
];
}
else if($this->request->query('id_user'))
{
$this->paginate = [
'conditions' => [
'Reservations.id_venue' => $this->Auth->user('id_venue_manager'),
'Reservations.id_user' => $this->request->query('id_user'),
],
];
}
else if($this->request->query('id_user') && $this->request->query('date'))
{
$this->paginate = [
'conditions' => [
'Reservations.id_venue' => $this->Auth->user('id_venue_manager'),
'Reservations.id_user' => $this->request->query('id_user'),
'Reservations.date' => $this->request->query('date'),
],
];
}
else
{
$this->paginate = [
'conditions' => [
'Reservations.id_venue' => $this->Auth->user('id_venue_manager'),
],
];
}
Try this
<?php
public function someMethod() {
$this->paginate = [
'conditions' => $this->getPaginationConditions();
];
}
private function getPaginationConditions()
{
$conditions = ['Reservations.id_venue' => $this->Auth->user('id_venue_manager')];
if ($date = $this->request->query('date')) {
$conditions['Reservations.date'] = $date;
}
if ($user = $this->request->query('id_user')) {
$conditions['Reservations.id_user'] = $user;
}
return $conditions;
}
To initialize my app I have the following route:
/initialize
This returns Taxonomies, Enumerables and a couple of other taxonomy like collections. This saves multiple HTTP requests.
Although with Dingo / Fractal, I cannot see how I can respond with multiple collections?
e.g.
return [
'taxonomies' => $this->response->collection($taxonomies, new TaxonomyTransformer);
'enumerables' => $this->response->collection($enumerables, new EnumerableTransformer);
'otherStuff' => $this->response->collection($otherStuff, new OtherStuffTransformer);
];
return response()->json([
'data' => [
'taxonomies' => $this->fractal->collection($taxonomies, new TaxonomyTransformer);
'enumerables' => $this->fractal->collection($enumerables, new EnumerableTransformer);
'otherStuff' => $this->fractal->collection($otherStuff, new OtherStuffTransformer);
]
], 200);
This should return the JSON in the format you are looking for.
I have the same issue ,and I found the solution from How to use Transformer in one to many relationship. #1054.
Here is the collection I want to return with the transfomer of dingo in my controller.
$user = User::where('email','=',$input['email'])->with('departments')->with('roles')->get();
DepartmentTransformer
class DepartmentTransformer extends TransformerAbstract
{
public function transform($department)
{
return [
'id' => $department['id'],
'name' => $department['name'],
'level' => $department['level'],
'parent_id' => $department['parent_id']
];
}
}
RolesTransformer
class RolesTransformer extends TransformerAbstract
{
public function transform($role)
{
return [
'name' => $role['name'],
'slug' => $role['slug'],
'description' => $role['description'],
'level' => $role['level']
];
}
}
UserTransformer
class UserTransformer extends TransformerAbstract
{
protected $defaultIncludes = ['departments','roles'];
public function transform($user)
{
return [
'id' => $user['id'],
'name' => $user['name'],
'email' => $user['email'],
'phone' => $user['phone'],
];
}
public function includeDepartments(User $user)
{
$dept = $user->departments;
return $this->collection($dept, new DepartmentTransformer());
}
public function includeRoles(User $user)
{
$rl = $user->roles;
return $this->collection($rl, new RolesTransformer());
}
}
In my controller
$user = User::where('email','=',$input['email'])->with('departments')->with('roles')->get();
return $this->response->collection($user, new UserTransformer());
And I got the result
"data": {
{
"id": 43,
"name": "test7",
"email": "test7#foxmail.com",
"phone": "186********",
"departments": {
"data": {
{
"id": 1,
"name": "业务一部",
"level": 1,
"parent_id": 0
}
}
},
"roles": {
"data": {
{
"name": "agent",
"slug": "agent",
"description": "业务员",
"level": 1
}
}
}
}
}
Please take note of the usage of $defaultIncludes and includeXXX() methonds in the UserTransformer.You can get more detail info from Fractal Doc.
How do I apply a filter to a field element with the contents of an array?
For example:
$this->add(
"name" => "tags",
"type" => "text",
"filter" => array(
array("name" => "StripTags"),
array("name" => "StringTrim")
)
);
$tags[0] = "PHP";
$tags[1] = "CSS";
If I attempt to filter I receive an error saying a scalar object is excepted, array given.
This isn't really possible at this time. Your best bet is to use a Callback filter and filter each Item individually. Something like this
$this->add(
"name" => "tags",
"type" => "text",
"filter" => array(
array("name" => "Callback", "options" => array(
"callback" => function($tags) {
$strip = new \Zend\Filter\StripTags();
$trim = new \Zend\Filter\StringTrim();
foreach($tags as $key => $tag) {
$tag = $strip->filter($tag);
$tag = $trim->filter($tag);
$tags[$key] = $tag;
}
return $tags;
}))
)
);
I realize this is old but you can specify the input type as ArrayInput and InputFilter will handle it as expected:
"name" => "tags",
"type" => "Zend\\InputFilter\\ArrayInput", // Treat this field as an array of inputs
"filter" => array(
array("name" => "StripTags"),
array("name" => "StringTrim")
)
I've made a CollectionValidator that applies an existing validator to all items in an array.
I'm using it with Apigility as such:
'input_filter_specs' => [
'Api\\Contact\\Validator' => [
[
'name' => 'addresses',
'required' => false,
'filters' => [],
'validators' => [
[
'name' => 'Application\\Validator\\CollectionValidator',
'options' => ['validator' => 'Api\\Address\\Validator']
]
],
'description'=> 'List of addresses for contact'
],
[
'name' => 'birthdate',
# ...
]
],
]
I'm not sure if this is how you would use a validator inside a controller, but probably something like this:
new Collection(array('validator' => 'Zend\Validator\CreditCard'))
It returns validation_messages per index. Let's say it was REST POST request to create a contact, it indicates that the second address contains an error in the zipcode field.
{
"detail": "Failed Validation",
"status": 422,
"title": "Unprocessable Entity",
"type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
"validation_messages": {
"addresses": {
"1": {
"zipcode": {
"notAlnum": "The input contains characters which are non alphabetic and no digits"
}
}
},
"birthdate": {
"dateInvalidDate": "The input does not appear to be a valid date"
}
}
}
The Collection validator:
<?php
namespace Application\Validator;
class Collection extends \Zend\Validator\AbstractValidator implements \Zend\ServiceManager\ServiceLocatorAwareInterface {
protected $serviceLocator;
protected $em;
protected $messages;
protected $options = array(
'validator' => null
);
public function setServiceLocator(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator) {
$this->serviceLocator = $serviceLocator->getServiceLocator();
}
public function getServiceLocator() {
return $this->serviceLocator;
}
public function isValid($array) {
$inputFilterManager = $this->getServiceLocator()->get('inputfiltermanager');
$validatorName = $this->getOption('validator');
$this->messages = [];
$isvalid = true;
foreach($array as $index => $item) {
$inputFilter = $inputFilterManager->get($validatorName);
$inputFilter->setData($item);
$isvalid = $isvalid && $inputFilter->isValid($item);
foreach($inputFilter->getMessages() as $field => $errors) {
foreach($errors as $key => $string) {
$this->messages[$index][$field][$key] = $string;
}
}
}
return $isvalid;
}
public function getMessages() {
return $this->messages;
}
}
Current limitations:
No support for translation
Only the errors for the first erroneous array item are returned.
I had a very simular issue and I was able to solve it with Zend\Form\Element\Collection.
With the Collection Element I was able to validate inputs that looks like
$post = [
[
'idUser' => 1,
'address' => 'foo street',
],
[
'idUser' => 2,
'address' => 'bar street',
],
];
For a more detailed explanation check out the Zend Documentation and this working example