View::detailedView()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
cc 3
nc 4
nop 1
1
<?php
2
3
namespace Alpha\View;
4
5
use Alpha\Controller\Front\FrontController;
6
use Alpha\Util\Logging\Logger;
7
use Alpha\Util\Config\ConfigProvider;
8
use Alpha\Model\ActiveRecord;
9
use Alpha\Model\Type\DEnum;
10
use Alpha\Exception\IllegalArguementException;
11
use Alpha\Util\Service\ServiceFactory;
12
use Alpha\View\Renderer\RendererProviderInterface;
13
use ReflectionClass;
14
15
/**
16
 * The master rendering view class for the Alpha Framework.
17
 *
18
 * @since 1.0
19
 *
20
 * @author John Collins <[email protected]>
21
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
22
 * @copyright Copyright (c) 2019, John Collins (founder of Alpha Framework).
23
 * All rights reserved.
24
 *
25
 * <pre>
26
 * Redistribution and use in source and binary forms, with or
27
 * without modification, are permitted provided that the
28
 * following conditions are met:
29
 *
30
 * * Redistributions of source code must retain the above
31
 *   copyright notice, this list of conditions and the
32
 *   following disclaimer.
33
 * * Redistributions in binary form must reproduce the above
34
 *   copyright notice, this list of conditions and the
35
 *   following disclaimer in the documentation and/or other
36
 *   materials provided with the distribution.
37
 * * Neither the name of the Alpha Framework nor the names
38
 *   of its contributors may be used to endorse or promote
39
 *   products derived from this software without specific
40
 *   prior written permission.
41
 *
42
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
43
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
44
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
45
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
46
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
47
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
48
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
49
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
50
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
51
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
52
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
53
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
54
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
55
 * </pre>
56
 */
57
class View
58
{
59
    /**
60
     * The business object that will be rendered.
61
     *
62
     * @var \Alpha\Model\ActiveRecord
63
     *
64
     * @since 1.0
65
     */
66
    protected $record;
67
68
    /**
69
     * The rendering provider that will be used to render the active record.
70
     *
71
     * @var \Alpha\View\Renderer\RendererProviderInterface
72
     *
73
     * @since 1.2
74
     */
75
    private static $provider;
76
77
    /**
78
     * Trace logger.
79
     *
80
     * @var Logger
81
     *
82
     * @since 1.0
83
     */
84
    private static $logger = null;
85
86
    /**
87
     * Constructor for the View.  As this is protected, use the View::getInstance method from a public scope.
88
     *
89
     * @param ActiveRecord $record           The main business object that this view is going to render
90
     * @param string       $acceptHeader Optionally pass the HTTP Accept header to select the correct renderer provider.
91
     *
92
     * @throws \Alpha\Exception\IllegalArguementException
93
     *
94
     * @since 1.0
95
     */
96
    protected function __construct($record, $acceptHeader = null)
97
    {
98
        self::$logger = new Logger('View');
99
        self::$logger->debug('>>__construct(Record=['.var_export($record, true).'], acceptHeader=['.$acceptHeader.'])');
100
101
        $config = ConfigProvider::getInstance();
102
103
        if ($record instanceof ActiveRecord) {
104
            $this->record = $record;
105
        } else {
106
            throw new IllegalArguementException('The record type provided ['.get_class($record).'] is not defined anywhere!');
107
        }
108
109
        self::setProvider($config->get('app.renderer.provider.name'), $acceptHeader);
110
        self::$provider->setRecord($this->record);
111
112
        self::$logger->debug('<<__construct');
113
    }
114
115
    /**
116
     * Static method which returns a View object or a custom child view for the Record specified
117
     * if one exists.
118
     *
119
     * @param ActiveRecord $record           The main business object that this view is going to render
120
     * @param bool         $returnParent Flag to enforce the return of this object instead of a child (defaults to false)
121
     * @param string       $acceptHeader Optionally pass the HTTP Accept header to select the correct renderer provider.
122
     *
123
     * @return View Returns a View object, or a child view object if one exists for this record
124
     *
125
     * @since 1.0
126
     */
127
    public static function getInstance($record, $returnParent = false, $acceptHeader = null)
128
    {
129
        if (self::$logger == null) {
130
            self::$logger = new Logger('View');
131
        }
132
        self::$logger->debug('>>getInstance(Record=['.var_export($record, true).'], returnParent=['.$returnParent.'], acceptHeader=['.$acceptHeader.'])');
133
134
        $class = new ReflectionClass($record);
135
        $childView = $class->getShortname();
136
        $childView = $childView.'View';
137
138
        // Check to see if a custom view exists for this record, and if it does return that view instead
139
        if (!$returnParent) {
140
            $className = '\Alpha\View\\'.$childView;
141
142
            if (class_exists($className)) {
143
                self::$logger->debug('<<getInstance [new '.$className.'('.get_class($record).')]');
144
145
                $instance = new $className($record, $acceptHeader);
146
147
                return $instance;
148
            }
149
150
            $className = '\View\\'.$childView;
151
152
            if (class_exists('\View\\'.$childView)) {
153
                self::$logger->debug('<<getInstance [new '.$className.'('.get_class($record).')]');
154
155
                $instance = new $className($record, $acceptHeader);
156
157
                return $instance;
158
            }
159
160
            self::$logger->debug('<<getInstance [new View('.get_class($record).', '.$acceptHeader.')]');
161
162
            return new self($record, $acceptHeader);
163
        } else {
164
            self::$logger->debug('<<getInstance [new View('.get_class($record).', '.$acceptHeader.')]');
165
166
            return new self($record, $acceptHeader);
167
        }
168
    }
169
170
    /**
171
     * Simple setter for the view business object.
172
     *
173
     * @param \Alpha\Model\ActiveRecord $record
174
     *
175
     * @throws \Alpha\Exception\IllegalArguementException
176
     *
177
     * @since 1.0
178
     */
179
    public function setRecord($record)
180
    {
181
        self::$logger->debug('>>setRecord(Record=['.var_export($record, true).'])');
182
183
        if ($record instanceof \Alpha\Model\ActiveRecord) {
184
            $this->record = $record;
185
            self::$provider->setRecord($this->record);
186
        } else {
187
            throw new IllegalArguementException('The Record provided ['.get_class($record).'] is not defined anywhere!');
188
        }
189
190
        self::$logger->debug('<<setRecord');
191
    }
192
193
    /**
194
     * Gets the Record attached to this view (if any).
195
     *
196
     * @return \Alpha\Model\ActiveRecord
197
     *
198
     * @since 1.0
199
     */
200
    public function getRecord()
201
    {
202
        return $this->record;
203
    }
204
205
    /**
206
     * Renders the default create view.
207
     *
208
     * @param array $fields Hash array of fields to pass to the template
209
     *
210
     * @return string
211
     *
212
     * @since 1.0
213
     */
214
    public function createView($fields = array())
215
    {
216
        self::$logger->debug('>>createView(fields=['.var_export($fields, true).'])');
217
218
        if (method_exists($this, 'before_createView_callback')) {
219
            $this->{'before_createView_callback'}();
220
        }
221
222
        $body = self::$provider->createView($fields);
223
224
        if (method_exists($this, 'after_createView_callback')) {
225
            $this->{'after_createView_callback'}();
226
        }
227
228
        self::$logger->debug('<<createView');
229
230
        return $body;
231
    }
232
233
    /**
234
     * Renders a form to enable object editing.
235
     *
236
     * @param array $fields Hash array of fields to pass to the template
237
     *
238
     * @return string
239
     *
240
     * @since 1.0
241
     */
242
    public function editView($fields = array())
243
    {
244
        self::$logger->debug('>>editView(fields=['.var_export($fields, true).'])');
245
246
        if (method_exists($this, 'before_editView_callback')) {
247
            $this->{'before_editView_callback'}();
248
        }
249
250
        $body = self::$provider->editView($fields);
251
252
        if (method_exists($this, 'after_editView_callback')) {
253
            $this->{'after_editView_callback'}();
254
        }
255
256
        self::$logger->debug('<<editView');
257
258
        return $body;
259
    }
260
261
    /**
262
     * Renders the list view.
263
     *
264
     * @param array $fields Hash array of fields to pass to the template
265
     *
266
     * @return string
267
     *
268
     * @since 1.0
269
     */
270
    public function listView($fields = array())
271
    {
272
        self::$logger->debug('>>listView(fields=['.var_export($fields, true).'])');
273
274
        if (method_exists($this, 'before_listView_callback')) {
275
            $this->{'before_listView_callback'}();
276
        }
277
278
        $body = self::$provider->listView($fields);
279
280
        if (method_exists($this, 'after_listView_callback')) {
281
            $this->{'after_listView_callback'}();
282
        }
283
284
        self::$logger->debug('<<listView');
285
286
        return $body;
287
    }
288
289
    /**
290
     * Renders a detailed view of the object (read-only).
291
     *
292
     * @param array $fields Hash array of fields to pass to the template
293
     *
294
     * @return string
295
     *
296
     * @since 1.0
297
     */
298
    public function detailedView($fields = array())
299
    {
300
        self::$logger->debug('>>detailedView(fields=['.var_export($fields, true).'])');
301
302
        if (method_exists($this, 'before_detailedView_callback')) {
303
            $this->{'before_detailedView_callback'}();
304
        }
305
306
        $body = self::$provider->detailedView($fields);
307
308
        if (method_exists($this, 'after_detailedView_callback')) {
309
            $this->{'after_detailedView_callback'}();
310
        }
311
312
        self::$logger->debug('<<detailedView');
313
314
        return $body;
315
    }
316
317
    /**
318
     * Renders the admin view for the business object screen.
319
     *
320
     * @param array $fields Hash array of fields to pass to the template
321
     *
322
     * @return string
323
     *
324
     * @since 1.0
325
     */
326
    public function adminView($fields = array())
327
    {
328
        self::$logger->debug('>>adminView(fields=['.var_export($fields, true).'])');
329
330
        if (method_exists($this, 'before_adminView_callback')) {
331
            $this->{'before_adminView_callback'}();
332
        }
333
334
        $body = self::$provider->adminView($fields);
335
336
        if (method_exists($this, 'after_adminView_callback')) {
337
            $this->{'after_adminView_callback'}();
338
        }
339
340
        self::$logger->debug('<<adminView');
341
342
        return $body;
343
    }
344
345
    /**
346
     * Method to render the page header content.
347
     *
348
     * @param \Alpha\Controller\Controller $controller
349
     *
350
     * @return string
351
     *
352
     * @throws \Alpha\Exception\IllegalArguementException
353
     *
354
     * @since 1.0
355
     */
356
    public static function displayPageHead($controller)
357
    {
358
        if (self::$logger == null) {
359
            self::$logger = new Logger('View');
360
        }
361
        self::$logger->debug('>>displayPageHead(controller=['.var_export($controller, true).'])');
362
363
        if (method_exists($controller, 'before_displayPageHead_callback')) {
364
            $controller->{'before_displayPageHead_callback'}();
365
        }
366
367
        $config = ConfigProvider::getInstance();
368
369
        if (!self::$provider instanceof RendererProviderInterface) {
370
            self::setProvider($config->get('app.renderer.provider.name'));
371
        }
372
373
        $provider = self::$provider;
374
        $header = $provider::displayPageHead($controller);
375
376
        if (method_exists($controller, 'after_displayPageHead_callback')) {
377
            $header .= $controller->{'after_displayPageHead_callback'}();
378
        }
379
380
        self::$logger->debug('<<displayPageHead ['.$header.']');
381
382
        return $header;
383
    }
384
385
    /**
386
     * Method to render the page footer content.
387
     *
388
     * @param \Alpha\Controller\Controller $controller
389
     *
390
     * @return string
391
     *
392
     * @since 1.0
393
     */
394
    public static function displayPageFoot($controller)
395
    {
396
        if (self::$logger == null) {
397
            self::$logger = new Logger('View');
398
        }
399
400
        self::$logger->debug('>>displayPageFoot(controller=['.get_class($controller).'])');
401
402
        $config = ConfigProvider::getInstance();
403
404
        $footer = '';
405
406
        if (method_exists($controller, 'before_displayPageFoot_callback')) {
407
            $footer .= $controller->before_displayPageFoot_callback();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Controller\Controller as the method before_displayPageFoot_callback() does only exist in the following sub-classes of Alpha\Controller\Controller: Alpha\Controller\ActiveRecordController, Alpha\Controller\ArticleController, Alpha\Controller\DEnumController, Alpha\Controller\LoginController, Alpha\Controller\SearchController, Alpha\Controller\SequenceController, Alpha\Controller\TagController. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
408
        }
409
410
        if (!self::$provider instanceof RendererProviderInterface) {
411
            self::setProvider($config->get('app.renderer.provider.name'));
412
        }
413
414
        $provider = self::$provider;
415
        $footer .= $provider::displayPageFoot($controller);
416
417
        if (method_exists($controller, 'after_displayPageFoot_callback')) {
418
            $footer .= $controller->{'after_displayPageFoot_callback'}();
419
        }
420
421
        self::$logger->debug('<<displayPageFoot ['.$footer.']');
422
423
        return $footer;
424
    }
425
426
    /**
427
     * Method for rendering the pagination links.
428
     *
429
     * @param \Alpha\Controller\Controller $controller
430
     *
431
     * @return string
432
     *
433
     * @since 3.0
434
     */
435
    public static function displayPageLinks($controller)
436
    {
437
        $config = ConfigProvider::getInstance();
438
439
        $html = '';
440
        $recordCount = $controller->getRecordCount();
0 ignored issues
show
Bug introduced by
The method getRecordCount() does not exist on Alpha\Controller\Controller. Did you maybe mean getRecord()?

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...
441
        $start = $controller->getStart();
0 ignored issues
show
Bug introduced by
The method getStart() does not exist on Alpha\Controller\Controller. Did you maybe mean getStartTime()?

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...
442
        $limit = $controller->getLimit();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Controller\Controller as the method getLimit() does only exist in the following sub-classes of Alpha\Controller\Controller: Alpha\Controller\ActiveRecordController, Alpha\Controller\ArticleController, Alpha\Controller\DEnumController, Alpha\Controller\SequenceController, Alpha\Controller\TagController. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
443
444
        // the index of the last record displayed on this page
445
        $last = $start+$config->get('app.list.page.amount');
446
447
        // ensure that the last index never overruns the total record count
448
        if ($last > $recordCount) {
449
            $last = $recordCount ;
450
        }
451
452
        // render a message for an empty list
453
        if ($recordCount  > 0) {
454
            $html .= '<ul class="pagination">';
455
        } else {
456
            $html .= '<p align="center">The list is empty.&nbsp;&nbsp;</p>';
457
458
            return $html;
459
        }
460
461
        // render "Previous" link
462
        if ($start  > 0) {
463
            // handle secure URLs
464
            if ($controller->getRequest()->getParam('token', null) != null) {
465
                $url = FrontController::generateSecureURL('act=Alpha\Controller\ActiveRecordController&ActiveRecordType='.$controller->getRequest()->getParam('ActiveRecordType').'&start='.($start-$controller->getLimit()).'&limit='.$limit);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Controller\Controller as the method getLimit() does only exist in the following sub-classes of Alpha\Controller\Controller: Alpha\Controller\ActiveRecordController, Alpha\Controller\ArticleController, Alpha\Controller\DEnumController, Alpha\Controller\SequenceController, Alpha\Controller\TagController. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
466
            } else {
467
                $url = '/records/'.urlencode($controller->getRequest()->getParam('ActiveRecordType')).'/'.($start-$limit).'/'.$limit;
468
            }
469
            $html .= '<li><a href="'.$url.'">&lt;&lt;-Previous</a></li>';
470
        } elseif ($recordCount  > $limit) {
471
            $html .= '<li class="disabled"><a href="#">&lt;&lt;-Previous</a></li>';
472
        }
473
474
        // render the page index links
475
        if ($recordCount  > $limit) {
476
            $page = 1;
477
478
            for ($i = 0; $i < $recordCount ; $i += $limit) {
479
                if ($i != $start) {
480
                    // handle secure URLs
481
                    if ($controller->getRequest()->getParam('token', null) != null) {
482
                        $url = FrontController::generateSecureURL('act=Alpha\Controller\ActiveRecordController&ActiveRecordType='.$controller->getRequest()->getParam('ActiveRecordType').'&start='.$i.'&limit='.$limit);
483
                    } else {
484
                        $url = '/records/'.urlencode($controller->getRequest()->getParam('ActiveRecordType')).'/'.$i.'/'.$limit;
485
                    }
486
                    $html .= '<li><a href="'.$url.'">'.$page.'</a></li>';
487
                } elseif ($recordCount  > $limit) { // render an anchor for the current page
488
                    $html .= '<li class="active"><a href="#">'.$page.'</a></li>';
489
                }
490
491
                ++$page;
492
            }
493
        }
494
495
        // render "Next" link
496
        if ($recordCount  > $last) {
497
            // handle secure URLs
498
            if ($controller->getRequest()->getParam('token', null) != null) {
499
                $url = FrontController::generateSecureURL('act=Alpha\Controller\ActiveRecordController&ActiveRecordType='.$controller->getRequest()->getParam('ActiveRecordType').'&start='.($start+$limit).'&limit='.$limit);
500
            } else {
501
                $url = '/records/'.urlencode($controller->getRequest()->getParam('ActiveRecordType')).'/'.($start+$limit.'/'.$limit);
502
            }
503
            $html .= '<li><a href="'.$url.'">Next-&gt;&gt;</a></li>';
504
        } elseif ($recordCount  > $limit) {
505
            $html .= '<li class="disabled"><a href="#">Next-&gt;&gt;</a></li>';
506
        }
507
508
        $html .= '</ul>';
509
510
        return $html;
511
    }
512
513
    /**
514
     * Renders the content for an update (e.g. successful save) message.
515
     *
516
     * @param string $message
517
     *
518
     * @return string
519
     *
520
     * @since 1.0
521
     */
522
    public static function displayUpdateMessage($message)
523
    {
524
        if (self::$logger == null) {
525
            self::$logger = new Logger('View');
526
        }
527
        self::$logger->debug('>>displayUpdateMessage(message=['.$message.'])');
528
529
        $config = ConfigProvider::getInstance();
530
531
        if (!self::$provider instanceof RendererProviderInterface) {
532
            self::setProvider($config->get('app.renderer.provider.name'));
533
        }
534
535
        $provider = self::$provider;
536
        $message = $provider::displayUpdateMessage($message);
537
538
        self::$logger->debug('<<displayUpdateMessage ['.$message.']');
539
540
        return $message;
541
    }
542
543
    /**
544
     * Renders the content for an error (e.g. save failed) message.
545
     *
546
     * @param string $message
547
     *
548
     * @return string
549
     *
550
     * @since 1.0
551
     */
552
    public static function displayErrorMessage($message)
553
    {
554
        if (self::$logger == null) {
555
            self::$logger = new Logger('View');
556
        }
557
        self::$logger->debug('>>displayErrorMessage(message=['.$message.'])');
558
559
        $config = ConfigProvider::getInstance();
560
561
        if (!self::$provider instanceof RendererProviderInterface) {
562
            self::setProvider($config->get('app.renderer.provider.name'));
563
        }
564
565
        $provider = self::$provider;
566
        $message = $provider::displayErrorMessage($message);
567
568
        self::$logger->debug('<<displayErrorMessage ['.$message.']');
569
570
        return $message;
571
    }
572
573
    /**
574
     * Renders an error page with the supplied error code (typlically a HTTP code) and a message.
575
     *
576
     * @param string $code
577
     * @param string $message
578
     *
579
     * @return string
580
     *
581
     * @since 1.0
582
     */
583
    public static function renderErrorPage($code, $message)
584
    {
585
        if (self::$logger == null) {
586
            self::$logger = new Logger('View');
587
        }
588
        self::$logger->debug('>>renderErrorPage(code=['.$code.'],message=['.$message.'])');
589
590
        $config = ConfigProvider::getInstance();
591
592
        if (!self::$provider instanceof RendererProviderInterface) {
593
            self::setProvider($config->get('app.renderer.provider.name'));
594
        }
595
596
        $provider = self::$provider;
597
        $message = $provider::renderErrorPage($code, $message);
598
599
        self::$logger->debug('<<renderErrorPage ['.$message.']');
600
601
        return $message;
602
    }
603
604
    /**
605
     * Method to render a hidden HTML form for posting the ID of an object to be deleted.
606
     *
607
     * @param string $URI The URI that the form will point to
608
     *
609
     * @return string
610
     *
611
     * @since 1.0
612
     */
613
    public static function renderDeleteForm($URI)
614
    {
615
        if (self::$logger == null) {
616
            self::$logger = new Logger('View');
617
        }
618
        self::$logger->debug('>>renderDeleteForm()');
619
620
        $config = ConfigProvider::getInstance();
621
622
        if (!self::$provider instanceof RendererProviderInterface) {
623
            self::setProvider($config->get('app.renderer.provider.name'));
624
        }
625
626
        $provider = self::$provider;
627
        $html = $provider::renderDeleteForm($URI);
628
629
        self::$logger->debug('<<renderDeleteForm ['.$html.']');
630
631
        return $html;
632
    }
633
634
    /**
635
     * Method to render a HTML form with two hidden, hashed (MD5) form fields to be used as
636
     * a check to ensure that a post to the controller is being sent from the same server
637
     * as hosting it.
638
     *
639
     * @return string
640
     *
641
     * @since 1.0
642
     */
643
    public static function renderSecurityFields()
644
    {
645
        if (self::$logger == null) {
646
            self::$logger = new Logger('View');
647
        }
648
        self::$logger->debug('>>renderSecurityFields()');
649
650
        $config = ConfigProvider::getInstance();
651
652
        if (!self::$provider instanceof RendererProviderInterface) {
653
            self::setProvider($config->get('app.renderer.provider.name'));
654
        }
655
656
        $provider = self::$provider;
657
        $html = $provider::renderSecurityFields();
658
659
        self::$logger->debug('<<renderSecurityFields ['.$html.']');
660
661
        return $html;
662
    }
663
664
    /**
665
     * Method to render the default Integer HTML.
666
     *
667
     * @param string $name      The field name
668
     * @param string $label     The label to apply to the field
669
     * @param string $mode      The field mode (create/edit/view)
670
     * @param string $value     The field value (optional)
671
     *
672
     * @return string
673
     *
674
     * @since 1.0
675
     */
676
    public function renderIntegerField($name, $label, $mode, $value = '')
677
    {
678
        self::$logger->debug('>>renderIntegerField(name=['.$name.'], label=['.$label.'], mode=['.$mode.'], value=['.$value.']');
679
680
        $html = self::$provider->renderIntegerField($name, $label, $mode, $value);
681
682
        self::$logger->debug('<<renderIntegerField ['.$html.']');
683
684
        return $html;
685
    }
686
687
    /**
688
     * Method to render the default Double HTML.
689
     *
690
     * @param string $name      The field name
691
     * @param string $label     The label to apply to the field
692
     * @param string $mode      The field mode (create/edit/view)
693
     * @param string $value     The field value (optional)
694
     *
695
     * @return string
696
     *
697
     * @since 1.0
698
     */
699
    public function renderDoubleField($name, $label, $mode, $value = '')
700
    {
701
        self::$logger->debug('>>renderDoubleField(name=['.$name.'], label=['.$label.'], mode=['.$mode.'], value=['.$value.'])');
702
703
        $html = self::$provider->renderDoubleField($name, $label, $mode, $value);
704
705
        self::$logger->debug('<<renderDoubleField ['.$html.']');
706
707
        return $html;
708
    }
709
710
    /**
711
     * Method to render the default Boolean HTML.
712
     *
713
     * @param string $name      The field name
714
     * @param string $label     The label to apply to the field
715
     * @param string $mode      The field mode (create/edit/view)
716
     * @param string $value     The field value (optional)
717
     *
718
     * @return string
719
     *
720
     * @since 1.0
721
     */
722
    public function renderBooleanField($name, $label, $mode, $value = '')
723
    {
724
        self::$logger->debug('>>renderBooleanField(name=['.$name.'], label=['.$label.'], mode=['.$mode.'], value=['.$value.'])');
725
726
        $html = self::$provider->renderBooleanField($name, $label, $mode, $value);
727
728
        self::$logger->debug('<<renderBooleanField ['.$html.']');
729
730
        return $html;
731
    }
732
733
    /**
734
     * Method to render the default Enum HTML.
735
     *
736
     * @param string $name      The field name
737
     * @param string $label     The label to apply to the field
738
     * @param string $mode      The field mode (create/edit/view)
739
     * @param array  $options   The Enum options
740
     * @param string $value     The field value (optional)
741
     *
742
     * @return string
743
     *
744
     * @since 1.0
745
     */
746
    public function renderEnumField($name, $label, $mode, $options, $value = '')
747
    {
748
        self::$logger->debug('>>renderEnumField(name=['.$name.'], label=['.$label.'], mode=['.$mode.'], value=['.$value.'])');
749
750
        $html = self::$provider->renderEnumField($name, $label, $mode, $options, $value);
751
752
        self::$logger->debug('<<renderEnumField ['.$html.']');
753
754
        return $html;
755
    }
756
757
    /**
758
     * Method to render the default DEnum HTML.
759
     *
760
     * @param string $name      The field name
761
     * @param string $label     The label to apply to the field
762
     * @param string $mode      The field mode (create/edit/view)
763
     * @param array  $options   The DEnum options
764
     * @param string $value     The field value (optional)
765
     *
766
     * @return string
767
     *
768
     * @since 1.0
769
     */
770
    public function renderDEnumField($name, $label, $mode, $options, $value = '')
771
    {
772
        self::$logger->debug('>>renderDEnumField(name=['.$name.'], label=['.$label.'], mode=['.$mode.'], value=['.$value.'])');
773
774
        $html = self::$provider->renderDEnumField($name, $label, $mode, $options, $value);
775
776
        self::$logger->debug('<<renderDEnumField ['.$html.']');
777
778
        return $html;
779
    }
780
781
    /**
782
     * Method to render the default field HTML when type is not known.
783
     *
784
     * @param string $name      The field name
785
     * @param string $label     The label to apply to the field
786
     * @param string $mode      The field mode (create/edit/view)
787
     * @param string $value     The field value (optional)
788
     *
789
     * @return string
790
     *
791
     * @since 1.0
792
     */
793
    public function renderDefaultField($name, $label, $mode, $value = '')
794
    {
795
        self::$logger->debug('>>renderDefaultField(name=['.$name.'], label=['.$label.'], mode=['.$mode.'], value=['.$value.'])');
796
797
        $html = self::$provider->renderDefaultField($name, $label, $mode, $value);
798
799
        self::$logger->debug('<<renderDefaultField ['.$html.']');
800
801
        return $html;
802
    }
803
804
    /**
805
     * render the default Text HTML.
806
     *
807
     * @param string $name      The field name
808
     * @param string $label     The label to apply to the field
809
     * @param string $mode      The field mode (create/edit/view)
810
     * @param string $value     The field value (optional)
811
     *
812
     * @return string
813
     *
814
     * @since 1.0
815
     */
816
    public function renderTextField($name, $label, $mode, $value = '')
817
    {
818
        self::$logger->debug('>>renderTextField(name=['.$name.'], label=['.$label.'], mode=['.$mode.'], value=['.$value.'])');
819
820
        $html = self::$provider->renderTextField($name, $label, $mode, $value);
821
822
        self::$logger->debug('<<renderTextField ['.$html.']');
823
824
        return $html;
825
    }
826
827
    /**
828
     * render the default Relation HTML.
829
     *
830
     * @param string $name      The field name
831
     * @param string $label     The label to apply to the field
832
     * @param string $mode      The field mode (create/edit/view)
833
     * @param string $value     The field value (optional)
834
     * @param bool   $expanded  Render the related fields in expanded format or not (optional)
835
     * @param bool   $buttons   Render buttons for expanding/contacting the related fields (optional)
836
     *
837
     * @return string
838
     *
839
     * @since 1.0
840
     */
841
    public function renderRelationField($name, $label, $mode, $value = '', $expanded = false, $buttons = true)
842
    {
843
        self::$logger->debug('>>renderRelationField(name=['.$name.'], label=['.$label.'], mode=['.$mode.'], value=['.$value.'], expanded=['.$expanded.'], buttons=['.$buttons.'])');
844
845
        $html = self::$provider->renderRelationField($name, $label, $mode, $value, $expanded, $buttons);
846
847
        self::$logger->debug('<<renderRelationField ['.$html.']');
848
849
        return $html;
850
    }
851
852
    /**
853
     * Renders all fields for the current Record in edit/create/view mode.
854
     *
855
     * @param string $mode           (view|edit|create)
856
     * @param array  $filterFields   Optional list of field names to exclude from rendering
857
     * @param array  $readOnlyFields Optional list of fields to render in a readonly fashion when rendering in create or edit mode
858
     *
859
     * @return string
860
     *
861
     * @since 1.0
862
     */
863
    public function renderAllFields($mode, $filterFields = array(), $readOnlyFields = array())
864
    {
865
        self::$logger->debug('>>renderAllFields(mode=['.$mode.'], filterFields=['.var_export($filterFields, true).'], readOnlyFields=['.var_export($readOnlyFields, true).'])');
866
867
        $html = self::$provider->renderAllFields($mode, $filterFields, $readOnlyFields);
868
869
        self::$logger->debug('<<renderAllFields ['.$html.']');
870
871
        return $html;
872
    }
873
874
    /**
875
     * Loads a template for the Record specified if one exists.  Lower level custom templates
876
     * take precedence.
877
     *
878
     * @param \Alpha\Model\ActiveRecord $record
879
     * @param string                   $mode
880
     * @param array                    $fields
881
     *
882
     * @return string
883
     *
884
     * @since 1.0
885
     *
886
     * @throws \Alpha\Exception\IllegalArguementException
887
     */
888
    public static function loadTemplate($record, $mode, $fields = array())
889
    {
890
        self::$logger->debug('>>loadTemplate(Record=['.var_export($record, true).'], mode=['.$mode.'], fields=['.var_export($fields, true).'])');
891
892
        $config = ConfigProvider::getInstance();
893
894
        // for each Record property, create a local variable holding its value
895
        $reflection = new ReflectionClass(get_class($record));
896
        $properties = $reflection->getProperties();
897
898
        foreach ($properties as $propObj) {
899
            $propName = $propObj->name;
900
901
            if ($propName != 'logger' && !$propObj->isPrivate()) {
902
                $prop = $record->getPropObject($propName);
903
                if ($prop instanceof DEnum) {
904
                    ${$propName} = $prop->getDisplayValue();
905
                } else {
906
                    ${$propName} = $record->get($propName);
907
                }
908
            }
909
        }
910
911
        // loop over the $fields array and create a local variable for each key value
912
        foreach (array_keys($fields) as $fieldName) {
913
            ${$fieldName} = $fields[$fieldName];
914
        }
915
916
        $filename = $mode.'.phtml';
917
        $class = new ReflectionClass($record);
918
        $className = $class->getShortname();
919
920
        $customPath = $config->get('app.root').'src/View/Html/Templates/'.$className.'/'.$filename;
921
        $defaultPath1 = $config->get('app.root').'vendor/alphadevx/alpha/Alpha/View/Renderer/Html/Templates/'.$className.'/'.$filename;
922
        $defaultPath2 = $config->get('app.root').'vendor/alphadevx/alpha/Alpha/View/Renderer/Html/Templates/'.$filename;
923
        $defaultPath3 = $config->get('app.root').'Alpha/View/Renderer/Html/Templates/'.$className.'/'.$filename;
924
        $defaultPath4 = $config->get('app.root').'Alpha/View/Renderer/Html/Templates/'.$filename;
925
926
        // Check to see if a custom template exists for this record, and if it does load that
927
        if (file_exists($customPath)) {
928
            self::$logger->debug('Loading template ['.$customPath.']');
929
            ob_start();
930
            require $customPath;
931
            $html = ob_get_clean();
932
933
            self::$logger->debug('<<loadTemplate');
934
            return $html;
935
        } elseif (file_exists($defaultPath1)) {
936
            self::$logger->debug('Loading template ['.$defaultPath1.']');
937
            ob_start();
938
            require $defaultPath1;
939
            $html = ob_get_clean();
940
941
            self::$logger->debug('<<loadTemplate');
942
            return $html;
943
        } elseif (file_exists($defaultPath2)) {
944
            self::$logger->debug('Loading template ['.$defaultPath2.']');
945
            ob_start();
946
            require $defaultPath2;
947
            $html = ob_get_clean();
948
949
            self::$logger->debug('<<loadTemplate');
950
            return $html;
951
        } elseif (file_exists($defaultPath3)) {
952
            self::$logger->debug('Loading template ['.$defaultPath3.']');
953
            ob_start();
954
            require $defaultPath3;
955
            $html = ob_get_clean();
956
957
            self::$logger->debug('<<loadTemplate');
958
            return $html;
959
        } elseif (file_exists($defaultPath4)) {
960
            self::$logger->debug('Loading template ['.$defaultPath4.']');
961
            ob_start();
962
            require $defaultPath4;
963
            $html = ob_get_clean();
964
965
            self::$logger->debug('<<loadTemplate');
966
            return $html;
967
        } else {
968
            self::$logger->debug('<<loadTemplate');
969
            throw new IllegalArguementException('No ['.$mode.'] HTML template found for class ['.$className.']');
970
        }
971
    }
972
973
    /**
974
     * Loads a template fragment from the Renderer/[type]/Fragments/[filename.ext] location.
975
     *
976
     * @param string $type     Currently only html supported, later json and xml.
977
     * @param string $fileName The name of the fragment file
978
     * @param array  $fields   A hash array of field values to pass to the template fragment.
979
     *
980
     * @return string
981
     *
982
     * @since 1.2
983
     *
984
     * @throws \Alpha\Exception\IllegalArguementException
985
     */
986
    public static function loadTemplateFragment($type, $fileName, $fields = array())
987
    {
988
        if (self::$logger == null) {
989
            self::$logger = new Logger('View');
990
        }
991
        self::$logger->debug('>>loadTemplateFragment(type=['.$type.'], fileName=['.$fileName.'], fields=['.var_export($fields, true).'])');
992
993
        $config = ConfigProvider::getInstance();
994
995
        // loop over the $fields array and create a local variable for each key value
996
        foreach (array_keys($fields) as $fieldName) {
997
            ${$fieldName} = $fields[$fieldName];
998
        }
999
1000
        $customPath = $config->get('app.root').'src/View/'.ucfirst($type).'/Fragments/'.$fileName;
1001
        $defaultPath1 = $config->get('app.root').'vendor/alphadevx/alpha/Alpha/View/Renderer/'.ucfirst($type).'/Fragments/'.$fileName;
1002
        $defaultPath2 = $config->get('app.root').'Alpha/View/Renderer/'.ucfirst($type).'/Fragments/'.$fileName;
1003
1004
        // Check to see if a custom template exists for this record, and if it does load that
1005
        if (file_exists($customPath)) {
1006
            self::$logger->debug('Loading template ['.$customPath.']');
1007
            ob_start();
1008
            require $customPath;
1009
            $html = ob_get_clean();
1010
1011
            self::$logger->debug('<<loadTemplateFragment');
1012
            return $html;
1013
        } elseif (file_exists($defaultPath1)) {
1014
            self::$logger->debug('Loading template ['.$defaultPath1.']');
1015
            ob_start();
1016
            require $defaultPath1;
1017
            $html = ob_get_clean();
1018
1019
            self::$logger->debug('<<loadTemplateFragment');
1020
            return $html;
1021
        } elseif (file_exists($defaultPath2)) {
1022
            self::$logger->debug('Loading template ['.$defaultPath2.']');
1023
            ob_start();
1024
            require $defaultPath2;
1025
            $html = ob_get_clean();
1026
1027
            self::$logger->debug('<<loadTemplateFragment');
1028
            return $html;
1029
        } else {
1030
            self::$logger->debug('<<loadTemplateFragment');
1031
            throw new IllegalArguementException('Template fragment not found in ['.$customPath.'] or ['.$defaultPath1.'] or ['.$defaultPath2.']!');
1032
        }
1033
    }
1034
1035
    /**
1036
     * Enables you to set an explicit type of RendererProviderInterface implementation to use for rendering the records
1037
     * attached to this view.
1038
     *
1039
     * @param string $ProviderClassName The name of the RendererProviderInterface implementation to use in this view object
1040
     * @param string $acceptHeader      Optional pass the HTTP Accept header to select the correct renderer provider.
1041
     *
1042
     * @since 1.2
1043
     *
1044
     * @throws \Alpha\Exception\IllegalArguementException
1045
     */
1046
    public static function setProvider($ProviderClassName, $acceptHeader = null)
1047
    {
1048
        if ($ProviderClassName == 'auto') {
1049
            $ProviderClassName = 'Alpha\View\Renderer\Html\RendererProviderHTML';
1050
1051
            if ($acceptHeader == 'application/json') {
1052
                $ProviderClassName = 'Alpha\View\Renderer\Json\RendererProviderJSON';
1053
            }
1054
1055
            self::$provider = ServiceFactory::getInstance($ProviderClassName, 'Alpha\View\Renderer\RendererProviderInterface');
1056
        } else {
1057
            if (class_exists($ProviderClassName)) {
1058
                $provider = new $ProviderClassName();
1059
1060
                if ($provider instanceof RendererProviderInterface) {
1061
                    self::$provider = ServiceFactory::getInstance($ProviderClassName, 'Alpha\View\Renderer\RendererProviderInterface');
1062
                } else {
1063
                    throw new IllegalArguementException('The provider class ['.$ProviderClassName.'] does not implement the RendererProviderInterface interface!');
1064
                }
1065
            } else {
1066
                throw new IllegalArguementException('The provider class ['.$ProviderClassName.'] does not exist!');
1067
            }
1068
        }
1069
    }
1070
1071
    /**
1072
     * Get the current view renderer provider.
1073
     *
1074
     * @return \Alpha\View\Renderer\RendererProviderInterface
1075
     *
1076
     * @since 2.0
1077
     */
1078
    public static function getProvider()
1079
    {
1080
        if (self::$provider instanceof RendererProviderInterface) {
1081
            return self::$provider;
1082
        } else {
1083
            $config = ConfigProvider::getInstance();
1084
1085
            self::$provider = ServiceFactory::getInstance($config->get('app.renderer.provider.name'), 'Alpha\View\Renderer\RendererProviderInterface');
1086
1087
            return self::$provider;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return self::$provider; (stdClass) is incompatible with the return type documented by Alpha\View\View::getProvider of type Alpha\View\Renderer\RendererProviderInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1088
        }
1089
    }
1090
}
1091