Completed
Push — master ( 48735e...1bc6cd )
by Mihail
02:17
created

App   B

Complexity

Total Complexity 29

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 29
c 3
b 0
f 0
lcom 1
cbo 18
dl 0
loc 234
rs 7.3333

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A factory() 0 4 1
B loadNativeServices() 0 23 4
C loadDynamicServices() 0 26 11
F run() 0 80 12
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\Exception\TemplateException;
15
use Ffcms\Core\Helper\Mailer;
16
use Ffcms\Core\Helper\Security;
17
use Ffcms\Core\Helper\Type\Any;
18
use Ffcms\Core\Helper\Type\Obj;
19
use Ffcms\Core\Helper\Type\Str;
20
use Ffcms\Core\I18n\Translate;
21
use Ffcms\Core\Managers\BootManager;
22
use Ffcms\Core\Managers\CronManager;
23
use Ffcms\Core\Managers\EventManager;
24
use Ffcms\Core\Network\Request;
25
use Ffcms\Core\Network\Response;
26
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
27
28
/**
29
 * Class App. Provide later static callbacks as entry point from any places of ffcms.
30
 * @package Ffcms\Core
31
 */
32
class App
33
{
34
    use DebugMeasure;
35
36
    /** @var \Ffcms\Core\Network\Request */
37
    public static $Request;
38
39
    /** @var \Ffcms\Core\Properties */
40
    public static $Properties;
41
42
    /** @var \Ffcms\Core\Network\Response */
43
    public static $Response;
44
45
    /** @var \Ffcms\Core\Alias */
46
    public static $Alias;
47
48
    /** @var \Ffcms\Core\Arch\View */
49
    public static $View;
50
51
    /** @var \Ffcms\Core\Debug\Manager|null */
52
    public static $Debug;
53
54
    /** @var \Ffcms\Core\Helper\Security */
55
    public static $Security;
56
57
    /** @var \Ffcms\Core\I18n\Translate */
58
    public static $Translate;
59
60
    /** @var \Ffcms\Core\Interfaces\iUser|\Apps\ActiveRecord\User */
61
    public static $User;
62
63
    /** @var \Symfony\Component\HttpFoundation\Session\Session */
64
    public static $Session;
65
66
    /** @var \Illuminate\Database\Capsule\Manager */
67
    public static $Database;
68
69
    /** @var \Ffcms\Core\Cache\MemoryObject */
70
    public static $Memory;
71
72
    /** @var Mailer */
73
    public static $Mailer;
74
75
    /** @var \Ffcms\Core\Interfaces\iCaptcha */
76
    public static $Captcha;
77
78
    /** @var FilesystemAdapter */
79
    public static $Cache;
80
81
    /** @var EventManager */
82
    public static $Event;
83
84
    /** @var CronManager */
85
    public static $Cron;
86
87
    private $_services;
88
    private $_loader;
89
90
    /**
91
     * App constructor. Build App entry-point instance
92
     * @param array|null $services
93
     * @param bool $loader
94
     * @throws \Ffcms\Core\Exception\NativeException
95
     * @throws \InvalidArgumentException
96
     */
97
    public function __construct(array $services = null, $loader = false)
98
    {
99
        // pass initialization data inside
100
        $this->_services = $services;
101
        $this->_loader = $loader;
102
        // initialize service links
103
        $this->loadNativeServices();
104
        $this->loadDynamicServices();
105
        // Initialize boot manager. This manager allow to auto-execute 'static boot()' methods in apps and widgets
106
        $bootManager = new BootManager($this->_loader);
107
        $bootManager->run();
108
    }
109
110
    /**
111
     * Factory method builder for app entry point
112
     * @param array|null $services
113
     * @param bool $loader
114
     * @return App
115
     * @throws \InvalidArgumentException
116
     */
117
    public static function factory(array $services = null, $loader = false): self
118
    {
119
        return new self($services, $loader);
120
    }
121
122
    /**
123
     * Prepare native static symbolic links for app services
124
     * @throws \InvalidArgumentException
125
     */
126
    private function loadNativeServices(): void
127
    {
128
        // initialize memory and properties controllers
129
        self::$Memory = MemoryObject::instance();
130
        self::$Properties = new Properties();
131
        // initialize debugger
132
        if (isset($this->_services['Debug']) && $this->_services['Debug'] === true && Debug::isEnabled()) {
133
            self::$Debug = new Debug();
134
            $this->startMeasure(__METHOD__);
135
        }
136
        // prepare request data
137
        self::$Request = Request::createFromGlobals();
138
        // initialize response, securty translate and other workers
139
        self::$Security = new Security();
140
        self::$Response = new Response();
141
        self::$View = new View();
142
        self::$Translate = new Translate();
143
        self::$Alias = new Alias();
144
        self::$Event = new EventManager();
145
        self::$Cron = new CronManager();
146
        // stop debug timeline
147
        $this->stopMeasure(__METHOD__);
148
    }
149
150
    /**
151
     * Prepare dynamic static links from object configurations as anonymous functions
152
     * @throws NativeException
153
     */
154
    private function loadDynamicServices(): void
155
    {
156
        $this->startMeasure(__METHOD__);
157
158
        /** @var array $objects */
159
        $objects = App::$Properties->getAll('object');
160
        if (!Any::isArray($objects)) {
161
            throw new NativeException('Object configurations is not loaded: /Private/Config/Object.php');
162
        }
163
164
        // each all objects as service_name => service_instance()
165
        foreach ($objects as $name => $instance) {
166
            // check if definition of object is exist and services list contains it or is null to auto build
167
            if (property_exists(get_called_class(), $name) && $instance instanceof \Closure && (isset($this->_services[$name]) || $this->_services === null)) {
168
                if ($this->_services[$name] === true || $this->_services === null) { // initialize from configs
169
                    self::${$name} = $instance();
170
                } elseif (is_callable($this->_services[$name])) { // raw initialization from App::run()
171
                    self::${$name} = $this->_services[$name]();
172
                }
173
            } elseif (Str::startsWith('_', $name)) { // just anonymous callback without entry-point
174
                @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...
175
            }
176
        }
177
178
        $this->stopMeasure(__METHOD__);
179
    }
180
181
    /**
182
     * Run applications and display output. Main entry point of system.
183
     * @return void
184
     */
185
    final public function run(): void
186
    {
187
        $html = null;
188
        // lets try to get html full content to page render
189
        try {
190
            $this->startMeasure(__METHOD__ . '::callback');
191
            /** @var \Ffcms\Core\Arch\Controller $callClass */
192
            $callClass = null;
193
            $callMethod = 'action' . self::$Request->getAction();
194
195
            // define callback class namespace/name full path
196
            $cName = (self::$Request->getCallbackAlias() ?? '\Apps\Controller\\' . env_name . '\\' . self::$Request->getController());
197
            if (!class_exists($cName)) {
198
                throw new NotFoundException('Callback class not found: ' . App::$Security->strip_tags($cName));
199
            }
200
201
            $callClass = new $cName;
202
            // check if callback method (action) is exist in class object
203
            if (!method_exists($callClass, $callMethod)) {
204
                throw new NotFoundException('Method "' . App::$Security->strip_tags($callMethod) . '()" not founded in "' . get_class($callClass) . '"');
205
            }
206
207
            $this->stopMeasure(__METHOD__ . '::callback');
208
            $params = [];
209
            if (!Str::likeEmpty(self::$Request->getID())) {
210
                $params[] = self::$Request->getID();
211
                if (!Str::likeEmpty(self::$Request->getAdd())) {
212
                    $params[] = self::$Request->getAdd();
213
                }
214
            }
215
216
            // get instance of callback object (class::method) as reflection
217
            $instance = new \ReflectionMethod($callClass, $callMethod);
218
            $argCount = 0;
219
            // calculate method defined arguments count
220
            foreach ($instance->getParameters() as $arg) {
221
                if (!$arg->isOptional()) {
222
                    $argCount++;
223
                }
224
            }
225
            // compare method arg count with passed
226
            if (count($params) < $argCount) {
227
                throw new NotFoundException(__('Arguments for method %method% is not enough. Expected: %required%, got: %current%.', [
228
                    'method' => $callMethod,
229
                    'required' => $argCount,
230
                    'current' => count($params)
231
                ]));
232
            }
233
234
            $this->startMeasure($cName . '::' . $callMethod);
235
            // make callback call to action in controller and get response
236
            $actionResponse = call_user_func_array([$callClass, $callMethod], $params);
237
            $this->stopMeasure($cName . '::' . $callMethod);
238
239
            // set response to controller attribute
240
            if (!Str::likeEmpty($actionResponse)) {
241
                $callClass->setOutput($actionResponse);
242
            }
243
244
            // build full compiled output html data with default layout and widgets
245
            $html = $callClass->buildOutput();
246
        } catch (\Exception $e) {
247
            // check if exception is system-based throw
248
            if ($e instanceof TemplateException) {
249
                $html = $e->display();
250
            } else { // or hook exception to system based :)))
251
                if (App::$Debug) {
252
                    $msg = $e->getMessage() . $e->getTraceAsString();
253
                    $html = (new NativeException($msg))->display();
254
                } else {
255
                    $html = (new NativeException($e->getMessage()))->display();
256
                }
257
            }
258
        }
259
260
        // set full rendered content to response builder
261
        self::$Response->setContent($html);
262
        // echo full response to user via symfony http foundation
263
        self::$Response->send();
264
    }
265
}
266