Completed
Push — master ( 75d5d0...c9b7ea )
by Mihail
02:29
created

App::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 1
eloc 7
nc 1
nop 2
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\Security;
16
use Ffcms\Core\Helper\Type\Obj;
17
use Ffcms\Core\Helper\Type\Str;
18
use Ffcms\Core\I18n\Translate;
19
use Ffcms\Core\Managers\BootManager;
20
use Ffcms\Core\Managers\CronManager;
21
use Ffcms\Core\Managers\EventManager;
22
use Ffcms\Core\Network\Request;
23
use Ffcms\Core\Network\Response;
24
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
25
26
/**
27
 * Class App. Provide later static callbacks as entry point from any places of ffcms.
28
 * @package Ffcms\Core
29
 */
30
class App
31
{
32
    use DebugMeasure;
33
34
    /** @var \Ffcms\Core\Network\Request */
35
    public static $Request;
36
37
    /** @var \Ffcms\Core\Properties */
38
    public static $Properties;
39
40
    /** @var \Ffcms\Core\Network\Response */
41
    public static $Response;
42
43
    /** @var \Ffcms\Core\Alias */
44
    public static $Alias;
45
46
    /** @var \Ffcms\Core\Arch\View */
47
    public static $View;
48
49
    /** @var \Ffcms\Core\Debug\Manager|null */
50
    public static $Debug;
51
52
    /** @var \Ffcms\Core\Helper\Security */
53
    public static $Security;
54
55
    /** @var \Ffcms\Core\I18n\Translate */
56
    public static $Translate;
57
58
    /** @var \Ffcms\Core\Interfaces\iUser */
59
    public static $User;
60
61
    /** @var \Symfony\Component\HttpFoundation\Session\Session */
62
    public static $Session;
63
64
    /** @var \Illuminate\Database\Capsule\Manager */
65
    public static $Database;
66
67
    /** @var \Ffcms\Core\Cache\MemoryObject */
68
    public static $Memory;
69
70
    /** @var \Swift_Mailer */
71
    public static $Mailer;
72
73
    /** @var \Ffcms\Core\Interfaces\iCaptcha */
74
    public static $Captcha;
75
76
    /** @var FilesystemAdapter */
77
    public static $Cache;
78
79
    /** @var EventManager */
80
    public static $Event;
81
82
    /** @var CronManager */
83
    public static $Cron;
84
85
    private $_services;
86
    private $_loader;
87
88
    /**
89
     * App constructor. Build App entry-point instance
90
     * @param array|null $services
91
     * @param bool $loader
92
     * @throws \Ffcms\Core\Exception\NativeException
93
     * @throws \InvalidArgumentException
94
     */
95
    public function __construct(array $services = null, $loader = false)
96
    {
97
        // pass initialization data inside
98
        $this->_services = $services;
99
        $this->_loader = $loader;
100
        // initialize service links
101
        $this->loadNativeServices();
102
        $this->loadDynamicServices();
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 native static symbolic links for app services
122
     * @throws \InvalidArgumentException
123
     */
124
    private function loadNativeServices(): 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 loadDynamicServices(): 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. Main entry point of system.
181
     * @return void
182
     */
183
    final public function run(): void
184
    {
185
        $html = null;
186
        // lets try to get html full content to page render
187
        try {
188
            $this->startMeasure(__METHOD__ . '::callback');
189
            /** @var \Ffcms\Core\Arch\Controller $callClass */
190
            $callClass = null;
191
            $callMethod = 'action' . self::$Request->getAction();
192
193
            // define callback class namespace/name full path
194
            $cName = (self::$Request->getCallbackAlias() ?? '\Apps\Controller\\' . env_name . '\\' . self::$Request->getController());
195
            if (!class_exists($cName))
196
                throw new NotFoundException('Callback class not found: ' . App::$Security->strip_tags($cName));
197
198
            $callClass = new $cName;
199
            // check if callback method (action) is exist in class object
200
            if (!method_exists($callClass, $callMethod))
201
                throw new NotFoundException('Method "' . App::$Security->strip_tags($callMethod) . '()" not founded in "' . get_class($callClass) . '"');
202
203
            $this->stopMeasure(__METHOD__ . '::callback');
204
            $params = [];
205
            if (!Str::likeEmpty(self::$Request->getID())) {
206
                $params[] = self::$Request->getID();
207
                if (!Str::likeEmpty(self::$Request->getAdd()))
208
                    $params[] = self::$Request->getAdd();
209
            }
210
211
            // get instance of callback object (class::method) as reflection
212
            $instance = new \ReflectionMethod($callClass, $callMethod);
213
            $argCount = 0;
214
            // calculate method defined arguments count
215
            foreach ($instance->getParameters() as $arg) {
216
                if (!$arg->isOptional())
217
                    $argCount++;
218
            }
219
            // compare method arg count with passed
220
            if (count($params) < $argCount)
221
                throw new NotFoundException(__('Arguments for method %method% is not enough. Expected: %required%, got: %current%.', [
222
                    'method' => $callMethod,
223
                    'required' => $argCount,
224
                    'current' => count($params)
225
                ]));
226
227
            $this->startMeasure($cName . '::' . $callMethod);
228
            // make callback call to action in controller and get response
229
            $actionResponse = call_user_func_array([$callClass, $callMethod], $params);
230
            $this->stopMeasure($cName . '::' . $callMethod);
231
232
            // set response to controller attribute
233
            if (!Str::likeEmpty($actionResponse))
234
                $callClass->setOutput($actionResponse);
235
236
            // build full compiled output html data with default layout and widgets
237
            $html = $callClass->buildOutput();
238
        } catch (\Exception $e) {
239
            // check if exception is system-based throw
240
            if ($e instanceof TemplateException)
241
                $html = $e->display();
242
            else // or hook exception to system based :)))
243
                $html = (new NativeException($e->getMessage()))->display();
244
        }
245
246
        // set full rendered content to response builder
247
        self::$Response->setContent($html);
248
        // echo full response to user via symfony http foundation
249
        self::$Response->send();
250
    }
251
252
}