Skip to content
This repository has been archived by the owner on Aug 18, 2022. It is now read-only.

Commit

Permalink
update Wizard to use StageContainer instead of full container and use…
Browse files Browse the repository at this point in the history
… autowiring and autoconfiguration.

Also remove dependencies script
  • Loading branch information
craigh committed Apr 6, 2020
1 parent 1d400af commit ea99016
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 1,762 deletions.
50 changes: 50 additions & 0 deletions AbstractStageContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Zikula package.
*
* Copyright Zikula Foundation - https://ziku.la/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Zikula\Component\Wizard;

abstract class AbstractStageContainer implements StageContainerInterface
{
private $stages;

/**
* @param StageInterface[] $stages
*/
public function __construct(iterable $stages = [])
{
$this->stages = [];
foreach ($stages as $stage) {
$this->add($stage);
}
}

public function add(StageInterface $stage): void
{
$this->stages[get_class($stage)] = $stage;
}

public function get(string $id): ?StageInterface
{
return $this->stages[$id] ?? null;
}

public function has(string $id): bool
{
return isset($this->stages[$id]);
}

public function all(): array
{
return $this->stages;
}
}
29 changes: 0 additions & 29 deletions InjectContainerInterface.php

This file was deleted.

52 changes: 44 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,24 @@ The Wizard Component is a management tool for multi-stage user interaction. It u
Wizard class to create a workflow that is compatible with Symfony Forms and Twig templating. Relying on the concept of
**Stages**, the developer is able to define a sequence using a `.yml` file and control that sequence in their Controller.

On instantiation, the Wizard class requires the **Symfony Container** and a full path to the **stage definition file**
On instantiation, the Wizard class requires a **StageContainer** and a full path to the **stage definition file**
(in yaml format). The Wizard will load the stage definitions from there. The Wizard Component includes a YamlFileLoader
for this purpose.

Create a concrete class that extends the `Zikula\Component\Wizard\AbstractStageContainer`. Use autowiring and autoconfiguration
to configure the class:

_instanceof: # only works for classes that are configured in this file
Zikula\Component\Wizard\StageInterface:
tags: ['my_special_tag']

# if this is the only instance of the interface you will use, you can use an alias
Zikula\Component\Wizard\StageContainerInterface: '@Acme\Bundle\MyCustomBundle\Container\FooStageContainer'

Acme\Bundle\MyCustomBundle\Container\FooStageContainer:
arguments:
$stages: !tagged_iterator my_special_tag


Stage
-----
Expand All @@ -19,10 +33,11 @@ completing some logic and returning a boolean. Stages marked as NOT **necessary*
instantiation and processing of the `isNecessary()` method, allowing that stage to complete tasks as needed before
proceeding. Stages are skipped when the Wizard calls the `getCurrentStage()` method.

Use Symfony autowiring and autoconfiguring or manual Dependency Injection to add services to your stages.

Stages may optionally implement:
- `InjectContainerInterface` if the Stage requires the Symfony container
- `FormHandlerInterface` if the Stage will be using a Symfony Form
- `WizardCompleteInterface` to indicate the wizard is finished and wrap up any logic at the end.

The Wizard can be halted in the `isNecessary()` method by throwing an `AbortStageException`. The message of which is
available for retrieval using `$wizard->getWarning()`.
Expand Down Expand Up @@ -65,48 +80,69 @@ stages:
###Sample Controller
```php
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Zikula\Component\Wizard\FormHandlerInterface;
use Zikula\Component\Wizard\StageContainerInterface;
use Zikula\Component\Wizard\Wizard;
use Zikula\Component\Wizard\WizardCompleteInterface;

class MyController
{
private $container
/**
* @var StageContainerInterface
*/
private $stageContainer;

/**
* @var \Twig\Environment
*/
private $twig;

/**
* @var FormFactoryInterface
*/
private $formFactory;

/**
* @var RouterInterface
*/
private $router;

/**
* define route = 'index/{stage}'
*/
public function indexAction(Request $request, $stage)
{
// begin the wizard
$wizard = new Wizard($this->container, realpath(__DIR__ . '/../Resources/config/stages.yml'));
$wizard = new Wizard($this->stageContainer, realpath(__DIR__ . '/../Resources/config/stages.yml'));
$currentStage = $wizard->getCurrentStage($stage);
if ($currentStage instanceof WizardCompleteInterface) {
return $currentStage->getResponse($request);
}
$templateParams = $currentStage->getTemplateParams();
if ($wizard->isHalted()) {
$request->getSession()->getFlashBag()->add('danger', $wizard->getWarning());
return $this->container->get('templating')->renderResponse('MyBundle::error.html.twig', $templateParams);
return new Response($this->twig->render('@MyCustomBundle/error.html.twig', $templateParams));
}

// handle the form
if ($currentStage instanceof FormHandlerInterface) {
$form = $this->container->get('form.factory')->create($currentStage->getFormType());
$form = $this->formFactory->create($currentStage->getFormType());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$currentStage->handleFormResult($form);
$url = $this->container->get('router')->generate('index', ['stage' => $wizard->getNextStage()->getName()], true);
$url = $this->router->generate('index', ['stage' => $wizard->getNextStage()->getName()], true);

return new RedirectResponse($url);
}
$templateParams['form'] = $form->createView();
}

return $this->container->get('templating')->renderResponse($currentStage->getTemplateName(), $templateParams);
return new Response($this->twig->render($currentStage->getTemplateName(), $templateParams));
}
}
```
Expand Down
30 changes: 30 additions & 0 deletions StageContainerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

/*
* This file is part of the Zikula package.
*
* Copyright Zikula Foundation - https://ziku.la/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Zikula\Component\Wizard;

interface StageContainerInterface
{
/**
* @param StageInterface[] $stages
*/
public function __construct(iterable $stages = []);

public function add(StageInterface $stage): void;

public function get(string $id): ?StageInterface;

public function has(string $id): bool;

public function all(): array;
}
27 changes: 11 additions & 16 deletions Wizard.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
use InvalidArgumentException;
use Symfony\Component\Config\Exception\LoaderLoadException;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;

/**
Expand All @@ -30,9 +29,9 @@
class Wizard
{
/**
* @var ContainerInterface
* @var StageContainerInterface
*/
private $container;
private $stageContainer;

/**
* @var array
Expand Down Expand Up @@ -69,9 +68,9 @@ class Wizard
*
* @throws LoaderLoadException
*/
public function __construct(ContainerInterface $container, string $path)
public function __construct(StageContainerInterface $stageContainer, string $path)
{
$this->container = $container;
$this->stageContainer = $stageContainer;
if (!empty($path)) {
$this->loadStagesFromYaml($path);
} else {
Expand Down Expand Up @@ -125,7 +124,7 @@ public function getCurrentStage(string $name): StageInterface
$useCurrentStage = false;
/** @var StageInterface $currentStage */
if (!isset($currentStage)) {
$currentStage = $this->getStageInstance($stageClass);
$currentStage = $this->getStage($stageClass);
}
$this->currentStageName = $currentStage->getName();
try {
Expand Down Expand Up @@ -173,25 +172,21 @@ private function getSequentialStage(string $direction): ?StageInterface
}
$key = $dir($this->stageOrder);
if (null !== $key && false !== $key) {
return $this->getStageInstance($this->stagesByName[$key]);
return $this->getStage($this->stagesByName[$key]);
}

return null;
}

/**
* Factory class to instantiate a StageClass
* Get stage from stageContainer
*/
private function getStageInstance(string $stageClass): StageInterface
private function getStage(string $stageClass): StageInterface
{
if (!class_exists($stageClass)) {
throw new FileNotFoundException('Error: Could not find requested stage class.');
if ($this->stageContainer->has($stageClass)) {
return $this->stageContainer->get($stageClass);
}
if (in_array("Zikula\\Component\\Wizard\\InjectContainerInterface", class_implements($stageClass), true)) {
return new $stageClass($this->container);
}

return new $stageClass();
throw new FileNotFoundException('Error: Could not find requested stage class.');
}

/**
Expand Down
8 changes: 4 additions & 4 deletions YamlFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class YamlFileLoader extends FileLoader
/**
* {@inheritdoc}
*/
public function load($resource, $type = null)
public function load($resource, string $type = null)
{
$path = $this->locator->locate($resource);

Expand All @@ -38,7 +38,7 @@ public function load($resource, $type = null)
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
public function supports($resource, string $type = null)
{
return is_string($resource) && 'yml' === pathinfo($resource, PATHINFO_EXTENSION);
}
Expand Down Expand Up @@ -85,11 +85,11 @@ private function validate($content, string $file): array
}

if (!is_array($content)) {
throw new InvalidArgumentException(__f('The yaml file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file));
throw new InvalidArgumentException(sprintf('The yaml file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file));
}

if (isset($content['stages']) && !is_array($content['stages'])) {
throw new InvalidArgumentException(__f('The "stages" key should contain an array in %s. Check your YAML syntax.', $file));
throw new InvalidArgumentException(sprintf('The "stages" key should contain an array in %s. Check your YAML syntax.', $file));
}

return $content;
Expand Down
Loading

0 comments on commit ea99016

Please sign in to comment.