I have a laravel 5.7 application where I want to add prooph for event sourcing. I have follow the instructions but I retrieve this error:
Prooph\ServiceBus\Exception\RuntimeException: CommandBus was not able to identify a CommandHandler for command App\src\Prooph\Model\Procedure\Command\ChangeProcedureState in /var/www/html/vendor/prooph/service-bus/src/CommandBus.php:58
This is my config/prooph.php file
return [
'event_store' => [
'adapter' => [
'type' => \Prooph\EventStore\Pdo\MySqlEventStore::class,
'options' => [
'connection_alias' => 'laravel.connections.pdo',
],
],
'plugins' => [
\Prooph\EventStoreBusBridge\EventPublisher::class,
\Prooph\EventStoreBusBridge\TransactionManager::class,
],
'procedure_collection' => [
'repository_class' => App\src\Prooph\Infrastructure\Repository\EventStoreProcedureCollection::class,
'aggregate_type' => App\src\Prooph\Model\Procedure\Procedure::class,
],
],
'service_bus' => [
'command_bus' => [
'router' => [
'type' => \Prooph\ServiceBus\Plugin\Router\CommandRouter::class,
'routes' => [
// list of commands with corresponding command handler
\App\src\Prooph\Model\Procedure\Command\ChangeProcedureState::class => App\src\Prooph\Model\Procedure\Handler\ChangeProcedureStateHandler::class,
],
],
],
'event_bus' => [
'plugins' => [
\Prooph\ServiceBus\Plugin\InvokeStrategy\OnEventStrategy::class,
],
'router' => [
'routes' => [
// list of events with a list of projectors
],
],
],
/*'event_bus' => [
'router' => [
'routes' => [
// list of events with a list of projectors
App\src\Prooph\ProophessorDo\Model\User\Event\UserWasRegistered::class => [
\App\src\Prooph\ProophessorDo\Projection\User\UserProjector::class
],
],
],
],*/
],
];
This is my service that dispatch the command
class ProcedureRetrieveStateChanged
{
/** #var \FluentProceduresRepository $procedureRepository */
private $procedureRepository;
/**
* #var CommandBus
*/
private $commandBus;
public function __construct(\FluentProceduresRepository $procedureRepository, CommandBus $commandBus)
{
$this->procedureRepository = $procedureRepository;
$this->commandBus = $commandBus;
}
public function execute($procedureId, array $groups)
{
$procedure = $this->procedureRepository->find($procedureId);
if (null === $procedure) {
return false;
}
foreach ($groups as $group) {
$actualState = $procedure->getStatebyGroup($group['pivot']['group_id']);
if ($actualState !== $group['pivot']['state']) {
$command = new ChangeProcedureState(
[
'procedure_id' => $procedureId,
'group_id' => $group['pivot']['group_id'],
'old_state' => $actualState,
'new_state' => $group['pivot']['state'],
]
);
$this->commandBus->dispatch($command);
}
}
}
}
This is my command
final class ChangeProcedureState extends Command implements PayloadConstructable
{
use PayloadTrait;
public function procedureId(): ProcedureId
{
return ProcedureId::fromString($this->payload['procedure_id']);
}
public function state(): string
{
return $this->payload['state'];
}
protected function setPayload(array $payload): void
{
Assertion::keyExists($payload, 'procedure_id');
Assertion::keyExists($payload, 'group_id');
Assertion::keyExists($payload, 'old_state');
Assertion::keyExists($payload, 'new_state');
$this->payload = $payload;
}
}
And this is my handler
class ChangeProcedureStateHandler
{
/**
* #var ProcedureCollection
*/
private $procedureCollection;
public function __construct(ProcedureCollection $procedureCollection)
{
$this->procedureCollection = $procedureCollection;
}
public function __invoke(ChangeProcedureState $command): void
{
$procedure = $this->procedureCollection->get($command->procedureId());
$procedure->changeState($command->state());
$this->procedureCollection->save($procedure);
}
}
Can someone help me with this problem?
To use handlers as a string you need to use ServiceLocatorPlugin, but before that you need to register all handlers in laravel container, like $app->singletor...or $app->bind.
Cheers.
Related
I'm porting code from ZF2 to ZF3.
In ZF2 when I create a form via FormElementManager I can access the servicelocator on the init method and configure some stuff like this:
public function init()
{
$this->serviceLocator = $this->getFormFactory()->getFormElementManager()->getServiceLocator();
$this->translator = $this->serviceLocator->get('translator');
}
This is convenient in very large applications. In fact all my forms inherit from a BaseForm class.
In ZF3 this is bad pratic and serviceLocator are deprecated.
Which is the best way to get the same result ?
One way is to inject every form in the ControllerFactory or ServiceFactory with the stuff needed but this is very tedious.
Any help is appreciate.
First of, you should not have the ServiceManager and/or childs of it (like the FormElementManager) available in your Form objects.
Using the Factory pattern, you should create fully functional, stand-alone Form, Fieldset and InputFilter objects.
There will definitely be some tedious work, as you put it, but you need only do it once.
Let's say you want to create a Location. A Location consists of a name property and a OneToOne unidirectional Address reference. This creates the following needs:
LocationForm (-InputFilter)
LocationFieldset (-InputFilter)
AddressFieldset (-InputFilter)
Config for the above
Factory for each of the 6 classes above
In this answer I'll mash everything down to bare minimums and use classes and examples from my own repositories, so for full code you can go here and for examples here.
After the creation of the classes themselves, I'll show you the config you need for this use case and the Factories which tie all of it together.
AbstractFieldset
abstract class AbstractFieldset extends Fieldset
{
public function init()
{
$this->add(
[
'name' => 'id',
'type' => Hidden::class,
'required' => false,
]
);
}
}
AbstractInputFilter
abstract class AbstractFieldsetInputFilter extends AbstractInputFilter
{
public function init()
{
$this->add([
'name' => 'id',
'required' => false,
'filters' => [
['name' => ToInt::class],
],
'validators' => [
['name' => IsInt::class],
],
]);
}
}
AddressFieldset
class AddressFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
$this->add([
'name' => 'street',
'required' => true,
'type' => Text::class,
'options' => [
'label' => 'Address',
],
]);
}
}
AddressInputFilter
class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter
{
public function init()
{
parent::init();
$this->add([
'name' => 'street',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
So far, easy. Now, we need to create the LocationFieldset and LocationFieldsetInputFilter. These will make use of the Address(-Fieldset) classes.
LocationFieldset
class LocationFieldset extends AbstractFieldset
{
public function init()
{
parent::init();
$this->add([
'name' => 'name',
'required' => true,
'type' => Text::class,
'options' => [
'label' => 'Name',
],
]);
$this->add([
'type' => AddressFieldset::class,
'name' => 'address',
'required' => true,
'options' => [
'use_as_base_fieldset' => false,
'label' => 'Address',
],
]);
}
}
LocationFieldsetInputFilter
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
/**
* #var AddressFieldsetInputFilter
*/
protected $addressFieldsetInputFilter;
public function __construct(AddressFieldsetInputFilter $addressFieldsetInputFilter)
{
$this->addressFieldsetInputFilter = $addressFieldsetInputFilter;
}
public function init()
{
parent::init();
$this->add($this->addressFieldsetInputFilter, 'address');
$this->add(
[
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]
);
}
}
Ok, so that's not very exciting yet. Do note, the LocationFieldset uses the AddressFieldset as a type. Instead, in the InputFilter class a full fledged class object (an InputFilter instance) is expected.
So, the Form. I also use an AbstractForm (BaseForm in your case) to handle a few defaults. In my complete one (in linked repo), there's a bit more, but for here this'll suffice. This adds CSRF protection to the Form and adds a submit button if the form does not have one. This only gets done if the Form class does not have either one when you call the init, so you can override these settings.
AbstractForm
abstract class AbstractForm extends \Zend\Form\Form implements InputFilterAwareInterface
{
protected $csrfTimeout = 900; // 15 minutes
public function __construct($name = null, $options = [])
{
$csrfName = null;
if (isset($options['csrfCorrector'])) {
$csrfName = $options['csrfCorrector'];
unset($options['csrfCorrector']);
}
parent::__construct($name, $options);
if ($csrfName === null) {
$csrfName = 'csrf';
}
$this->addElementCsrf($csrfName);
}
public function init()
{
if (!$this->has('submit')) {
$this->addSubmitButton();
}
}
public function addSubmitButton($value = 'Save', array $classes = null)
{
$this->add([
'name' => 'submit',
'type' => Submit::class,
'attributes' => [
'value' => $value,
'class' => (!is_null($classes) ? join (' ', $classes) : 'btn btn-primary'),
],
]);
}
public function get($elementOrFieldset)
{
if ($elementOrFieldset === 'csrf') {
// Find CSRF element
foreach ($this->elements as $formElement) {
if ($formElement instanceof Csrf) {
return $formElement;
}
}
}
return parent::get($elementOrFieldset);
}
protected function addElementCsrf($csrfName = 'csrf')
{
$this->add([
'type' => Csrf::class,
'name' => 'csrf',
'options' => [
'csrf_options' => [
'timeout' => $this->csrfTimeout,
],
],
]);
}
}
LocationForm
class LocationForm extends AbstractForm
{
public function init()
{
$this->add([
'name' => 'location',
'type' => LocationFieldset::class,
'options' => [
'use_as_base_fieldset' => true,
],
]);
parent::init();
}
}
Now we have everything to make the Form. We still need the validation. Let's create these now:
AddressFieldsetInputFilter
class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter
{
public function init()
{
parent::init();
$this->add([
'name' => 'street',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]);
}
}
LocationFieldsetInputFilter
class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
protected $addressFieldsetInputFilter;
public function __construct(AddressFieldsetInputFilter $addressFieldsetInputFilter)
{
$this->addressFieldsetInputFilter = $addressFieldsetInputFilter;
}
public function init()
{
parent::init();
$this->add($this->addressFieldsetInputFilter, 'address');
$this->add(
[
'name' => 'name',
'required' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 3,
'max' => 255,
],
],
],
]
);
}
}
LocationFormInputFilter
class LocationFormInputFilter extends AbstractFormInputFilter
{
/** #var LocationFieldsetInputFilter */
protected $locationFieldsetInputFilter;
public function __construct(LocationFieldsetInputFilter $filter)
{
$this->locationFieldsetInputFilter = $filter
}
public function init()
{
$this->add($this->locationFieldsetInputFilter, 'location');
parent::init();
}
}
Right, that's all of the classes themselves. Do you see how they'll be nested together? This creates re-usable components, which is why I said you'll need to do this only once. Next time you need an Address or a Location, you just make sure to load the AddressFieldset and set the InputFilter in the Factory. The latter, setting the right InputFilter, is done via Setter Injection the Factories. Shown below.
AbstractFieldsetFactory
abstract class AbstractFieldsetFactory implements FactoryInterface
{
/**
* #var string
*/
protected $name;
/**
* #var string
*/
protected $fieldset;
/**
* #var string
*/
protected $fieldsetName;
/**
* #var string
*/
protected $fieldsetObject;
public function __construct($fieldset, $name, $fieldsetObject)
{
$this->fieldset = $fieldset;
$this->fieldsetName = $name;
$this->fieldsetObject = $fieldsetObject;
$this->hydrator = new Reflection(); // Replace this with your own preference, either Reflection of ZF or maybe the Doctrine EntityManager...
}
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$fieldset = $this->fieldset;
$fieldsetObject = $this->fieldsetObject;
/** #var AbstractFieldset $fieldset */
$fieldset = new $fieldset($this->hydrator, $this->name ?: $this->fieldsetName);
$fieldset->setHydrator(
new DoctrineObject($this->hydrator)
);
$fieldset->setObject(new $fieldsetObject());
return $fieldset;
}
}
AddressFieldsetFactory
class AddressFieldsetFactory extends AbstractFieldsetFactory
{
public function __construct()
{
parent::__construct(AddressFieldset::class, 'address', Address::class);
}
}
LocationFieldsetFactory
class LocationFieldsetFactory extends AbstractFieldsetFactory
{
public function __construct()
{
parent::__construct(LocationFieldset::class, 'location', Location::class);
}
}
AbstractFieldsetInputFilterFactory
abstract class AbstractFieldsetInputFilterFactory implements FactoryInterface
{
/**
* #var ContainerInterface
*/
protected $container;
/**
* #var HydratorInterface
*/
protected $hydrator;
/**
* #var InputFilterPluginManager
*/
protected $inputFilterManager;
/**
* Use this function to setup the basic requirements commonly reused.
*
* #param ContainerInterface $container
* #param string|null $className
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
*/
public function setupRequirements(ContainerInterface $container, $className = null)
{
$this->inputFilterManager = $container->get(InputFilterPluginManager::class);
}
}
AddressFieldsetInputFilterFactory
class AddressFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
parent::setupRequirements($container, Address::class);
return new AddressFieldsetInputFilter($this->hydrator);
}
}
LocationFieldsetInputFilterFactory
class LocationFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
parent::setupRequirements($container, Location::class);
/** #var AddressFieldsetInputFilter $addressFieldsetInputFilter */
$addressFieldsetInputFilter = $this->inputFilterManager->get(AddressFieldsetInputFilter::class);
return new LocationFieldsetInputFilter(
$addressFieldsetInputFilter,
$this->hydrator
);
}
}
That takes care of the FieldsetInputFilterFactory classes. Just the Form left.
In my case I use the same abstract factory class as for the Fieldset classes.
LocationFormInputFilterFactory
class LocationFormInputFilterFactory extends AbstractFieldsetInputFilterFactory
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
parent::setupRequirements($container);
/** #var LocationFieldsetInputFilter $locationFieldsetInputFilter */
$locationFieldsetInputFilter = $this->getInputFilterManager()->get(LocationFieldsetInputFilter::class);
return new LocationFormInputFilter(
$locationFieldsetInputFilter,
$this->hydrator
);
}
}
So, that's all of the classes done. It's a complete setup. You might encounter some bugs as I modified my own code to remove getters/setters, code comments/hinting, error, property and variable checking without testing. But it should work ;)
However, we're nearly done. We still need:
config
usage in a Controller
print/use Form in a View
The config is easy:
'form_elements' => [
'factories' => [
AddressFieldset::class => AddressFieldsetFactory::class,
LocationFieldset::class => LocationFieldsetFactory::class,
LocationForm::class => LocationFormFactory::class,
],
],
'input_filters' => [
'factories' => [
AddressFieldsetInputFilter::class => AddressFieldsetInputFilterFactory::class,
LocationFieldsetInputFilter::class => LocationFieldsetInputFilterFactory::class,
LocationFormInputFilter::class => LocationFormInputFilterFactory::class,
],
],
That's it. That little bit ties all of the above classes together.
To get the Form into a Controller, you would do something like this in the Factory:
class EditControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$hydrator = new Reflection(); // or $container->get('hydrator') or $container->get(EntityManager::class), or whatever you use
/** #var FormElementManagerV3Polyfill $formElementManager */
$formElementManager = $container->get('FormElementManager');
/** #var LocationForm $form */
$form = $formElementManager->get(LocationForm::class); // See :) Easy, and re-usable
return new EditController($hydrator, $form);
}
}
A typical "Edit" action would be like this (mind, this one uses Doctrine's EntityManager as the hydrator):
public function editAction()
{
$id = $this->params()->fromRoute('id', null);
/** #var Location $entity */
$entity = $this->getObjectManager()->getRepository(Location::class)->find($id);
/** #var LocationForm $form */
$form = $this->form;
$form->bind($entity);
/** #var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
/** #var Location $object */
$object = $form->getObject();
$this->getObjectManager()->persist($object);
try {
$this->getObjectManager()->flush();
} catch (Exception $e) {
// exception handling
}
return $this->redirect()->toRoute('route/name', ['id' => $object->getId()]);
}
}
return [
'form' => $form,
'validationMessages' => $form->getMessages() ?: '',
];
}
And the View Partial would look like this (based on the return in the above action):
form($form) ?>
So, that's it. Fully fledged, re-usable classes. Single setup. And in the end just a single line in the Factory for the Controller.
Please take note though:
Form, Fieldset and InputFilter use "address" input name. Very important to keep these the same throughout as Zend does some magic based on the names to match Fieldset with InputFilter.
If you have any more questions about how this works, please read through the documentation in the repo's I linked first, before asking below this question. There's more there that should help you out more, for example for Collection handling.
I've a problem to use the zf-hal hydrator in combination with an abstract factory. This is my module configuration:
public function getConfig()
{
return [
'hydrators' => [
'abstract_factories' => [
AbstractHydratorFactory::class,
]
],
'service_manager' => [
'factories' => [
ModuleOptions::class => ModuleOptionsFactory::class,
],
],
'zf-hal' => [
'renderer' => [
'default_hydrator' => 'reflection'
],
]
];
}
My abstract factory looks like this:
class AbstractHydratorFactory implements AbstractFactoryInterface
{
public function canCreate(ContainerInterface $container, $requestedName)
{
$moduleOptions = $container->get(ModuleOptions::class);
$configuration = $moduleOptions->getClass();
return isset($configuration[$requestedName]['property_name_translation']);
}
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$moduleOptions = $container->get(ModuleOptions::class);
$configuration = $moduleOptions->getClass();
$hydrator = $container->get($moduleOptions->getHydrator());
$hydrator->setNamingStrategy(
new ArrayMapNamingStrategy(
$configuration[$requestedName]['property_name_translation']
)
);
return $hydrator;
}
}
To test my module I created some unit tests. One of them is:
class HalEntityHydratorTest extends TestCase
{
protected $moduleLoader;
protected function setUp()
{
$this->moduleLoader = new ModuleLoader([
'modules' => [
'Zend\Hydrator',
'Zend\Router',
'ZF\Hal',
'MyHalHydratorModule',
'MyHalHydratorModuleTest\Integration\Hydrator\HalEntityHydratorTest',
],
'module_listener_options' => [],
]);
$this->moduleLoader->getApplication()->bootstrap();
}
public function testHalRendererWithHalEntities()
{
$halPlugin = $this->moduleLoader->getServiceManager()->get('ViewHelperManager')->get('Hal');
$rootTestEntity = new RootTestEntity();
$childTestEntity = new ChildTestEntity();
$rootTestEntity->setChildEntity(new Entity($childTestEntity));
$rootTestEntity->setUnkownChildEntity(new Entity(new UnkownChildTestEntity()));
$expectedArray = [
'_embedded' => [
'phpunit:test-entity' => [
'_links' => [],
],
'unkownChildEntity' => [
'unkownChildTestProperty' => 'phpunit',
'_links' => [],
],
],
'_links' => [],
];
$this->assertSame($expectedArray, $halPlugin->renderEntity(new Entity($rootTestEntity)));
}
}
These are my test entities:
class ChildTestEntity
{
}
class UnkownChildTestEntity
{
protected $unkownChildTestProperty = 'phpunit';
}
class RootTestEntity
{
protected $childEntity;
protected $unkownChildEntity;
public function setUnkownChildEntity($unkownChildEntity)
{
$this->unkownChildEntity = $unkownChildEntity;
}
public function setChildEntity($childEntity)
{
$this->childEntity = $childEntity;
}
}
And then it could be good to know what my test module configuration looks like:
public function getConfig()
{
return [
'zf-hal' => [
'metadata_map' => [
ChildTestEntity::class => [
'force_self_link' => false,
],
UnkownChildTestEntity::class => [
'force_self_link' => false,
],
],
],
'my-hal-hydrator-module' => [
'class' => [
RootTestEntity::class => [
'property_name_translation' => [
'childEntity' => 'phpunit:test-entity',
],
],
],
],
];
}
Ok, enough sourcecode. What happens now?
I run my test suite and the test above fails because of the arrays are different. That's why the first key of the result array is still 'childEntity' and not 'phpunit:test-entity' as expected.
So I think the property replacement has not take place but I don't know why.
I don't understand why this error occur.
get error on call a cms
http://localhost/yii-cms/web/cms
Calling unknown method: yii2mod\cms\controllers\CmsController::setInstance()
i am try to use of yii2-cms
cmsController
<?php
namespace yii2mod\cms\controllers;
use Yii;
use yii2mod\cms\models\CmsModel;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use yii2mod\cms\models\search\CmsModelSearch;
use yii2mod\editable\EditableAction;
use yii2mod\toggle\actions\ToggleAction;
/**
* Class CmsController
* #package yii2mod\cms\controllers
*/
class CmsController extends Controller
{
/**
* #var string view path
*/
public $viewPath = '#vendor/yii2mod/yii2-cms/views/cms/';
/**
* #inheritdoc
*/
public function behaviors()
{
return [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'index' => ['get'],
'create' => ['get', 'post'],
'update' => ['get', 'post'],
'delete' => ['post']
],
]
];
}
/**
* #inheritdoc
*/
public function actions()
{
return [
'edit-page' => [
'class' => EditableAction::className(),
'modelClass' => CmsModel::className(),
'forceCreate' => false
],
'toggle' => [
'class' => ToggleAction::className(),
'modelClass' => CmsModel::className(),
]
];
}
/**
* Lists all CmsModel models.
* #return mixed
*/
public function actionIndex()
{
$searchModel = new CmsModelSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render($this->viewPath . 'index', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel
]);
}
/**
* Creates a new CmsModel model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionCreate()
{
$model = new CmsModel();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
Yii::$app->session->setFlash('success', Yii::t('yii2mod.cms', 'Page has been created.'));
return $this->redirect(['index']);
}
return $this->render($this->viewPath . 'create', [
'model' => $model,
]);
}
/**
* Updates an existing CmsModel model.
* If update is successful, the browser will be redirected to the 'view' page.
*
* #param integer $id
*
* #return mixed
*/
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
Yii::$app->session->setFlash('success', Yii::t('yii2mod.cms', 'Page has been updated.'));
return $this->redirect(['index']);
}
return $this->render($this->viewPath . 'update', [
'model' => $model,
]);
}
/**
* Deletes an existing CmsModel model.
* If deletion is successful, the browser will be redirected to the 'index' page.
*
* #param integer $id
*
* #return mixed
*/
public function actionDelete($id)
{
$this->findModel($id)->delete();
Yii::$app->session->setFlash('success', Yii::t('yii2mod.cms', 'Page has been deleted.'));
return $this->redirect(['index']);
}
/**
* Finds the CmsModel model based on its primary key value.
* If the model is not found, a 404 HTTP exception will be thrown.
*
* #param integer $id
*
* #return CmsModel the loaded model
* #throws NotFoundHttpException if the model cannot be found
*/
protected function findModel($id)
{
if (($model = CmsModel::findOne($id)) !== null) {
return $model;
} else {
throw new NotFoundHttpException(Yii::t('yii2mod.cms', 'The requested page does not exist.'));
}
}
}
i have following yii2-cms and it's work great
set instance error occur due to they can not find out given class and that's possible due to miss configuration.
follow Configuration Link https://github.com/yii2mod/yii2-cms#configuration
1) To use this extension first you need to configure the comments extension, after that you have to configure the main config in your application:
'modules' => [
'admin' => [
'controllerMap' => [
'cms' => 'yii2mod\cms\controllers\CmsController'
// You can set your template files
// 'layout' => '#app/modules/backend/views/layouts/main',
// 'viewPath' => '#app/modules/backend/views/cms/',
],
],
],
You can then access to management section through the following URL:
http://localhost/path/to/index.php?r=admin/cms/index
2) Add new Rule class to the urlManager array in your application configuration by the following code:
'components' => [
'urlManager' => [
'rules' => [
['class' => 'yii2mod\cms\components\PageUrlRule'],
]
],
],
3) Add to SiteController (or configure via $route param in urlManager):
/**
* #return array
*/
public function actions()
{
return [
'page' => [
'class' => 'yii2mod\cms\actions\PageAction',
// You can set your template files
'view' => '#app/views/site/page'
],
];
}
And now you can create your own pages via the admin panel, and access them via the url of each page.
Yes error is solved by following proper Configuration STEP.
Error is occur due to miss configure second step
2) Add new Rule class to the urlManager array in your application configuration by the following code:
'components' => [
'urlManager' => [
'rules' => [
['class' => 'yii2mod\cms\components\PageUrlRule'],
]
],
],
Full Configuration you need to did :
1) To use this extension first you need to configure the comments extension, after that you have to configure the main config in your application:
'modules' => [
'admin' => [
'controllerMap' => [
'cms' => 'yii2mod\cms\controllers\CmsController'
// You can set your template files
// 'layout' => '#app/modules/backend/views/layouts/main',
// 'viewPath' => '#app/modules/backend/views/cms/',
],
],
],
You can then access to management section through the following URL:
http://localhost/path/to/index.php?r=admin/cms/index
2) Add new Rule class to the urlManager array in your application configuration by the following code:
'components' => [
'urlManager' => [
'rules' => [
['class' => 'yii2mod\cms\components\PageUrlRule'],
]
],
],
3) Add to SiteController (or configure via $route param in urlManager):
/**
* #return array
*/
public function actions()
{
return [
'page' => [
'class' => 'yii2mod\cms\actions\PageAction',
// You can set your template files
'view' => '#app/views/site/page'
],
];
}
And now you can create your own pages via the admin panel, and access them via the url of each page.
You seem to be using the cms extension in your site component as-is.
In your web.php file, add this:
'components' => [
...
'i18n' => [
'translations' => [
'*' => [
'class' => 'yii\i18n\PhpMessageSource',
'basePath' => '#app/messages'
'sourceLanguage' => 'en',
],
],
],
]
...,
'controllerMap': => [
'cms' => 'yii2mod\cms\controllers\CmsController'
],
NOTE: You should exclude the path about configuring it as a component in the admin module since you're not using it anyway.
If you were using it in a module, then the steps documented in the README is just fine for you.
I'm working on the backend side of a plugin and I'm having some issues getting it to work. I created all the plugin needed files, models, register things and so on but any time I try to access backend URL as per example http://alomicuba.dev/backend/alomicuba/balancerecharge I get a 404 error and I don't know what I''m doing wrong. This is the code on /plugins/alomicuba/balancerecharge/controllers/balancerecharge/BalanceRecharge.php file:
<?php namespace Alomicuba\BalanceRecharge\Controllers;
use Flash;
use BackendMenu;
use Backend\Classes\Controller;
use System\Classes\SettingsManager;
use Alomicuba\RechargeBalance\Models\Settings as BalanceRechargeSettings;
class BalanceRecharge extends Controller
{
public $implement = [
'Backend.Behaviors.FormController',
'Backend.Behaviors.ListController'
];
public $formConfig = 'config_form.yaml';
public $listConfig = 'config_list.yaml';
public $requiredPermissions = ['balancerecharge.*'];
public $bodyClass = 'compact-container';
public function __construct()
{
parent::__construct();
BackendMenu::setContext('Alomicuba.BalanceRecharge', 'balancerecharge');
SettingsManager::setContext('Alomicuba.BalanceRecharge', 'settings');
}
}
And this is the Plugin.php code:
class Plugin extends PluginBase {
/**
* Returns information about this plugin.
*
* #return array
*/
public function pluginDetails()
{
return [
'name' => 'Balance Recharge',
'description' => 'Plugin that allows users to recharge theirs balance through the PayPal payment gateway',
'author' => 'Dynamo Technology Solutions',
'icon' => 'icon-credit-card'
];
}
public function registerNavigation()
{
return [
'bradmin' => [
'label' => 'Balance Recharge',
'url' => Backend::url('alomicuba/balancerecharge/balancerecharge'),
'icon' => 'icon-credit-card',
'permissions' => ['brecharge.*'],
'order' => 500,
'sideMenu' => [
'brecharge' => [
'label' => 'Balance Recharge',
'icon' => 'icon-credit-card',
'url' => Backend::url('alomicuba/balancerecharge/balancerecharge'),
'permissions' => ['brecharge.*'],
],
]
]
];
}
public function registerSettings()
{
return [
'settings' => [
'label' => 'Balance Recharge PayPal Configuration',
'description' => 'Manage the settings for Balance Recharge.',
'category' => 'AloMiCuba',
'icon' => 'icon-cog',
'class' => 'Alomicuba\BalanceRecharge\Models\Settings',
'order' => 100
]
];
}
public function boot()
{
\App::register('Barryvdh\Omnipay\ServiceProvider');
\Illuminate\Foundation\AliasLoader::getInstance()->alias('Omnipay', 'Barryvdh\Omnipay\Facade');
UserModel::extend(function($model){
$model->hasMany['payment'] = ['Alomicuba\BalanceRecharge\Models\Payment'];
});
}
public function registerComponents()
{
return [
'Alomicuba\BalanceRecharge\Components\Payment' => 'Payment'
];
}
}
I'm missing something here?
In October CMS URL for controller is like
domain-name/backend/author-name/plugin-name/controller-name
so in your case you can use
alomicuba.dev/backend/alomicuba/balancerecharge/balancerecharge
So I have a "simple" form
class SiteAddForm extends Form
{
public function __construct()
{
parent::__construct('add_site_form');
$site = new SiteFieldSet();
$this->add($site);
}
public function getTemplate()
{
return 'site_add.phtml';
}
}
The form it self does nothing. It adds a field_set and returns a template name.
The SiteFieldSet looks likes:
class SiteFieldSet
extends FieldSet
implements InputFilterProviderInterface
{
public function __construct()
{
parent::__construct('site');
$name = new Text('name');
$this->add($name);
$domains = new Collection('domains');
$domains->setTargetElement(new DomainFieldSet())
->setShouldCreateTemplate(true);
$this->add($domains);
}
public function getTemplate()
{
return 'site.phtml';
}
/**
* Should return an array specification compatible with
* {#link Zend\InputFilter\Factory::createInputFilter()}.
*
* #return array
*/
public function getInputFilterSpecification()
{
return [
'name' => [
'required' => true,
'validators' => [
new StringLength([
'min' => 200,
])
]
],
'domains' => [
'required' => true,
],
];
}
}
It adds a text and collection element to the fieldset. The field set implements InputFilterProviderInterface to validate the data thrown into it.
The name must be at least 200 chars (for testing) and the collection is required.
But now comes my problem. With the field set that is thrown into the collection, code:
class DomainFieldSet
extends FieldSet
implements InputFilterProviderInterface
{
public function __construct()
{
parent::__construct('domain');
$host = new Url('host');
$this->add($host);
$language = new Select('language', [
'value_options' => [
'nl_NL' => 'NL',
],
]);
$this->add($language);
$theme = new Select('theme', [
'value_options' => [
'yeti' => 'Yeti',
]
]);
$this->add($theme);
}
public function getTemplate()
{
return 'domain.phtml';
}
/**
* Should return an array specification compatible with
* {#link Zend\InputFilter\Factory::createInputFilter()}.
*
* #return array
*/
public function getInputFilterSpecification()
{
return [
'host' => [
'required' => true,
'validators' => [
new StringLength([
'min' => 200,
])
]
],
'language' => [
'required' => true,
],
'theme' => [
'required' => true,
],
];
}
}
Again nothing special. There are now three elements defined host, theme & language. Again the field set implements InputFilterProviderInterface. So there must be an getInputFilterSpecification in the class.
When I fill in the form
site[name] = "test"
site[domains][0][host] = 'test'
site[domains][0][theme] = 'yeti'
site[domains][0][language] = 'nl_NL'
It gives an error for site[name] saying it must be atleast 200 chars, so validations "works"
But it should also give an error on site[domains][0][host] that it needs to be atleast 200 chars (code was copy pasted, and the use is correct).
So why doesn't the validation kicks in, and or how can I solve the issue so a element/field set inside a collection is properly validated
Try using setValidationGroup in the form __construct method
like:
public function __construct()
{
$this->add(array(
'type' => 'Your\Namespace\SiteFieldSet',
'options' => array(
'use_as_base_fieldset' => true,
),
));
$this->setValidationGroup(array(
'site' => array(
'domain' => array(
'host',
'language',
'theme',
),
),
));
}
or this may also work...
$this->setValidationGroup(FormInterface::VALIDATE_ALL);