'guard' => false in newEntity() options not working in Cakephp4 - php

There's certainly something that I do wrong, but I can't use correctly the guard option of newEntity() method.
// Entity
class Bloc extends AbstractBloc
{
protected $_accessible = [
'*' => false // All fields are protected against mass assignment
];
}
'guard' => false doesn't allow to save my entity in this example :
// Controller
public function test()
{
$entity = $this->Blocs->newEntity([
'titre' => 'TEST ASSIGNEMENT',
'rubrique_id' => 282,
'description' => 'Content'
], ['guard' => false]); // guard is false but nothing changes
if ($this->Blocs->save($entity)) {
return $this->redirect([...]);
}
else {
die('save is false');
}
}
What am I doing wrong ?

There is no such option for Table::newEntity(), the guard option belongs to Entity::set(). The option for newEntity() is called accessibleFields.
$entity = $this->Blocs->newEntity(
[
'titre' => 'TEST ASSIGNEMENT',
'rubrique_id' => 282,
'description' => 'Content',
],
[
'accessibleFields' => [
// '*' => true
'titre' => true,
'rubrique_id' => true,
'description' => true,
],
]
);
https://book.cakephp.org/4/en/orm/saving-data.html#changing-accessible-fields

Related

PHP Laminas DoctrineObjectInputFilter get value of other property in Callback input filter

I am working with Laminas DoctrineObjectInputFilter and want to get value of other property in Callback input filter like this code is in init function of Filter class which extends DoctrineObjectInputFilter
// input filter whose value is required
$this->add([
'name' => 'name',
'allow_empty' => false,
'filters' => []
]);
// Input filter in which I want value of input name
$this->add([
'name' => 'value',
'allow_empty' => true,
'filters' => [
[
'name' => 'Callback',
'options' => [
'callback' => function ($value) {
$name = // want to get property name value here
if (key_exists($name, $this->applicationConfig) && gettype($value) === 'string') {
return trim(strip_tags($value));
}
else {
return trim($value);
}
return $value;
},
],
],
],
]);
have checked $this->getRawValues() but its returning null for all inputs.
a bit late but I guess you 're searching for $context. Since the value of name is in the same InputFilter instance, you can simply use $context in your callback function.
<?php
declare(strict_types=1);
namespace Marcel\InputFilter;
use Laminas\Filter\StringTrim;
use Laminas\Filter\StripTags;
use Laminas\Filter\ToNull;
use Laminas\InputFilter\InputFilter;
use Laminas\Validator\Callback;
class ExampleInputFilter extends InputFilter
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'allow_empty' => false,
'filters' => [
[ 'name' => StripTags::class ],
[ 'name' => StringTrim::class ],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
]);
$this->add([
'name' => 'value',
'required' => true,
'allow_empty' => true,
'filters' => [],
'validators' => [
[
'name' => Callback::class,
'options' => [
'callback' => function($value, $context) {
$name = $context['name'];
...
},
],
],
],
]);
}
}

PHP Laravel GraphQL Query Declaration Incompatibility issue

I followed the following article to setup laravel with graphql on my local machine:
https://auth0.com/blog/developing-and-securing-graphql-apis-with-laravel
I following the complete article step by step without any issue. But when I run my app using
php artisan serve
and hit the endpoint localhost:8000/graphql, I get the following error.
[Sat Aug 31 23:09:20 2019] PHP Fatal error: Declaration of App\GraphQL\Queries\WineQuery::type() must be compatible with
Rebing\GraphQL\Support\Field::type(): GraphQL\Type\Definition\Type
in /Users/sushilsingh/Projects/winestore/app/GraphQL/Queries/WineQuery.php on line 9
Here is my WineQuery.php
<?php
namespace App\GraphQL\Queries;
use App\Wine;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
class WineQuery extends Query
{
protected $attributes = [
'name' => 'wine',
];
public function type()
{
return GraphQL::type('Wine');
}
public function args()
{
return [
'id' => [
'name' => 'id',
'type' => Type::int(),
'rules' => ['required']
],
];
}
public function resolve($root, $args)
{
return Wine::findOrFail($args['id']);
}
}
Here is graphql.php
<?php
declare(strict_types=1);
use example\Type\ExampleType;
use example\Query\ExampleQuery;
use example\Mutation\ExampleMutation;
use example\Type\ExampleRelationType;
return [
// The prefix for routes
'prefix' => 'graphql',
// The routes to make GraphQL request. Either a string that will apply
// to both query and mutation or an array containing the key 'query' and/or
// 'mutation' with the according Route
//
// Example:
//
// Same route for both query and mutation
//
// 'routes' => 'path/to/query/{graphql_schema?}',
//
// or define each route
//
// 'routes' => [
// 'query' => 'query/{graphql_schema?}',
// 'mutation' => 'mutation/{graphql_schema?}',
// ]
//
'routes' => '{graphql_schema?}',
// The controller to use in GraphQL request. Either a string that will apply
// to both query and mutation or an array containing the key 'query' and/or
// 'mutation' with the according Controller and method
//
// Example:
//
// 'controllers' => [
// 'query' => '\Rebing\GraphQL\GraphQLController#query',
// 'mutation' => '\Rebing\GraphQL\GraphQLController#mutation'
// ]
//
'controllers' => \Rebing\GraphQL\GraphQLController::class.'#query',
// Any middleware for the graphql route group
'middleware' => [],
// Additional route group attributes
//
// Example:
//
// 'route_group_attributes' => ['guard' => 'api']
//
'route_group_attributes' => [],
// The name of the default schema used when no argument is provided
// to GraphQL::schema() or when the route is used without the graphql_schema
// parameter.
'default_schema' => 'default',
// The schemas for query and/or mutation. It expects an array of schemas to provide
// both the 'query' fields and the 'mutation' fields.
//
// You can also provide a middleware that will only apply to the given schema
//
// Example:
//
// 'schema' => 'default',
//
// 'schemas' => [
// 'default' => [
// 'query' => [
// 'users' => 'App\GraphQL\Query\UsersQuery'
// ],
// 'mutation' => [
//
// ]
// ],
// 'user' => [
// 'query' => [
// 'profile' => 'App\GraphQL\Query\ProfileQuery'
// ],
// 'mutation' => [
//
// ],
// 'middleware' => ['auth'],
// ],
// 'user/me' => [
// 'query' => [
// 'profile' => 'App\GraphQL\Query\MyProfileQuery'
// ],
// 'mutation' => [
//
// ],
// 'middleware' => ['auth'],
// ],
// ]
//
// 'schemas' => [
// 'default' => [
// 'query' => [
// // 'example_query' => ExampleQuery::class,
// ],
// 'mutation' => [
// // 'example_mutation' => ExampleMutation::class,
// ],
// 'middleware' => [],
// 'method' => ['get', 'post'],
// ],
// ],
'schemas' => [
'default' => [
'query' => [
'wine' => App\GraphQL\Queries\WineQuery::class,
'wines' => App\GraphQL\Queries\WinesQuery::class,
]
],
],
// The types available in the application. You can then access it from the
// facade like this: GraphQL::type('user')
//
// Example:
//
// 'types' => [
// 'user' => 'App\GraphQL\Type\UserType'
// ]
//
'types' => [
// 'example' => ExampleType::class,
// 'relation_example' => ExampleRelationType::class,
// \Rebing\GraphQL\Support\UploadType::class,
'Wine' => App\GraphQL\Types\WineType::class,
],
// The types will be loaded on demand. Default is to load all types on each request
// Can increase performance on schemes with many types
// Presupposes the config type key to match the type class name property
'lazyload_types' => false,
// This callable will be passed the Error object for each errors GraphQL catch.
// The method should return an array representing the error.
// Typically:
// [
// 'message' => '',
// 'locations' => []
// ]
'error_formatter' => ['\Rebing\GraphQL\GraphQL', 'formatError'],
/*
* Custom Error Handling
*
* Expected handler signature is: function (array $errors, callable $formatter): array
*
* The default handler will pass exceptions to laravel Error Handling mechanism
*/
'errors_handler' => ['\Rebing\GraphQL\GraphQL', 'handleErrors'],
// You can set the key, which will be used to retrieve the dynamic variables
'params_key' => 'variables',
/*
* Options to limit the query complexity and depth. See the doc
* # https://github.com/webonyx/graphql-php#security
* for details. Disabled by default.
*/
'security' => [
'query_max_complexity' => null,
'query_max_depth' => null,
'disable_introspection' => false,
],
/*
* You can define your own pagination type.
* Reference \Rebing\GraphQL\Support\PaginationType::class
*/
'pagination_type' => \Rebing\GraphQL\Support\PaginationType::class,
/*
* Config for GraphiQL (see (https://github.com/graphql/graphiql).
*/
'graphiql' => [
'prefix' => '/graphiql',
'controller' => \Rebing\GraphQL\GraphQLController::class.'#graphiql',
'middleware' => [],
'view' => 'graphql::graphiql',
'display' => env('ENABLE_GRAPHIQL', true),
],
/*
* Overrides the default field resolver
* See http://webonyx.github.io/graphql-php/data-fetching/#default-field-resolver
*
* Example:
*
* ```php
* 'defaultFieldResolver' => function ($root, $args, $context, $info) {
* },
* ```
* or
* ```php
* 'defaultFieldResolver' => [SomeKlass::class, 'someMethod'],
* ```
*/
'defaultFieldResolver' => null,
/*
* Any headers that will be added to the response returned by the default controller
*/
'headers' => [],
/*
* Any JSON encoding options when returning a response from the default controller
* See http://php.net/manual/function.json-encode.php for the full list of options
*/
'json_encoding_options' => 0,
];
You missed a few imports and return types, check the updated code:
app/GraphQL/Types/WineType.php
<?php
namespace App\GraphQL\Types;
use App\Wine;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;
class WineType extends GraphQLType
{
protected $attributes = [
'name' => 'Wine',
'description' => 'Details about a wine',
'model' => Wine::class
];
public function fields(): array
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'Id of the wine',
],
'name' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The name of the wine',
],
'description' => [
'type' => Type::nonNull(Type::string()),
'description' => 'Short description of the wine',
],
'color' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The color of the wine',
],
'grape_variety' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The grape variety of the wine',
],
'country' => [
'type' => Type::nonNull(Type::string()),
'description' => 'The country of origin of the wine',
]
];
}
}
app/GraphQL/Queries/WineQuery.php
<?php
namespace App\GraphQL\Queries;
use App\Wine;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
class WineQuery extends Query
{
protected $attributes = [
'name' => 'wine',
];
public function type(): Type
{
return GraphQL::type('Wine');
}
public function args():array
{
return [
'id' => [
'name' => 'id',
'type' => Type::int(),
'rules' => ['required']
],
];
}
public function resolve($root, $args)
{
return Wine::findOrFail($args['id']);
}
}
app/GraphQL/Queries/WinesQuery.php
<?php
namespace App\GraphQL\Queries;
use App\Wine;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
class WinesQuery extends Query
{
protected $attributes = [
'name' => 'wines',
];
public function type(): Type
{
return Type::listOf(GraphQL::type('Wine'));
}
public function resolve($root, $args)
{
return Wine::all();
}
}

Yii2 Kartik-v fileiput: Get uploaded images when wrapped in the array of $_FILES instead of $_POST

I am developing my app using the yii2-formwizard widget. I have gotten it working fine in all the other parts and even submits a single image perfectly. The problem comes when I try to submit multiple images. I get the $_POST array with form model values and the image model as an empty array inside the $_POST, but then all the image model values are wrapped in an array of $_FILES. How do I go about solving this as I have never dealt with such before? Here are the relevant codes:
_form view
<?php
use yii\helpers\Html;
use kartik\widgets\ActiveForm;
use kartik\builder\Form;
use kartik\datecontrol\DateControl;
use kartik\widgets\Select2;
use buttflattery\formwizard\FormWizard;
use yii\helpers\ArrayHelper;
use kartik\file\FileInput;
/**
* #var yii\web\View $this
* #var common\models\Listing $model
* #var yii\widgets\ActiveForm $form
*/
?>
<?php
echo FormWizard::widget([
'theme' => FormWizard::THEME_MATERIAL,
'labelFinish' => 'Submit',
// 'formOptions'=>[
// 'options'=>['enctype'=>'multipart/form-data'],
// ],
'steps' => [
[
'model'=>$listingModel,
'title'=>'Basic',
'description'=>'Give us the details of the list',
'formInfoText'=>'Fill all required fields',
'fieldConfig' => [
'created_by' => false, //hide a specific field
'updated_at' => false, //hide a specific field
'created_at' => false, //hide a specific field
'expires_on' => false, //hide a specific field
'status' => false, //hide a specific field
'listing_type_id' => false, //hide a specific field
'latitude' => false, //hide a specific field
'longitude' => false, //hide a specific field
'listing_title' => ['type' => Form::INPUT_TEXT, 'options' => ['placeholder' => 'Enter Listing Title...', 'maxlength' => 50]],
'country' => [
'widget' => Select2::class, //widget class name
'options' => [
'data' => ArrayHelper::map(common\models\Country::find()->all(), 'country_id', 'country_name'),
'options' => [
'prompt'=>'Select Country',
'onchange'=>'
$.post( "'.Yii::$app->urlManager->createUrl('listing/select-state?id=').'"+$(this).val(), function( data ) {
$( "select#state_id" ).html( data );
});
'
]
]
],
'states' => [
'widget' => Select2::class, //widget class name
'options' => [
'data' => ArrayHelper::map(common\models\States::find()->all(), 'state_id', 'state_name'),
'options' => [
'id'=>'state_id',
'prompt'=>'Select Select the Country First',
'onchange'=>'
$.post( "'.Yii::$app->urlManager->createUrl('listing/select-area?id=').'"+$(this).val(), function( data ) {
$( "select#area_code" ).html( data );
});
'
]
]
],
'area_id' => [
'widget' => Select2::class, //widget class name
'options' => [
'data' => ArrayHelper::map(common\models\Areas::find()->all(), 'area_id', 'area_name'),
'options' => [
'prompt'=>'Please Select the State/Region First',
'id' => 'area_code'
],
]
],
'physical_address' => ['type' => Form::INPUT_TEXT, 'options' => ['placeholder' => 'Enter The Actual Physical Address...', 'maxlength' => 50]],
'neighborhood' => ['type' => Form::INPUT_TEXT, 'options' => ['placeholder' => 'Enter The Nearby Landmark or Neighbourhood...', 'maxlength' => 50]],
'address' => [
'widget' => \kalyabin\maplocation\SelectMapLocationWidget::className(),
'options' => [
'attributeLatitude' => 'latitude',
'attributeLongitude' => 'longitude',
'googleMapApiKey' => 'AIzaSyDU30XgKi1ik7wpWteHUENKVH_d09sTqRg',
'draggable' => true,
],
]
]
],
[
'model'=>$model,
'title'=>'Prices and More',
'description'=>'Give us the details of the list',
'formInfoText'=>'Fill all required fields',
'fieldConfig' => [
// 'only' => ['property_category', 'sub_category_id', 'available_from', 'desc', 'price', 'currency_id', 'price_conditions', 'deposit', 'agent_commission', 'other_payments'],
'only' => ['property_category', 'sub_category_id', 'price', 'currency_id'],
'property_category' => [
'widget' => Select2::class, //widget class name
'options' => [
'data' => ArrayHelper::map(common\models\PropertyCategory::find()->all(), 'category_id', 'category_name'),
'options' => [
'id'=>'state_id',
'prompt'=>'Select Property Category',
'onchange'=>'
$.post( "'.Yii::$app->urlManager->createUrl('listing/select-property-category?id=').'"+$(this).val(), function( data ) {
$( "select#sub_category_value" ).html( data );
});
'
]
]
],
'sub_category_id' => [
'widget' => Select2::class, //widget class name
'options' => [
'data' => ArrayHelper::map(common\models\PropertySubCategory::find()->all(), 'sub_category_id', 'name'),
'options' => [
'prompt'=>'Please Select the Property Category First',
'id' => 'sub_category_value'
],
]
],
'currency_id' => [
'widget' => Select2::class, //widget class name
'options' => [
'data' => ArrayHelper::map(common\models\Currency::find()->all(), 'currency_id', 'title'),
]
],
'price' => ['type' => Form::INPUT_TEXT, 'options' => ['placeholder' => 'Enter Price...']],
]
],
[
'model'=>$model,
'title'=>'Features',
'description'=>'Give us the details of the list',
'formInfoText'=>'Fill all required fields',
'fieldConfig' => [
// 'only' => ['beds', 'baths', 'rooms', 'living_area', 'living_size', 'floor', 'total_floors', 'build_year', 'car_spaces', 'fully_furnished', 'property_features'],
'only' => ['property_features'],
'property_features' => [
'widget' => Select2::class, //widget class name
'options' => [
'data' => ArrayHelper::map(common\models\PropertyFeatures::find()->all(), 'feature_id', 'feature_name', 'featuresType.type_name'),
'options' => ['multiple' => true, 'placeholder' => 'Select Property Features ...']
]
],
]
],
[
'model'=>$imageModel,
'title'=>'Images',
'description'=>'Give us the details of the list',
'formInfoText'=>'Fill all required fields',
'fieldConfig' => [
'only' => ['image'],
'image' => [
'multifield'=>true,
'widget' => FileInput::classname(),
'options' =>[
'options' => [
'multiple' => true,
'accept' => 'image/*',
'pluginOptions' => [
'showCaption' => false,
'showRemove' => false,
'showUpload' => false,
'browseClass' => 'btn btn-primary btn-block',
'browseIcon' => '<i class="glyphicon glyphicon-camera"></i> ',
'browseLabel' => 'Attach Listing Images',
'allowedFileExtensions' => ['jpg','gif','png'],
'overwriteInitial' => false
],
],
],
]
]
],
]
]);
?>
Controller Action
/**
* Creates a new Property model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionProperty()
{
$listingModel = new Listing;
$imageModel = new ListingImages;
$model = new Property;
if ($listingModel->load(Yii::$app->request->post()) && $imageModel->load(Yii::$app->request->post()) && $model->load(Yii::$app->request->post())) {
$transaction = Yii::$app->db->beginTransaction();
try {
$listingModel->listing_type_id = $listingModel->listingType('Property');
$listingModel->created_by = Yii::$app->user->id;
if ($flag = $listingModel->save(false)) {
$model->listing_id = $listingModel->listing_id;
$model->physical_address = $listingModel->physical_address;
$model->neighborhood = $listingModel->neighborhood;
$model->area_id = $listingModel->area_code;
$model->address = $listingModel->physical_address;
$model->latitude = $listingModel->latitude;
$model->longitude = $listingModel->longitude;
$model->created_by = Yii::$app->user->id;
$flag = $model->save(false);
foreach ($_FILES['ListingImages']['name']['image'] as $key => $image) {
$image = $imageModel->uploadImage();
$imageModel->created_by = Yii::$app->user->id;
$imageModel->listing_id = $listingModel->listing_id;
$imageModel->active = 'Y';
if ($flag = $imageModel->save()) {
if ($image !== false) {
$path = $imageModel->getImageFile();
$image->saveAs($path);
}
}
}
}
if ($flag) {
$transaction->commit();
return $this->redirect(['view', 'id' => $listingModel->listing_id]);
} else {
$transaction->rollBack();
}
} catch (Exception $e) {
$transaction->rollBack();
}
} else {
return $this->render('create', [
'listingModel' => $listingModel, 'model' => $model, 'form' => '_property', 'imageModel' => $imageModel,
]);
}
}
ListingImages model
<?php
namespace common\models;
use Yii;
use yii\web\UploadedFile;
use yii\helpers\FileHelper;
/**
* This is the model class for table "listing_images".
*
* #property int $image_id
* #property int $listing_id
* #property string $image_url_link generated filename on server
* #property string $updated_at
* #property string $created_at
* #property int $created_by
* #property string $active
* #property string $filename source filename from client
*
* #property Listing $listing
*/
class ListingImages extends \yii\db\ActiveRecord
{
const PERMISSIONS_PRIVATE = 10;
const PERMISSIONS_PUBLIC = 20;
public $filename;
public $image;
/**
* {#inheritdoc}
*/
public static function tableName()
{
return 'listing_images';
}
/**
* {#inheritdoc}
*/
public function rules()
{
return [
[['listing_id', 'image_url_link', 'created_by', 'active'], 'required'],
[['listing_id', 'created_by'], 'integer'],
[['updated_at', 'created_at', 'filename'], 'safe'],
[['active'], 'string'],
[['image_url_link'], 'string', 'max' => 80],
[['listing_id'], 'exist', 'skipOnError' => true, 'targetClass' => Listing::className(), 'targetAttribute' => ['listing_id' => 'listing_id']],
// [['image'], 'file', 'extensions'=>'jpg, gif, png'],
// [['image'], 'file', 'maxSize'=>'2048000'],
// [['image'], 'file','maxFiles' => 30],
[['image'], 'file', 'extensions' => ['png', 'jpg', 'gif'], 'maxSize' => 2048000, 'maxFiles' => 30],
];
}
/**
* {#inheritdoc}
*/
public function attributeLabels()
{
return [
'image_id' => Yii::t('app', 'Image ID'),
'listing_id' => Yii::t('app', 'Listing ID'),
'image_url_link' => Yii::t('app', 'Listing Image'),
'updated_at' => Yii::t('app', 'Updated At'),
'created_at' => Yii::t('app', 'Created At'),
'created_by' => Yii::t('app', 'Created By'),
'active' => Yii::t('app', 'Active'),
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getListing()
{
return $this->hasOne(Listing::className(), ['listing_id' => 'listing_id']);
}
/**
* fetch stored image file name with complete path
* #return string
*/
public function getImageFile()
{
$directory = Yii::$app->params['uploadPath'];
if (!is_dir($directory)) {
FileHelper::createDirectory($directory);
}
return isset($this->image_url_link) ? $directory . '/' . $this->image_url_link : null;
}
/**
* fetch stored image url
* #return string
*/
public function getImageUrl()
{
$directory = Yii::$app->params['uploadUrl'];
// return a default image placeholder if your source image_url_link is not found
$image_url_link = isset($this->image_url_link) ? $this->image_url_link : 'default_user.jpg';
return $directory . $image_url_link;
}
/**
* Process upload of image
*
* #return mixed the uploaded image instance
*/
public function uploadImage() {
// get the uploaded file instance. for multiple file uploads
// the following data will return an array (you may need to use
// getInstances method)
$image = UploadedFile::getInstance($this, 'image');
// if no image was uploaded abort the upload
if (empty($image)) {
return false;
}
// store the source file name
$tmp = explode(".", $image->name);
$ext = end($tmp);
// generate a unique file name
$this->image_url_link = Yii::$app->security->generateRandomString().".{$ext}";
// the uploaded image instance
return $image;
}
/**
* Process deletion of image
*
* #return boolean the status of deletion
*/
public function deleteImage() {
$file = $this->getImageFile();
// check if file exists on server
if (empty($file) || !file_exists($file)) {
return false;
}
// check if uploaded file can be deleted on server
if (!unlink($file)) {
return false;
}
// if deletion successful, reset your file attributes
$this->image_url_link = null;
$this->filename = null;
return true;
}
}
Note that the other part of the code is working fine. I have only twisted some few parts to get a favourable results. Here is the submitted data via the form. The part that is really confusing me is that wrapped in the $_FILES array at the end of this code that has all the uploaded files information.
$_POST = [
'_csrf-backend' => '_ioSvkoWdYDTEG_L4AHgnyQSEe7pZOqEWwQfPANPbM2uGkbIKHo8y5ZAGriqT4XsQCBkl5sOnvweT31wUx01kg==',
'Listing' => [
'listing_title' => 'tyguhijokpl[',
'country' => '2',
'states' => '4',
'area_id' => '1537',
'physical_address' => 'yghbunjikmol,',
'neighborhood' => 'gvybhunjimko,l',
'address' => 'Dandora phase 4, Nairobi, Kenya',
'latitude' => '-1.2423923',
'longitude' => '36.90438449999999',
],
'Property' => [
'property_category' => '1',
'sub_category_id' => '2',
'currency_id' => '1',
'price' => '897465123',
'property_features' => [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
],
],
'ListingImages' => [
'image' => [
'',
],
],
];
$_FILES = [
'ListingImages' => [
'name' => [
'image' => [
'coins-1015125_1920.jpg',
'computer-768608_1920.jpg',
'content-is-king-1132259_1920.jpg',
'content-marketing.jpg',
'contentpyramid.png',
'cup-of-coffee-1280537_1920.jpg',
'ecommerce-3546296_1920.jpg',
'email-3249062_1280.png',
],
],
'type' => [
'image' => [
'image/jpeg',
'image/jpeg',
'image/jpeg',
'image/jpeg',
'image/png',
'image/jpeg',
'image/jpeg',
'image/png',
],
],
'tmp_name' => [
'image' => [
'C:\\xampp\\tmp\\php8AF1.tmp',
'C:\\xampp\\tmp\\php8B02.tmp',
'C:\\xampp\\tmp\\php8B03.tmp',
'C:\\xampp\\tmp\\php8B23.tmp',
'C:\\xampp\\tmp\\php8B34.tmp',
'C:\\xampp\\tmp\\php8B35.tmp',
'C:\\xampp\\tmp\\php8B46.tmp',
'C:\\xampp\\tmp\\php8B47.tmp',
],
],
'error' => [
'image' => [
0,
0,
0,
0,
0,
0,
0,
0,
],
],
'size' => [
'image' => [
364796,
275881,
310313,
301511,
119458,
508911,
219479,
242737,
],
],
],
];
i created this extension a few months back. The files that you submitted will be in the $_FILES array and not the $_POST and you need to call the UploadedFile::getInstances('image') to get all the images you selected to upload and then iterate on them to upload, you can access all the properties listed here for every image.
And you are not creating the new object every time you are inserting the image inside the foreach ($_FILES['ListingImages']['name']['image'] as $key => $image) { which will show only the last image inserted , your $imageModel is initialized in the start of the action whereas you should have it inside the foreach too
$imageInstances=UploadedFile::getInstances('image');
foreach ($imageInstances as $instance) {
$imageModel=new ListingImages();
$image = $imageModel->uploadImage($instance);
$imageModel->created_by = Yii::$app->user->id;
$imageModel->listing_id = $listingModel->listing_id;
$imageModel->active = 'Y';
if ($flag = $imageModel->save()) {
if ($image !== false) {
$path = $imageModel->getImageFile();
$image->saveAs($path);
}
}
}
and inside you uploadImage() use this instance to access the name , type, size and extension of the image to assign to the specific fields
/**
* Process upload of image
*
* #return mixed the uploaded image instance
*/
public function uploadImage($image) {
// get the uploaded file instance. for multiple file uploads
// the following data will return an array (you may need to use
// getInstances method)
// store the source file name
$tmp = explode(".", $image->name);
$ext = end($tmp);
// generate a unique file name
$this->image_url_link = Yii::$app->security->generateRandomString().".{$ext}";
// the uploaded image instance
return $image;
}

zf2/zf3 how to validate dependent inputs in collection's fieldset?

I have a form. The form has a Collection whose target element is a fieldset with a checkbox and a couple of text fields. The fieldset attached as the target element to Collection looks like this (simplified to avoid too much code):
class AFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct(HydratorInterface $hydrator)
{
parent::__construct();
$this->setHydrator($hydrator)
->setObject(new SomeObject());
$this->add([
'type' => Hidden::class,
'name' => 'id',
]);
$this->add([
'type' => Checkbox::class,
'name' => 'selectedInForm',
]);
$this->add([
'type' => Text::class,
'name' => 'textField1',
]);
$this->add([
'type' => Text::class,
'name' => 'textField2',
]);
}
public function getInputFilterSpecification()
{
return [
'selectedInForm' => [
'required' => false,
'continue_if_empty' => true,
'validators' => [
['name' => Callback::class // + options for the validator],
],
],
'id' => [
'requred' => false,
'continue_if_empty' => true,
],
'textField1' => [
'required' => false,
'continue_if_empty' => true,
'validators' => [
['name' => SomeValidator::class],
],
],
'textField2' => [
'required' => true,
'validators' => [
['name' => SomeValidator::class],
],
],
],
}
}
I'd like to validate textField1 and textField2 based on if selectedInForm checkbox is checked in the form.
How could I do this?
I though of using a Callback validator for selectedInForm checkbox like this:
'callback' => function($value) {
if ($value) {
$this->get('textField1')->isValid();
// or $this->get('textField1')->getValue() and do some validation with it
}
}
but the problem with it is that, for some reason, the posted value of textField1 value isn't attached to the input yet. Same is true for textField2.
Two option is available. One is where you started, with callback validators.
The other one is to write a custom validator, and to make it reusable I recommend this solution.
<?php
use Zend\Validator\NotEmpty;
class IfSelectedInFormThanNotEmpty extends NotEmpty
{
public function isValid($value, array $context = null): bool
{
if (! empty($context['selectedInForm']) && $context['selectedInForm']) {
return parent::isValid($value);
}
return true;
}
}
And then you can use it as every other validator:
'textField2' => [
'required' => true,
'validators' => [
['name' => IfSelectedInFormThanNotEmpty::class],
],
],
This may not be your exact case, but I hope it helped to get the idea.
You may define options to make it more reusable with a configurable conditional field in public function __construct($options = null).

Require at least one element in ZF2 FieldSet

The problem
I have a Form and a FieldSet. I would like to validate that the FieldSet is not empty. Also, I want to validate each field in the FieldSet.
So far, whatever I have tried is validating one or the other, but not both. If elements is present in the Form's input filter specification, then it validates that elements is not empty, but does not validate the bar and baz fields of FieldSet. And, of course, the other way around. Any clue as to how to approach this issue would be much appreciated.
The Form
class FooForm extends Form implements InputFilterProviderInterface
{
public function init()
{
$this->add([
'name' => 'elements',
'type' => Collection::class,
'required' => true,
'options' => [
'target_element' => [
'type' => SomeElementFieldSet::class
]
]
]);
}
public function getInputFilterSpecification()
{
return [
[
'name' => 'elements',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
]
];
}
}
The FieldSet
class SomeElementFieldSet extends Fieldset implements InputFilterProviderInterface
{
public function init()
{
$this->add(['name' => 'bar']);
$this->add(['name' => 'baz']);
}
public function getInputFilterSpecification()
{
return [
[
'name' => 'bar',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
],
[
'name' => 'baz',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
]
];
}
}
Edit: Added full validation spec.
After getting some hints on Google and digging through the source code, I found a solution. Unfortunately the zend-inputfilter implementation is a little buggy and won't work nicely with getInputFilterSpecification(), but we can just construct our own InputFilter and return that directly:
The Form
class FooForm extends BaseForm
{
public function init()
{
$this->add([
'name' => 'elements',
'type' => Collection::class,
'options' => [
'target_element' => [
'type' => SomeElementFieldSet::class
]
]
]);
}
public function getInputFilter()
{
if (!$this->filter) {
$this->filter = new InputFilter();
/** #var Collection $elementsCollection */
$elementsCollection = $this->fieldsets['elements'];
/** #var SomeElementFieldSet $elementsFieldSet */
$elementsFieldSet = $elementsCollection->getTargetElement();
$collectionFilter = new CollectionInputFilter();
$collectionFilter->setIsRequired(true);
$collectionFilter->setInputFilter(
$elementsFieldSet->getInputFilterSpecification()
);
$this->filter->add($collectionFilter, 'elements');
}
return $this->filter;
}
}
This will validate that there is at least one element in the collection. And will validate all the elements one by one by the FieldSet's specification.
One problem persists, though. Whenever the collection is empty, the validation will return false, but will not return any messages. This is due to a bug in the zend-inputfilter component. Issues reported here and here. But that is another problem altogether.
Use setValidationGroup() method in the Form object by specifying an array of input fields you want to validate. Please refer to the Doc!
You may give a try this way. Though I have added some extra fields to the form for testing purpose only.
class FooForm extends Form implements InputFilterProviderInterface
{
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
$this->add(['name' => 'title']);
$this->add([
'name' => 'elements',
'type' => Collection::class,
'required' => true,
'options' => [
'target_element' => [
'type' => SomeElementFieldSet::class,
],
],
]);
$this->add([
'type' => 'submit',
'name' => 'submit',
'attributes' => [
'value' => 'Post'
],
]);
// I pointed this. Here you can specify fields to be validated
$this->setValidationGroup([
'title',
'elements' => [
'bar',
],
]);
}
public function getInputFilterSpecification()
{
return [
[
'name' => 'title',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
],
];
}
}
And your fieldset class should be
class SomeElementFieldSet extends Fieldset implements InputFilterProviderInterface
{
public function init()
{
$this->add(['name' => 'bar']);
$this->add(['name' => 'baz']);
}
public function getInputFilterSpecification()
{
return [
[
'name' => 'bar',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
],
[
'name' => 'baz',
'required' => true,
'validators' => [
['name' => 'NotEmpty']
]
]
];
}
}
Hope this would help!

Categories