Skip to content

promani/MagicWizard

Repository files navigation

Magic Wizard for Symfony

Symfony has a great forms library. I think it lacks nothing and is very well achieved. Why is it not used more? This library only tries to show its potential by integrating the solution with Vue.js. The implementation is heavy inspired in EasyAdminBundle

Instllation

In a Symfony project install it via composer

composer require promani\magic-wizard

Requirements

  • Forms
  • Twig
  • ExpressionLanguage

Only for check. Be sure to have a template for forms.

twig:
    form_themes: ['bootstrap_4_layout.html.twig']

Implementation

First create a Flow class with a method getSteps with Steps

use MagicWizardBundle\Model\Flow;

class DemoFlow extends Flow
{
	public function getSteps(): array
	{
		return [
			new FirstStep(),
			new SecondStep(),
		];
	}

}

The steps are something like this:

use MagicWizardBundle\Model\Step;

class FirstStep extends Step
{
	public $title = 'First step';
	public $subtitle = 'subtitle';
	public $description = 'description';

	public function getFormType()
	{
		return Step1Type::class;
	}

}

As you can see, you also have to create a FormType but they are neither more nor less than the powerful Symfony FormTypes

By last add a class to your proyect that extends AbstractStepController

use MagicWizardBundle\Controller\AbstractStepController;
use Symfony\Component\Routing\Annotation\Route;

/**
 * @Route("/test", name="test_")
 */
class TestController extends AbstractStepController
{
	public static function getFlowClass(): string
	{
		return TestFlow::class;
	}
}

Did you notice the route? it is important that you do something very similar.

Go to see your Wizard: wizard

The information that is sent by the form is validated with all the features that the forms have. If the form is valid, the UI go to the next step with no refresh and save the information sent in the session. The path ends when there are no more steps or a step has no more forms.

You can change the color, change the icon for a name or add a url to your logo.

magic_wizard:
    color: '#FF5733'
    company_name: 'The wizard of steps' 
    logo_url: 'images/logo.png' ## with more priority between company_name

You can change the Flow template or the template of a single Step.

But you really want to change all the templates and make your own. See documentation

Additionally you can add callbacks for each Step. You may want to save the information in each case or maybe in the end. In your Step class add:

public function getCallback()
{
    return function ($data) {
        $this->client->post('https://backend.url', $data);
    };
}

You can allow it to fail or the UI sends a SweetAlert alert with the error.

public function isAllowFail()
{
    return false;
}

Something like that work as follow:

public function getCallback()
{
    return function ($data) {
        throw new \Exception(sprintf('You have sended %s fields', count($data)));
    };
}

wizard

You can skip a step with a condition. To do that, simply add:

public function getNextStep()
{
    return FurtherStep::class;
}

public function condition()
{
    return 'data.name == "Rambo"';
}

To write the condition see Expression Language documentation. You have in the data variable all the info of all Steps.

In summary:

You have a few entrypoints prefixed by your router configuration in the controller: /prefix/form For GET and the POST forms

/prefix/clear For clear all the information submited for the user

/prefix/back For go back only one Step

For this last endpoint may be you want implement a back button.

You are able to have many Controllers with diferent routes for diferent proposits. Not magic enought? Try it out!

A case of use

Imagine your registration in several steps with complex logic between steps: Magic Wizard can Handle it. The flow

class RegistrationFlow extends Flow
{
	public function getSteps(): array
	{
		return [
			new UserInfoStep(),
			new AgeStep(),
			new ConfirmDataStep(),
			new SuccessStep(),
			new FilureStep(),
		];
	}

}

The condition and nextStep for second step:

public function getCondition()
{
    return 'data.age > 13';
}

public function getNextStep()
{
    return FilureStep::class;
}

The formType for third step:

class ConfirmDataType extends AbstractType
{
	public function buildForm(FormBuilderInterface $builder, array $options)
    	{
    		$builder
    			->add('username', TextType::class, ['disabled' => 'true'])
    			->add('age', NumberType::class, ['disabled' => 'true'])
    			->add('submit', SubmitType::class)
    		;
    	}
}

At this point you have this: wizard

The success callback for third step:

public function getCallback()
{
    return function ($data, $container) {
        $em = $container->get('doctrine')->getManager();
        $user = new User($data['username'], $data['password']);
        $user->setAge($data['age']);
        $em->persist($user);
        $em->flush();
    };
}

WARNING: You can't allways get what you want. The container is a minimun conteiner that contains a few services.

The failure message for FailureStep:

class FirstStep extends Step
{
	public $title = 'Sorry!';
	public $subtitle = 'You are too young for register with us';
	public $image = 'baby.png';
}

At last you get:

wizard