Form::add()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
3
/**
4
 * This file is part of slick/form package
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Slick\Form;
11
12
use Psr\Http\Message\ServerRequestInterface;
13
use Slick\Form\Element\ContainerInterface;
14
use Slick\Form\Element\FieldSet;
15
use Slick\Form\Element\RenderAwareMethods;
16
use Slick\Form\Input\File;
17
use Slick\Form\Renderer\Form as FormRenderer;
18
use Slick\Form\Renderer\RendererInterface;
19
use Slick\Http\PhpEnvironment\Request;
20
21
/**
22
 * HTTP Form
23
 *
24
 * @package Slick\Form
25
 * @author  Filipe Silva <[email protected]>
26
 */
27
class Form extends FieldSet implements FormInterface
28
{
29
30
    /**
31
     * @var string Form id
32
     */
33
    protected $id;
34
35
    /**
36
     * Add render capabilities to element
37
     */
38
    use RenderAwareMethods;
39
40
    /**
41
     * Creates a form with basic attributes
42
     */
43 54
    public function __construct()
44
    {
45 54
        parent::__construct();
46 54
        $this->setAttribute('action', '')
47 54
            ->setAttribute('method', 'post');
48 54
    }
49
50
    /**
51
     * @var ServerRequestInterface
52
     */
53
    protected $request;
54
55
    /**
56
     * Set data to validate and/or populate elements
57
     *
58
     * @param array $data
59
     *
60
     * @return self|$this|FormInterface
61
     */
62 2
    public function setData($data)
63
    {
64 2
        $this->setValues($data);
65 2
        return $this;
66
    }
67
68
    /**
69
     * Returns submitted data or current data
70
     *
71
     * @return array
72
     */
73 4
    public function getData()
74
    {
75 4
        $data = [];
76 4
        $this->getElementData($this, $data);
77 4
        return $data;
78
    }
79
80
    /**
81
     * Sets HTTP server request
82
     *
83
     * The form data should be populated from this requests.
84
     *
85
     * @param ServerRequestInterface $request
86
     * @return self|$this|FormInterface
87
     */
88 6
    public function setRequest(ServerRequestInterface $request)
89
    {
90 6
        $this->request = $request;
91 6
        return $this;
92
    }
93
94
    /**
95
     * Recursively collects all posted data
96
     *
97
     * @param ContainerInterface $container
98
     * @param array $data
99
     */
100 4
    protected function getElementData(
101 2
        ContainerInterface $container, &$data = []
102
    ) {
103 4
         foreach ($container as $element) {
104 2
             if ($element instanceof InputInterface) {
105 2
                 $data[$element->getName()] = $element->getValue();
106 2
                 continue;
107
             }
108
109 4
             if ($element instanceof ContainerInterface) {
110 2
                 $this->getElementData($element, $data);
111 2
                 continue;
112
             }
113 4
        }
114 4
    }
115
116
    /**
117
     * Gets the HTML renderer for this element
118
     *
119
     * @return RendererInterface
120
     */
121 2
    protected function getRenderer()
122
    {
123 2
        return new FormRenderer($this);
124
    }
125
126
    /**
127
     * Gets the form id
128
     *
129
     * @return string
130
     */
131 6
    public function getId()
132
    {
133 6
        return $this->id;
134
    }
135
136
    /**
137
     * Sets internal form ID
138
     *
139
     * @param string $formId
140
     *
141
     * @return self|$this|FormInterface
142
     */
143 16
    public function setId($formId)
144
    {
145 16
        $this->id = $formId;
146 16
        return $this;
147
    }
148
149
    /**
150
     * Adds an element to the container
151
     *
152
     * @param ElementInterface $element
153
     *
154
     * @return self|$this|ContainerInterface
155
     */
156 42
    public function add(ElementInterface $element)
157
    {
158 42
        if ($element instanceof File) {
159 2
            $this->setAttribute('enctype', 'multipart/form-data');
160 2
        }
161 42
        return parent::add($element);
162
    }
163
164
    /**
165
     * Check if for was submitted or not
166
     *
167
     * @return boolean
168
     */
169 4
    public function wasSubmitted()
170
    {
171 4
        $formMethod = strtoupper($this->getAttribute('method', 'post'));
172 4
        $formId = $this->getRequest()->getPost('form-id', false);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Http\Message\ServerRequestInterface as the method getPost() does only exist in the following implementations of said interface: Slick\Http\PhpEnvironment\Request.

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...
173
        $formId = $formId
174 4
            ? $formId
175 4
            : $this->getRequest()->getQuery('form-id', null);
0 ignored issues
show
Bug introduced by
The method getQuery() does not exist on Psr\Http\Message\ServerRequestInterface. Did you maybe mean getQueryParams()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
176
177 4
        $submitted = $this->request->getMethod() == $formMethod &&
178 4
            $formId == $this->getId();
179 4
        if ($submitted) {
180 2
            $this->updateValuesFromRequest();
181 2
        }
182 4
        return $submitted;
183
    }
184
185
    /**
186
     * Gets the HTTP request
187
     *
188
     * @return ServerRequestInterface|Request
189
     */
190 6
    public function getRequest()
191
    {
192 6
        if (null == $this->request) {
193 2
            $this->request = new Request();
194 2
        }
195 6
        return $this->request;
196
    }
197
198
    /**
199
     * Assign form values from request
200
     */
201 2
    protected function updateValuesFromRequest()
0 ignored issues
show
Coding Style introduced by
updateValuesFromRequest uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
202
    {
203 2
        $data = $this->getRequest()->isPost()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Http\Message\ServerRequestInterface as the method isPost() does only exist in the following implementations of said interface: Slick\Http\PhpEnvironment\Request.

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...
204 2
            ? $this->getRequest()->getPost()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Http\Message\ServerRequestInterface as the method getPost() does only exist in the following implementations of said interface: Slick\Http\PhpEnvironment\Request.

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...
205 2
            : $this->getRequest()->getQuery();
0 ignored issues
show
Bug introduced by
The method getQuery() does not exist on Psr\Http\Message\ServerRequestInterface. Did you maybe mean getQueryParams()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
206 2
        if (isset($_FILES)) {
207 2
            $data = array_merge($data, $this->request->getUploadedFiles());
208 2
        }
209 2
        $this->setValues($data);
210
211
    }
212
}