Completed
Push — master ( c9b7ea...c1fd74 )
by Mihail
02:38
created

App::factory()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
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\Any;
17
use Ffcms\Core\Helper\Type\Obj;
18
use Ffcms\Core\Helper\Type\Str;
19
use Ffcms\Core\I18n\Translate;
20
use Ffcms\Core\Managers\BootManager;
21
use Ffcms\Core\Managers\CronManager;
22
use Ffcms\Core\Managers\EventManager;
23
use Ffcms\Core\Network\Request;
24
use Ffcms\Core\Network\Response;
25
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
26
27
/**
28
 * Class App. Provide later static callbacks as entry point from any places of ffcms.
29
 * @package Ffcms\Core
30
 */
31
class App
32
{
33
    use DebugMeasure;
34
35
    /** @var \Ffcms\Core\Network\Request */
36
    public static $Request;
37
38
    /** @var \Ffcms\Core\Properties */
39
    public static $Properties;
40
41
    /** @var \Ffcms\Core\Network\Response */
42
    public static $Response;
43
44
    /** @var \Ffcms\Core\Alias */
45
    public static $Alias;
46
47
    /** @var \Ffcms\Core\Arch\View */
48
    public static $View;
49
50
    /** @var \Ffcms\Core\Debug\Manager|null */
51
    public static $Debug;
52
53
    /** @var \Ffcms\Core\Helper\Security */
54
    public static $Security;
55
56
    /** @var \Ffcms\Core\I18n\Translate */
57
    public static $Translate;
58
59
    /** @var \Ffcms\Core\Interfaces\iUser|\Apps\ActiveRecord\User */
60
    public static $User;
61
62
    /** @var \Symfony\Component\HttpFoundation\Session\Session */
63
    public static $Session;
64
65
    /** @var \Illuminate\Database\Capsule\Manager */
66
    public static $Database;
67
68
    /** @var \Ffcms\Core\Cache\MemoryObject */
69
    public static $Memory;
70
71
    /** @var \Swift_Mailer */
72
    public static $Mailer;
73
74
    /** @var \Ffcms\Core\Interfaces\iCaptcha */
75
    public static $Captcha;
76
77
    /** @var FilesystemAdapter */
78
    public static $Cache;
79
80
    /** @var EventManager */
81
    public static $Event;
82
83
    /** @var CronManager */
84
    public static $Cron;
85
86
    private $_services;
87
    private $_loader;
88
89
    /**
90
     * App constructor. Build App entry-point instance
91
     * @param array|null $services
92
     * @param bool $loader
93
     * @throws \Ffcms\Core\Exception\NativeException
94
     * @throws \InvalidArgumentException
95
     */
96
    public function __construct(array $services = null, $loader = false)
97
    {
98
        // pass initialization data inside
99
        $this->_services = $services;
100
        $this->_loader = $loader;
101
        // initialize service links
102
        $this->loadNativeServices();
103
        $this->loadDynamicServices();
104
        // Initialize boot manager. This manager allow to auto-execute 'static boot()' methods in apps and widgets
105
        $bootManager = new BootManager($this->_loader);
106
        $bootManager->run();
107
    }
108
109
    /**
110
     * Factory method builder for app entry point
111
     * @param array|null $services
112
     * @param bool $loader
113
     * @return App
114
     * @throws \InvalidArgumentException
115
     */
116
    public static function factory(array $services = null, $loader = false): self
117
    {
118
        return new self($services, $loader);
119
    }
120
121
    /**
122
     * Prepare native static symbolic links for app services
123
     * @throws \InvalidArgumentException
124
     */
125
    private function loadNativeServices(): void
126
    {
127
        // initialize memory and properties controllers
128
        self::$Memory = MemoryObject::instance();
129
        self::$Properties = new Properties();
130
        // initialize debugger
131
        if (isset($this->_services['Debug']) && $this->_services['Debug'] === true && Debug::isEnabled()) {
132
            self::$Debug = new Debug();
133
            $this->startMeasure(__METHOD__);
134
        }
135
        // prepare request data
136
        self::$Request = Request::createFromGlobals();
137
        // initialize response, securty translate and other workers
138
        self::$Security = new Security();
139
        self::$Response = new Response();
140
        self::$View = new View();
141
        self::$Translate = new Translate();
142
        self::$Alias = new Alias();
143
        self::$Event = new EventManager();
144
        self::$Cron = new CronManager();
145
        // stop debug timeline
146
        $this->stopMeasure(__METHOD__);
147
    }
148
149
    /**
150
     * Prepare dynamic static links from object configurations as anonymous functions
151
     * @throws NativeException
152
     */
153
    private function loadDynamicServices(): void
154
    {
155
        $this->startMeasure(__METHOD__);
156
157
        /** @var array $objects */
158
        $objects = App::$Properties->getAll('object');
159
        if (!Any::isArray($objects))
160
            throw new NativeException('Object configurations is not loaded: /Private/Config/Object.php');
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
                if (App::$Debug) {
244
                    $msg = $e->getMessage() . $e->getTraceAsString();
245
                    $html = (new NativeException($msg))->display();
246
                } else {
247
                    $html = (new NativeException($e->getMessage()))->display();
248
                }
249
250
            }
251
        }
252
253
        // set full rendered content to response builder
254
        self::$Response->setContent($html);
255
        // echo full response to user via symfony http foundation
256
        self::$Response->send();
257
    }
258
259
}