Passed
Push — fix_coverage_in_scrutinizer ( cd0379...a04ba4 )
by Herberto
13:22
created

BlogController::delete()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2.0054

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 8
cts 9
cp 0.8889
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 2
crap 2.0054
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace App\Controller\Admin;
13
14
use App\Entity\Post;
15
use App\Form\PostType;
16
use App\Repository\PostRepository;
17
use App\Utils\Slugger;
18
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
19
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
20
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
21
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
22
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
23
use Symfony\Component\HttpFoundation\Request;
24
use Symfony\Component\HttpFoundation\Response;
25
26
/**
27
 * Controller used to manage blog contents in the backend.
28
 *
29
 * Please note that the application backend is developed manually for learning
30
 * purposes. However, in your real Symfony application you should use any of the
31
 * existing bundles that let you generate ready-to-use backends without effort.
32
 *
33
 * See http://knpbundles.com/keyword/admin
34
 *
35
 * @Route("/admin/post")
36
 * @Security("has_role('ROLE_ADMIN')")
37
 *
38
 * @author Ryan Weaver <[email protected]>
39
 * @author Javier Eguiluz <[email protected]>
40
 */
41
class BlogController extends AbstractController
42
{
43
    /**
44
     * Lists all Post entities.
45
     *
46
     * This controller responds to two different routes with the same URL:
47
     *   * 'admin_post_index' is the route with a name that follows the same
48
     *     structure as the rest of the controllers of this class.
49
     *   * 'admin_index' is a nice shortcut to the backend homepage. This allows
50
     *     to create simpler links in the templates. Moreover, in the future we
51
     *     could move this annotation to any other controller while maintaining
52
     *     the route name and therefore, without breaking any existing link.
53
     *
54
     * @Route("/", name="admin_index")
55
     * @Route("/", name="admin_post_index")
56
     * @Method("GET")
57
     */
58 1
    public function index(PostRepository $posts): Response
59
    {
60 1
        $authorPosts = $posts->findBy(['author' => $this->getUser()], ['publishedAt' => 'DESC']);
61
62 1
        return $this->render('admin/blog/index.html.twig', ['posts' => $authorPosts]);
63
    }
64
65
    /**
66
     * Creates a new Post entity.
67
     *
68
     * @Route("/new", name="admin_post_new")
69
     * @Method({"GET", "POST"})
70
     *
71
     * NOTE: the Method annotation is optional, but it's a recommended practice
72
     * to constraint the HTTP methods each controller responds to (by default
73
     * it responds to all methods).
74
     */
75 1
    public function new(Request $request): Response
76
    {
77 1
        $post = new Post();
78 1
        $post->setAuthor($this->getUser());
0 ignored issues
show
Documentation introduced by
$this->getUser() is of type null|object, but the function expects a object<App\Entity\User>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
79
80
        // See https://symfony.com/doc/current/book/forms.html#submitting-forms-with-multiple-buttons
81 1
        $form = $this->createForm(PostType::class, $post)
82 1
            ->add('saveAndCreateNew', SubmitType::class);
83
84 1
        $form->handleRequest($request);
85
86
        // the isSubmitted() method is completely optional because the other
87
        // isValid() method already checks whether the form is submitted.
88
        // However, we explicitly add it to improve code readability.
89
        // See https://symfony.com/doc/current/best_practices/forms.html#handling-form-submits
90 1
        if ($form->isSubmitted() && $form->isValid()) {
91 1
            $post->setSlug(Slugger::slugify($post->getTitle()));
92
93 1
            $em = $this->getDoctrine()->getManager();
94 1
            $em->persist($post);
95 1
            $em->flush();
96
97
            // Flash messages are used to notify the user about the result of the
98
            // actions. They are deleted automatically from the session as soon
99
            // as they are accessed.
100
            // See https://symfony.com/doc/current/book/controller.html#flash-messages
101 1
            $this->addFlash('success', 'post.created_successfully');
102
103 1
            if ($form->get('saveAndCreateNew')->isClicked()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Form\FormInterface as the method isClicked() does only exist in the following implementations of said interface: Symfony\Component\Form\SubmitButton.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
104
                return $this->redirectToRoute('admin_post_new');
105
            }
106
107 1
            return $this->redirectToRoute('admin_post_index');
108
        }
109
110 1
        return $this->render('admin/blog/new.html.twig', [
111 1
            'post' => $post,
112 1
            'form' => $form->createView(),
113
        ]);
114
    }
115
116
    /**
117
     * Finds and displays a Post entity.
118
     *
119
     * @Route("/{id}", requirements={"id": "\d+"}, name="admin_post_show")
120
     * @Method("GET")
121
     */
122 2
    public function show(Post $post): Response
123
    {
124
        // This security check can also be performed
125
        // using an annotation: @Security("is_granted('show', post)")
126 2
        $this->denyAccessUnlessGranted('show', $post, 'Posts can only be shown to their authors.');
127
128 2
        return $this->render('admin/blog/show.html.twig', [
129 2
            'post' => $post,
130
        ]);
131
    }
132
133
    /**
134
     * Displays a form to edit an existing Post entity.
135
     *
136
     * @Route("/{id}/edit", requirements={"id": "\d+"}, name="admin_post_edit")
137
     * @Method({"GET", "POST"})
138
     */
139 1
    public function edit(Request $request, Post $post): Response
140
    {
141 1
        $this->denyAccessUnlessGranted('edit', $post, 'Posts can only be edited by their authors.');
142
143 1
        $form = $this->createForm(PostType::class, $post);
144 1
        $form->handleRequest($request);
145
146 1
        if ($form->isSubmitted() && $form->isValid()) {
147 1
            $post->setSlug(Slugger::slugify($post->getTitle()));
148 1
            $this->getDoctrine()->getManager()->flush();
149
150 1
            $this->addFlash('success', 'post.updated_successfully');
151
152 1
            return $this->redirectToRoute('admin_post_edit', ['id' => $post->getId()]);
153
        }
154
155 1
        return $this->render('admin/blog/edit.html.twig', [
156 1
            'post' => $post,
157 1
            'form' => $form->createView(),
158
        ]);
159
    }
160
161
    /**
162
     * Deletes a Post entity.
163
     *
164
     * @Route("/{id}/delete", name="admin_post_delete")
165
     * @Method("POST")
166
     * @Security("is_granted('delete', post)")
167
     *
168
     * The Security annotation value is an expression (if it evaluates to false,
169
     * the authorization mechanism will prevent the user accessing this resource).
170
     */
171 1
    public function delete(Request $request, Post $post): Response
172
    {
173 1
        if (!$this->isCsrfTokenValid('delete', $request->request->get('token'))) {
174
            return $this->redirectToRoute('admin_post_index');
175
        }
176
177
        // Delete the tags associated with this blog post. This is done automatically
178
        // by Doctrine, except for SQLite (the database used in this application)
179
        // because foreign key support is not enabled by default in SQLite
180 1
        $post->getTags()->clear();
181
182 1
        $em = $this->getDoctrine()->getManager();
183 1
        $em->remove($post);
184 1
        $em->flush();
185
186 1
        $this->addFlash('success', 'post.deleted_successfully');
187
188 1
        return $this->redirectToRoute('admin_post_index');
189
    }
190
}
191