13 – Delete blog post

This is very similar to adding and editing a post.

First up, we’re going to add the route so we can delete a post by creating the delete route into the view child routes.

'delete' => [
    //routeName: blog/view/delete
    //route: /blog/:id[/slug]/delete
    'type'          => 'Literal',
    'may_terminate' => true,
    'options'       => [
        'route'    => '/delete',
        'defaults' => [
            'module'     => 'Blog',
            'controller' => 'Blog\\Controller\\Post',
            'action'     => 'delete',
        ],
    ],
],

Next up, we’re using the following code for the delete.phtml view.

/** @var Blog\Form\PostDeleteForm $form */
?>

<section>
    <?php $form->prepare() ?>
    <?= $this->form()->openTag($form) ?>
    <?= $this->formRow($form->get('csrf')) ?>

    <?= $this->formRow($form->get('submit[yes]')) ?>
    <?= $this->formRow($form->get('submit[no]')) ?>
    <?= $this->form()->closeTag($form) ?>
</section>

You’ll notice that this is different from the previous form pages (add/edit). This is because it only has a yes or no option on it.

Next up is the deleteAction, copy the followin into the PostController.

public function deleteAction()
{
    $id = $this->params()->fromRoute('id');
    if (empty($id)) {
        throw new \Exception('Id must be given; if given, it was not found.');
    }

    /** @var Post $post */
    $post = $this->getEntityManager()->getRepository(Entity::POST)->find($id);
    if (!$post instanceof Post) {
    throw new \Exception('Something went wrong retreving this post, please try again or contact us.');
    }

    /** @var PostForm $form */
    $form = $this->getServiceLocator()->get('post_delete_form');

    /** @var Request $request */
    $request = $this->getRequest();
    if ($request->isPost()) {
        $form->setData($request->getPost());

        if ($form->isValid()) {
            if (array_key_exists('yes', $request->getPost()->toArray()['submit'])) {
                //Save post to DB
                $this->getEntityManager()->remove($post);
                $this->getEntityManager()->flush();

                //Redirect user to view all posts
                $this->redirect()->toRoute('blog');
            } else {

                return $this->redirect()->toRoute('blog/view', [
                    'id' => $post->getId(),
                    'slug' => $post->getSlug(),
                ]);
            }
        }
    }

    // Bind object so existing values are set when viewing form
    $form->bind($post);
    return [
        'form' => $form,
    ];
}

You’ll notice that it looks a lot like the editAction. There are, however some subtle differences. For one, we use a different $form. Also, after the check we try to see if a key yes is present in the array of post data.

If it is, we don’t use the EntityManager’s persist() function to store the post, instead we user remove(). This tells the EntityManager that it should be removed, however, until you flush() it won’t actually be gone.

If the key yes isn’t present, we route the user back to the post. If it is, and thus the post has been removed, we route the user back to the index of the blog.

Also, we always bind the $post object to the $form object. This is opposed to the editAction where we only want to bind before a user sends us back data. For the deleteAction we always want to show the stored data, so that we could show the user the data that will be deleted if they were to proceed.

Next, we create the route to the page, the same was as for the edit, in the view.phtml file with the code below (add it below the edit link):

<a href="<?= $this->url('blog/view/delete', [
    'id'   => $this->escapeHtml($post->getId()),
    'slug' => $this->escapeHtml($post->getSlug())
]) ?>"
   title="<?= $this->translate('Delete') ?> - <?= $this->escapeHtml($post->getTitle()) ?>">
    <?= $this->translate('Delete') ?>
</a>

If you were to view a post now, you would see the link appear. However, visiting it would get you a ServiceNotFoundException for your troubles.

We still have to create the PostDeleteForm.php file and set up its factory in the Module.php file of the module.

Create the PostDeleteForm.php file next to the PostForm.php and copy in the code below.

namespace Blog\Form;

use Application\Form\AbstractForm;
use Zend\Form\Element;

class PostDeleteForm extends AbstractForm
{
    public function __construct($name = 'post-delete-form', $options = [])
    {
        parent::__construct($name, $options);

        $this->addElements();
    }

    public function addElements()
    {
        $this->add([
            'name'       => 'submit[yes]',
            'type'       => 'submit',
            'attributes' => [
                'name' => 'submit[yes]',
                'value'       => 'Delete post',
                'data-action' => 'delete',
            ],
        ]);

        $this->add([
            'name'       => 'submit[no]',
            'type'       => 'submit',
            'attributes' => [
                'name' => 'submit[no]',
                'value'       => 'Cancel',
                'data-action' => 'cancel',
            ],
        ]);
    }
}

As you can see, the start of this form is the same as for the PostForm class, apart from the default name in the __construct() function.

You’ll notice that the form only contains two elements. Here a little piece of Zend magic happens. Zend recognises that the names of the two elements are essentially the same, namely (sorry, couldn’t help myself): submit. However, the [] after it tells the Zend Framework to set it up as an array, allowing the elements to have the same name, but different values.

Next up, we add the factory to the Module.php file. Add the code below after the post_form factory, but within the array to be returned by thegetServiceConfig() function.

'post_delete_form' => function ()
{
    $form = new PostDeleteForm();
    $form->setInputFilter(new InputFilter());

    return $form;
}

A quick note on this factory. You’ll notice that we use just “InputFilter”. We do not need to set anything special or validate anything apart from the CSRF for this action. Therefore the default Zend InputFilter class is enough.