Completed
Push — master ( bc3898...bd8c0c )
by Mihail
04:18
created

App   C

Complexity

Total Complexity 35

Size/Duplication

Total Lines 250
Duplicated Lines 4 %

Coupling/Cohesion

Components 1
Dependencies 20

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 35
c 4
b 0
f 0
lcom 1
cbo 20
dl 10
loc 250
rs 5.8235

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A factory() 0 4 1
B nativeStaticLinks() 0 23 4
C dynamicStaticLinks() 0 26 11
F run() 10 95 18

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Ffcms\Core;
4
5
use Ffcms\Core\Arch\View;
6
use Ffcms\Core\Cache\MemoryObject;
7
use Ffcms\Core\Debug\DebugMeasure;
8
use Ffcms\Core\Debug\Manager as Debug;
9
use Ffcms\Core\Exception\ForbiddenException;
10
use Ffcms\Core\Exception\JsonException;
11
use Ffcms\Core\Exception\NativeException;
12
use Ffcms\Core\Exception\NotFoundException;
13
use Ffcms\Core\Exception\SyntaxException;
14
use Ffcms\Core\Helper\Security;
15
use Ffcms\Core\Helper\Type\Obj;
16
use Ffcms\Core\Helper\Type\Str;
17
use Ffcms\Core\I18n\Translate;
18
use Ffcms\Core\Managers\BootManager;
19
use Ffcms\Core\Managers\CronManager;
20
use Ffcms\Core\Managers\EventManager;
21
use Ffcms\Core\Network\Request;
22
use Ffcms\Core\Network\Response;
23
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
24
25
/**
26
 * Class App. Provide later static callbacks as entry point from any places of ffcms.
27
 * @package Ffcms\Core
28
 */
29
class App
30
{
31
    use DebugMeasure;
32
33
    /** @var \Ffcms\Core\Network\Request */
34
    public static $Request;
35
36
    /** @var \Ffcms\Core\Properties */
37
    public static $Properties;
38
39
    /** @var \Ffcms\Core\Network\Response */
40
    public static $Response;
41
42
    /** @var \Ffcms\Core\Alias */
43
    public static $Alias;
44
45
    /** @var \Ffcms\Core\Arch\View */
46
    public static $View;
47
48
    /** @var \Ffcms\Core\Debug\Manager|null */
49
    public static $Debug;
50
51
    /** @var \Ffcms\Core\Helper\Security */
52
    public static $Security;
53
54
    /** @var \Ffcms\Core\I18n\Translate */
55
    public static $Translate;
56
57
    /** @var \Ffcms\Core\Interfaces\iUser */
58
    public static $User;
59
60
    /** @var \Symfony\Component\HttpFoundation\Session\Session */
61
    public static $Session;
62
63
    /** @var \Illuminate\Database\Capsule\Manager */
64
    public static $Database;
65
66
    /** @var \Ffcms\Core\Cache\MemoryObject */
67
    public static $Memory;
68
69
    /** @var \Swift_Mailer */
70
    public static $Mailer;
71
72
    /** @var \Ffcms\Core\Interfaces\iCaptcha */
73
    public static $Captcha;
74
75
    /** @var FilesystemAdapter */
76
    public static $Cache;
77
78
    /** @var EventManager */
79
    public static $Event;
80
81
    /** @var CronManager */
82
    public static $Cron;
83
84
    private $_services;
85
    private $_loader;
86
87
    /**
88
     * App constructor. Build App entry-point instance
89
     * @param array|null $services
90
     * @param bool $loader
91
     * @throws \Ffcms\Core\Exception\NativeException
92
     * @throws \InvalidArgumentException
93
     */
94
    public function __construct(array $services = null, $loader = false)
95
    {
96
        // pass initialization data inside
97
        $this->_services = $services;
98
        $this->_loader = $loader;
99
        // initialize service links
100
        $this->nativeStaticLinks();
101
        // notify load timeline
102
        $this->dynamicStaticLinks();
103
        // Initialize boot manager. This manager allow to auto-execute 'static boot()' methods in apps and widgets
104
        $bootManager = new BootManager($this->_loader);
105
        $bootManager->run();
106
    }
107
108
    /**
109
     * Factory method builder for app entry point
110
     * @param array|null $services
111
     * @param bool $loader
112
     * @return App
113
     * @throws \InvalidArgumentException
114
     */
115
    public static function factory(array $services = null, $loader = false): self
116
    {
117
        return new self($services, $loader);
118
    }
119
120
    /**
121
     * Prepare static symbolic links for app services
122
     * @throws \InvalidArgumentException
123
     */
124
    private function nativeStaticLinks(): void
125
    {
126
        // initialize memory and properties controllers
127
        self::$Memory = MemoryObject::instance();
128
        self::$Properties = new Properties();
129
        // initialize debugger
130
        if (isset($this->_services['Debug']) && $this->_services['Debug'] === true && Debug::isEnabled()) {
131
            self::$Debug = new Debug();
132
            $this->startMeasure(__METHOD__);
133
        }
134
        // prepare request data
135
        self::$Request = Request::createFromGlobals();
136
        // initialize response, securty translate and other workers
137
        self::$Security = new Security();
138
        self::$Response = new Response();
139
        self::$View = new View();
140
        self::$Translate = new Translate();
141
        self::$Alias = new Alias();
142
        self::$Event = new EventManager();
143
        self::$Cron = new CronManager();
144
        // stop debug timeline
145
        $this->stopMeasure(__METHOD__);
146
    }
147
148
    /**
149
     * Prepare dynamic static links from object configurations as anonymous functions
150
     * @throws NativeException
151
     */
152
    private function dynamicStaticLinks(): void
153
    {
154
        $this->startMeasure(__METHOD__);
155
156
        /** @var array $objects */
157
        $objects = App::$Properties->getAll('object');
158
        if (!Obj::isArray($objects)) {
159
            throw new NativeException('Object configurations is not loaded: /Private/Config/Object.php');
160
        }
161
162
        // each all objects as service_name => service_instance()
163
        foreach ($objects as $name => $instance) {
164
            // check if definition of object is exist and services list contains it or is null to auto build
165
            if (property_exists(get_called_class(), $name) && $instance instanceof \Closure && (isset($this->_services[$name]) || $this->_services === null)) {
166
                if ($this->_services[$name] === true || $this->_services === null) { // initialize from configs
167
                    self::${$name} = $instance();
168
                } elseif (is_callable($this->_services[$name])) { // raw initialization from App::run()
169
                    self::${$name} = $this->_services[$name]();
170
                }
171
            } elseif (Str::startsWith('_', $name)) { // just anonymous callback without entry-point
172
                @call_user_func($instance);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
173
            }
174
        }
175
176
        $this->stopMeasure(__METHOD__);
177
    }
178
179
    /**
180
     * Run applications and display output
181
     */
182
    public function run()
183
    {
184
        $this->startMeasure(__METHOD__);
185
186
        $html = null;
187
        // lets try to get html full content to page render
188
        try {
189
            /** @var \Ffcms\Core\Arch\Controller $callClass */
190
            $callClass = null;
191
            $callMethod = 'action' . self::$Request->getAction();
192
193
            // founded callback injection alias
194
            if (self::$Request->getCallbackAlias() !== false) {
195
                $cName = self::$Request->getCallbackAlias();
196 View Code Duplication
                if (class_exists($cName)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
197
                    $callClass = new $cName;
198
                } else {
199
                    throw new NotFoundException('Callback alias of class "' . App::$Security->strip_tags($cName) . '" is not founded');
0 ignored issues
show
Bug introduced by
It seems like $cName defined by self::$Request->getCallbackAlias() on line 195 can also be of type boolean; however, Ffcms\Core\Helper\Security::strip_tags() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
200
                }
201
            } else { // typical parsing of native apps
202
                $cName = '\Apps\Controller\\' . env_name . '\\' . self::$Request->getController();
203
204
                // try to initialize class object
205 View Code Duplication
                if (class_exists($cName)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
206
                    $callClass = new $cName;
207
                } else {
208
                    throw new NotFoundException('Application can not be runned. Initialized class not founded: ' . App::$Security->strip_tags($cName));
209
                }
210
            }
211
212
            // try to call method of founded callback class
213
            if (method_exists($callClass, $callMethod)) {
214
                $actionQuery = [];
215
                // prepare action params for callback
216
                if (!Str::likeEmpty(self::$Request->getID())) {
217
                    $actionQuery[] = self::$Request->getID();
218
                    if (!Str::likeEmpty(self::$Request->getAdd())) {
219
                        $actionQuery[] = self::$Request->getAdd();
220
                    }
221
                }
222
223
                // get controller method arguments count
224
                $reflection = new \ReflectionMethod($callClass, $callMethod);
225
                $argumentCount = 0;
226
                foreach ($reflection->getParameters() as $arg) {
227
                    if (!$arg->isOptional()) {
228
                        $argumentCount++;
229
                    }
230
                }
231
232
                // check method arguments count and current request count to prevent warnings
233
                if (count($actionQuery) < $argumentCount) {
234
                    throw new NotFoundException(__('Arguments for method %method% is not enough. Expected: %required%, got: %current%.', [
235
                        'method' => $callMethod,
236
                        'required' => $argumentCount,
237
                        'current' => count($actionQuery)
238
                    ]));
239
                }
240
241
                $this->startMeasure($cName . '::' . $callMethod);
242
                // make callback call to action in controller and get response
243
                $actionResponse = call_user_func_array([$callClass, $callMethod], $actionQuery);
244
                $this->stopMeasure($cName . '::' . $callMethod);
245
246
                if ($actionResponse !== null && !Str::likeEmpty($actionResponse)) {
247
                    // set response to controller property object
248
                    $callClass->setOutput($actionResponse);
249
                }
250
251
                // build full compiled output html data
252
                $html = $callClass->buildOutput();
253
            } else {
254
                throw new NotFoundException('Method "' . App::$Security->strip_tags($callMethod) . '()" not founded in "' . get_class($callClass) . '"');
255
            }
256
        } catch (NotFoundException $e) { // catch exceptions and set output
257
            $html = $e->display();
258
        } catch (ForbiddenException $e) {
259
            $html = $e->display();
260
        } catch (SyntaxException $e) {
261
            $html = $e->display();
262
        } catch (JsonException $e) {
263
            $html = $e->display();
264
        } catch (NativeException $e) {
265
            $html = $e->display();
266
        } catch (\Exception $e) { // catch all other exceptions
267
            $html = (new NativeException($e->getMessage()))->display();
268
        }
269
270
        // set full rendered content to response builder
271
        self::$Response->setContent($html);
272
        // echo full response to user via http foundation
273
        self::$Response->send();
274
275
        $this->stopMeasure(__METHOD__);
276
    }
277
278
}