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;
}
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!
I have a login Form LoginForm.php with its Filter LoginFilter.php, that has a View /login/index.phtml, a Controller LoginController.php, two Factory LoginControllerFactory.php & LoginFormFactory.php and it is called in the config.module.php and works perfect. The Form is correctly displayed.
I have a ViewController.php that has a method idAction that shows a post by its id passed by parameter from the homepage in a View called /view/id.phtml. I want to display this Form I created within this View and I don't know how. First, I created the Form exactly as I created the login Form, but I realized that I already configured my id child-route, inside of view route with a Factory in module.config.php.
Then, I tried to set the form in the idAction method, exactly as I did in indexAction in LoginController.php Controller, but I'm receiving the following error: An exception was raised while creating "Rxe\Factory\ViewController"; no instance returned.
I will now show you what I did to try to display this new Form.
First, the Form itself:
class CommentForm extends Form
{
public function buildForm()
{
$this->setAttribute('method', 'POST');
$this->setAttribute('id', 'add-comment-form');
$this->add(array(
'name' => 'comment',
'type' => 'textarea',
'options' => array(
'label' => 'Category'
),
'attributes' => array(
'class' => 'form-control'
)
));
$this->add(array(
'name' => 'submit',
'type' => 'submit',
'attributes' => array(
'class' => 'btn btn-success',
'value' => 'Comment'
)
));
}
}
Form's CommentFormFactory.php calling its Filter and building the Form:
class CommentFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$form = new CommentForm();
$form->setInputFilter($serviceLocator->get('Rxe\Factory\CommentFilter'));
$form->buildForm();
return $form;
}
}
The ViewControllerFactory.php calling the CommentFormFactory.php, just like in LoginControllerFactory.php:
class ViewControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$serviceManager = $serviceLocator->getServiceLocator();
$viewController = new ViewController();
$viewController->setPostsTable($serviceManager->get('Rxe\Factory\PostsTable'));
$viewController->setCommentsTable($serviceManager->get('Rxe\Factory\CommentsTable'));
$viewController->setCommentForm($serviceManager->get('Rxe\Factory\CommentForm'));
return $viewController;
}
}
The ViewController.php, calling the form within its idAction's ViewModel:
class ViewController extends AbstractActionController
{
use PostsTableTrait;
use CommentsTableTrait;
private $commentForm;
function setCommentForm($commentForm)
{
$this->commentForm = $commentForm;
}
public function indexAction()
{
$category = $this->params()->fromRoute('category');
return new ViewModel(array(
'posts' => $this->postsTable->getPostsByCategory($category),
'categories' => $category
));
}
public function idAction()
{
$id = $this->params()->fromRoute('id');
$viewModel = new ViewModel(array(
'commentForm' => $this->commentForm,
'commentParams' => $this->params()->fromPost(),
'messages' => $this->flashMessenger()->getMessages(),
'posts' => $this->postsTable->getPostById($id),
'posts' => $this->commentsTable->getNumberOfCommentsByPost($id),
'comments' => $this->commentsTable->getCommentsByPost($id)
));
$viewModel->setTemplate('rxe/view/id.phtml');
if ($this->getRequest()->isPost()) {
$this->commentForm->setData($this->params()->fromPost());
if ($this->commentForm->isValid()) {
$this->flashMessenger()->addMessage('Thank you for your comment. :)');
} else {
$this->flashMessenger()->addMessage('Your comment wasn\'t sent.');
}
}
return $viewModel;
}
}
And finally my module.config.php
'controllers' => array(
'invokables' => array(
'Rxe\Controller\Index' => 'Rxe\Controller\IndexController',
'Rxe\Controller\View' => 'Rxe\Controller\ViewController',
'Rxe\Controller\Login' => 'Rxe\Controller\LoginController'
),
'factories' => array(
'Rxe\Factory\LoginController' => 'Rxe\Factory\LoginControllerFactory',
'Rxe\Factory\ViewController' => 'Rxe\Factory\ViewControllerFactory',
'Rxe\Factory\IndexController' => 'Rxe\Factory\IndexControllerFactory'
)
),
'service_manager' => array(
'factories' => array(
'Rxe\Factory\LoginForm' => 'Rxe\Factory\LoginFormFactory',
'Rxe\Factory\LoginFilter' => 'Rxe\Factory\LoginFilterFactory',
'Rxe\Factory\CommentForm' => 'Rxe\Factory\CommentFormFactory',
'Rxe\Factory\CommentFilter' => 'Rxe\Factory\CommentFilterFactory',
'Rxe\Factory\PostsTable' => 'Rxe\Factory\PostsTableFactory',
'Rxe\Factory\CategoriesTable' => 'Rxe\Factory\CategoriesTableFactory',
'Rxe\Factory\CommentsTable' => 'Rxe\Factory\CommentsTableFactory',
'Zend\Db\Adapter\AdapterService' => 'Zend\Db\Adapter\AdapterServiceFactory'
)
),
Please, let me know if you need me to show you more codes. Thank you in advance.
EDIT #1
If I remove the line that calls the Form in the ViewControllerFactory.php, I get the following error: Fatal error: Call to a member function prepare() on a non-object in /home/vol12_3/byethost4.com/b4_16354889/htdocs/module/Rxe/view/rxe/view/id.phtml on line 31
The id.phtml is:
<!-- Comment form -->
<div id="comment-form-area" class="col-xs-3">
<?php $this->commentForm->prepare() ?>
<?php echo $this->form()->openTag($this->commentForm); ?>
<div class="form-group comment-area">
<?php echo $this->formRow($this->commentForm->get('comment_content')); ?>
</div>
<div class="form-group">
<?php echo $this->formRow($this->commentForm->get('submit')); ?>
</div>
<?php echo $this->form()->closeTag(); ?>
</div>
<!-- /Comment form -->
Try removing these lines
'invokables' => array(
'Rxe\Controller\Index' => 'Rxe\Controller\IndexController',
'Rxe\Controller\View' => 'Rxe\Controller\ViewController',
'Rxe\Controller\Login' => 'Rxe\Controller\LoginController'
),
If it doesn't work, have a look at this tutorial how to create proper controller factories and pass dependencies. https://samsonasik.wordpress.com/2015/03/31/zend-framework-2-using-__invokepluginmanager-manager-in-services-factory/
An example how I build my forms:
namespace Admin\Form;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;
class ContentForm extends Form implements InputFilterProviderInterface
{
public function __construct()
{
parent::__construct("content");
}
public function init()
{
$this->setAttribute('method', 'post');
$this->add([
'type' => 'Zend\Form\Element\Text',
'name' => 'title',
'attributes' => [
'required' => true,
'size' => 40,
'id' => "seo-caption",
'placeholder' => 'Title',
],
'options' => [
'label' => 'Title',
],
]);
$this->add([
'type' => 'Zend\Form\Element\Text',
'name' => 'text',
'attributes' => [
'class' => 'ckeditor',
'rows' => 5,
'cols' => 80,
],
'options' => [
'label' => 'Text',
],
]);
}
public function getInputFilterSpecification()
{
return [
[
"name"=>"title",
"required" => true,
'filters' => [
['name' => 'StripTags'],
['name' => 'StringTrim'],
],
'validators' => [
['name' => 'NotEmpty'],
[
'name' => 'StringLength',
'options' => [
'encoding' => 'UTF-8',
'min' => 1,
'max' => 200,
],
],
],
],
[
"name"=>"text",
"required" => true,
'filters' => [
['name' => 'StripTags'],
['name' => 'StringTrim'],
],
'validators' => [
['name' => 'NotEmpty'],
[
'name' => 'StringLength',
'options' => [
'encoding' => 'UTF-8',
'min' => 1,
],
],
],
],
];
}
}
Than I create a Factory
namespace Admin\Factory\Controller;
use Admin\Controller\ContentController;
use Zend\Mvc\Controller\ControllerManager;
class ContentFormFactory
{
/**
* #{inheritDoc}
*/
public function __invoke(ControllerManager $controllerManager)
{
return new ContentController(
$controllerManager->getServiceLocator()->get('FormElementManager')->get('Admin\Form\ContentForm')
);
}
}
Inside module.config.php I have this code
'controllers' => [
'factories' => [
'Admin\Controller\Content' => "Admin\Factory\Controller\ContentFormFactory",
],
'invokables' => [
...
],
],
Please, show us some more code.
I have created Auth and Acl modules using zend-framework2, And these two are working good. Now I want to use these modules multiple times with different configuration.
Basically, I have two sections in my project -
User Section
Admin Section
And both have different session variables and different database tables.
I have Auth configuration file as following (module.config.php) -
return array(
'auth' => array(
'db' => array(
'table' => 'user',
'identity' => 'email',
'credential' => 'password',
'credential_treatment' => array(
'class' => '\My\Common',
'method' => 'encrypt'
),
'status' => array(
'is_enabled = true',
),
),
'view' => array(
'label' => array(
'identity' => 'Email',
),
),
'route' => array(
'login' => 'home',
'logout' => 'home',
),
'whitelist' => array(
'home',
)
),
....
....
);
I want to use the same module for both Admin section and User section, But with different configuration settings, Like different database tables and session variables.
Is it possible to do so or I have to create different modules for different section?
Let me know if you need more details.
Here's an simple approach for your use case. I didn't test it, so expect some typos :)
I hope you get the basic idea. If not, let me know.
config/autoload/auth.global.php
return [
'service_manager' => [
'factories' => [
'YourAuthNamespace\Config' => 'YourAuthNamespace\Service\ConfigServiceFactory',
'YourAuthNamespace\AbstractAuthFactoryFactory' => 'YourAuthNamespace\Service\AbstractAuthFactoryFactory',
],
'abstract_factories' => [
'YourAuthNamespace\AbstractAuthFactoryFactory'
]
]
];
src/YourAuth/Service/AbstractAuthFactoryFactory.php
namespace YourAuth\Service;
class AbstractAuthFactoryFactory implements \Zend\ServiceManager\AbstractFactoryInterface
{
public function canCreateServiceWithName(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
$key = $this->getConfigKeyFromServiceName($name);
$config = $this->getConfig($serviceLocator);
return array_key_exists($key, $config);
}
private function getConfig(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator)
{
return $serviceLocator->get('YourAuthNamespace\Config');
}
public function createServiceWithName(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
$key = $this->getConfigKeyFromServiceName($name);
$config = $this->getConfig($serviceLocator);
return new YourAuthClass($config[$key]);
}
private function getConfigKeyFromServiceName($name)
{
return preg_replace('#^YourAuthNamespace\Auth\#i', '', $name);
}
}
src/YourAuth/Service/ConfigServiceFactory.php
namespace YourAuth\Service;
use Zend\ServiceManager\FactoryInterface;
class ConfigServiceFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$config = $serviceLocator->get('Config');
return $config['yourauth'];
}
}
module/MyModuleA/config/module.config.php
return [
'yourauth' => [
'ModuleA' => [ // 'ModuleA' is also the key used by the abstract factory
'db' => [
'table' => 'module_a_auth_table',
// ...
],
'session' => [
'namespace' => 'module_a_session_namespace'
// ...
],
]
],
'service_manager' => array(
'factories' => array(
'MyModuleA\Auth' => function ($sm) {
return $sm->get('YourAuthNamespace\Auth\ModuleA');
}
),
),
];
module/MyModuleA/src/AuthClient.php
namespace MyModuleA;
class AuthClient implements \Zend\ServiceManager\ServiceLocatorAwareInterface
{
public function doSomethingWithAuth()
{
if ($this->getServiceLocator()->get('MyModuleA\Auth')->isAuthorized()) {
// blah...
}
}
}