Completed
Push — master ( dafeb1...a1063d )
by grégoire
18s queued 14s
created

Model::getModel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
/*
3
 * This file is part of the PommProject/ModelManager package.
4
 *
5
 * (c) 2014 - 2015 Grégoire HUBERT <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace PommProject\ModelManager\Model;
11
12
use PommProject\Foundation\Client\ClientInterface;
13
use PommProject\Foundation\Session\Session;
14
use PommProject\ModelManager\Converter\PgEntity;
15
use PommProject\ModelManager\Exception\ModelException;
16
use PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface;
17
18
/**
19
 * Model
20
 *
21
 * Base class for custom Model classes.
22
 *
23
 * @abstract
24
 * @package     Pomm
25
 * @copyright   2014 - 2019 Grégoire HUBERT
26
 * @author      Grégoire HUBERT
27
 * @license     X11 {@link http://opensource.org/licenses/mit-license.php}
28
 * @see         ClientInterface
29
 */
30
abstract class Model implements ClientInterface
31
{
32
    protected $session;
33
    protected $flexible_entity_class;
34
35
36
    /**
37
     * @var RowStructure
38
     */
39
    protected $structure;
40
41
    /**
42
     * getSession
43
     *
44
     * Return the current session. If session is not set, a ModelException is
45
     * thrown.
46
     *
47
     * @access public
48
     * @return Session
49
     * @throws ModelException
50
     */
51
    public function getSession()
52
    {
53
        if ($this->session === null) {
54
            throw new ModelException(sprintf("Model class '%s' is not registered against the session.", get_class($this)));
55
        }
56
57
        return $this->session;
58
    }
59
60
    /**
61
     * getClientType
62
     *
63
     * @see ClientInterface
64
     */
65
    public function getClientType()
66
    {
67
        return 'model';
68
    }
69
70
    /**
71
     * getClientIdentifier
72
     *
73
     * @see ClientInterface
74
     */
75
    public function getClientIdentifier()
76
    {
77
        return trim(get_class($this), "\\");
78
    }
79
80
    /**
81
     * initialize
82
     *
83
     * @see ClientInterface
84
     */
85
    public function initialize(Session $session)
86
    {
87
        $this->session = $session;
88
89
        if ($this->structure === null) {
90
            throw new ModelException(sprintf("Structure not set while initializing Model class '%s'.", get_class($this)));
91
        }
92
93
        if ($this->flexible_entity_class == null) {
94
            throw new ModelException(sprintf("Flexible entity not set while initializing Model class '%s'.", get_class($this)));
95
        } elseif (!(new \ReflectionClass($this->flexible_entity_class))
96
            ->implementsInterface('\PommProject\ModelManager\Model\FlexibleEntity\FlexibleEntityInterface')
97
        ) {
98
            throw new ModelException(sprintf("Flexible entity must implement FlexibleEntityInterface."));
99
        }
100
101
        $session->getPoolerForType('converter')
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface PommProject\Foundation\C...t\ClientPoolerInterface as the method getConverterHolder() does only exist in the following implementations of said interface: PommProject\Foundation\Converter\ConverterPooler.

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...
102
            ->getConverterHolder()
103
            ->registerConverter(
104
                $this->flexible_entity_class,
105
                new PgEntity(
106
                    $this->flexible_entity_class,
107
                    $this->getStructure()
108
                ),
109
                [
110
                    $this->getStructure()->getRelation(),
111
                    $this->flexible_entity_class,
112
                ]
113
        );
114
    }
115
116
    /**
117
     * shutdown
118
     *
119
     * @see ClientInterface
120
     */
121
    public function shutdown()
122
    {
123
    }
124
125
    /**
126
     * createEntity
127
     *
128
     * Create a new entity.
129
     *
130
     * @access public
131
     * @param array $values
132
     * @return FlexibleEntityInterface
133
     */
134
    public function createEntity(array $values = [])
135
    {
136
        $class_name = $this->getFlexibleEntityClass();
137
138
        return (new $class_name)
139
            ->hydrate($values)
140
            ;
141
    }
142
143
    /**
144
     * query
145
     *
146
     * Execute the given query and return a Collection iterator on results. If
147
     * no projections are passed, it will use the default projection using
148
     * createProjection() method.
149
     *
150
     * @access protected
151
     * @param  string             $sql
152
     * @param  array              $values
153
     * @param  Projection         $projection
154
     * @return CollectionIterator
155
     */
156
    protected function query($sql, array $values = [], Projection $projection = null)
157
    {
158
        if ($projection === null) {
159
            $projection = $this->createProjection();
160
        }
161
162
        $result = $this
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface PommProject\Foundation\Client\ClientInterface as the method execute() does only exist in the following implementations of said interface: PommProject\Foundation\PreparedQuery\PreparedQuery.

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...
163
            ->getSession()
164
            ->getClientUsingPooler('prepared_query', $sql)
165
            ->execute($values)
166
            ;
167
168
        $collection = new CollectionIterator(
169
            $result,
170
            $this->getSession(),
171
            $projection
172
        );
173
174
        return $collection;
175
    }
176
177
    /**
178
     * createDefaultProjection
179
     *
180
     * This method creates a projection based on the structure definition of
181
     * the underlying relation. It may be used to shunt parent createProjection
182
     * call in inherited classes.
183
     * This method can be used where a projection that sticks to table
184
     * definition is needed like recursive CTEs. For normal projections, use
185
     * createProjection instead.
186
     *
187
     * @access public
188
     * @return Projection
189
     */
190
    final public function createDefaultProjection()
191
    {
192
        return new Projection($this->flexible_entity_class, $this->structure->getDefinition());
193
    }
194
195
    /**
196
     * createProjection
197
     *
198
     * This is a helper to create a new projection according to the current
199
     * structure.Overriding this method will change projection for all models.
200
     *
201
     * @access  public
202
     * @return  Projection
203
     */
204
    public function createProjection()
205
    {
206
        return $this->createDefaultProjection();
207
    }
208
209
    /**
210
     * checkFlexibleEntity
211
     *
212
     * Check if the given entity is an instance of this model's flexible class.
213
     * If not an exception is thrown.
214
     *
215
     * @access protected
216
     * @param  FlexibleEntityInterface $entity
217
     * @throws \InvalidArgumentException
218
     * @return Model          $this
219
     */
220
    protected function checkFlexibleEntity(FlexibleEntityInterface $entity)
221
    {
222
        if (!($entity instanceof $this->flexible_entity_class)) {
223
            throw new \InvalidArgumentException(sprintf(
224
                "Entity class '%s' is not a '%s'.",
225
                get_class($entity),
226
                $this->flexible_entity_class
227
            ));
228
        }
229
230
        return $this;
231
    }
232
233
    /**
234
     * getStructure
235
     *
236
     * Return the structure.
237
     *
238
     * @access public
239
     * @return RowStructure
240
     */
241
    public function getStructure()
242
    {
243
        return $this->structure;
244
    }
245
246
    /**
247
     * getModel
248
     *
249
     * Proxy to Session::getModel();
250
     *
251
     * @param  string    model identifier
252
     * @return Model
253
     */
254
    protected function getModel($identifier)
255
    {
256
        return $this
257
            ->getSession()
258
            ->getClientUsingPooler('model', $identifier);
259
    }
260
261
262
    /**
263
     * getFlexibleEntityClass
264
     *
265
     * Return the according flexible entity class associate with this Model
266
     * instance.
267
     *
268
     * @access public
269
     * @return string
270
     */
271
    public function getFlexibleEntityClass()
272
    {
273
        return $this->flexible_entity_class;
274
    }
275
276
    /**
277
     * escapeLiteral
278
     *
279
     * Handy method to escape strings.
280
     *
281
     * @access protected
282
     * @param  string $string
283
     * @return string
284
     */
285
    protected function escapeLiteral($string)
286
    {
287
        return $this
288
            ->getSession()
289
            ->getConnection()
290
            ->escapeLiteral($string);
291
    }
292
293
    /**
294
     * escapeLiteral
295
     *
296
     * Handy method to escape strings.
297
     *
298
     * @access protected
299
     * @param  string $string
300
     * @return string
301
     */
302
    protected function escapeIdentifier($string)
303
    {
304
        return $this
305
            ->getSession()
306
            ->getConnection()
307
            ->escapeIdentifier($string);
308
    }
309
310
    /**
311
     * executeAnonymousQuery
312
     *
313
     * Handy method for DDL statements.
314
     *
315
     * @access protected
316
     * @param  string $sql
317
     * @return Model  $this
318
     */
319
    protected function executeAnonymousQuery($sql)
320
    {
321
        $this
322
            ->getSession()
323
            ->getConnection()
324
            ->executeAnonymousQuery($sql);
325
326
        return $this;
327
    }
328
}
329