App::loadNativeServices()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 4
nop 0
dl 0
loc 23
rs 9.4555
c 0
b 0
f 0
1
<?php
2
3
namespace Ffcms\Core;
4
5
use Ffcms\Core\Arch\Controller;
6
use Ffcms\Core\Arch\View;
7
use Ffcms\Core\Cache\MemoryObject;
8
use Ffcms\Core\Debug\DebugMeasure;
9
use Ffcms\Core\Debug\Manager as Debug;
10
use Ffcms\Core\Exception\NativeException;
11
use Ffcms\Core\Exception\NotFoundException;
12
use Ffcms\Core\Exception\TemplateException;
13
use Ffcms\Core\Helper\Mailer;
14
use Ffcms\Core\Helper\Security;
15
use Ffcms\Core\Helper\Type\Any;
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 Ffcms\Core\Traits\ClassTools;
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, ClassTools;
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|\Apps\ActiveRecord\User */
0 ignored issues
show
Bug introduced by
The type Apps\ActiveRecord\User was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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 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
     */
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->loadNativeServices();
101
        $this->loadDynamicServices();
102
        // Initialize boot manager. This manager allow to auto-execute 'static boot()' methods in apps and widgets
103
        $bootManager = new BootManager($this->_loader);
104
        $bootManager->run();
105
    }
106
107
    /**
108
     * Factory method builder for app entry point
109
     * @param array|null $services
110
     * @param bool $loader
111
     * @return App
112
     * @throws NativeException
113
     */
114
    public static function factory(array $services = null, $loader = false): self
115
    {
116
        return new self($services, $loader);
117
    }
118
119
    /**
120
     * Prepare native static symbolic links for app services
121
     * @throws NativeException
122
     */
123
    private function loadNativeServices(): void
124
    {
125
        // initialize memory and properties controllers
126
        self::$Memory = MemoryObject::instance();
127
        self::$Properties = new Properties();
128
        // initialize debugger
129
        if (isset($this->_services['Debug']) && $this->_services['Debug'] === true && Debug::isEnabled()) {
130
            self::$Debug = new Debug();
131
            $this->startMeasure(__METHOD__);
132
        }
133
        // prepare request data
134
        self::$Request = Request::createFromGlobals();
135
        // initialize response, securty translate and other workers
136
        self::$Security = new Security();
137
        self::$Response = new Response();
138
        self::$View = new View();
139
        self::$Translate = new Translate();
140
        self::$Alias = new Alias();
141
        self::$Event = new EventManager();
142
        self::$Cron = new CronManager();
143
        // stop debug timeline
144
        if (self::$Debug) {
145
            $this->stopMeasure(__METHOD__);
146
        }
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
163
        // each all objects as service_name => service_instance()
164
        foreach ($objects as $name => $instance) {
165
            // check if definition of object is exist and services list contains it or is null to auto build
166
            if (property_exists(get_called_class(), $name) && $instance instanceof \Closure && (isset($this->_services[$name]) || $this->_services === null)) {
167
                if ($this->_services[$name] === true || $this->_services === null) { // initialize from configs
168
                    self::${$name} = $instance();
169
                } elseif (is_callable($this->_services[$name])) { // raw initialization from App::run()
170
                    self::${$name} = $this->_services[$name]();
171
                }
172
            } elseif (Str::startsWith('_', $name)) { // just anonymous callback without entry-point
173
                @call_user_func($instance);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for call_user_func(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

173
                /** @scrutinizer ignore-unhandled */ @call_user_func($instance);

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...
174
            }
175
        }
176
177
        $this->stopMeasure(__METHOD__);
178
    }
179
180
    /**
181
     * Run applications and display output. Main entry point of system.
182
     * @return void
183
     */
184
    public function run(): void
185
    {
186
        try {
187
            /** @var \Ffcms\Core\Arch\Controller $callClass */
188
            $callClass = $this->getCallbackClass();
189
            $callMethod = 'action' . self::$Request->getAction();
190
            $arguments = self::$Request->getArguments();
191
192
            // check if callback method (action) is exist in class object
193
            if (!method_exists($callClass, $callMethod)) {
194
                throw new NotFoundException('Method "' . App::$Security->strip_tags($callMethod) . '()" not founded in "' . get_class($callClass) . '"');
195
            }
196
197
            // check if method arguments counts equals passed count
198
            $requiredArgCount = $this->getMethodRequiredArgCount($callClass, $callMethod);
199
200
            // compare method arg count with passed
201
            if (count($arguments) < $requiredArgCount) {
202
                throw new NotFoundException(__('Arguments for method %method% is not enough. Expected: %required%, got: %current%.', [
0 ignored issues
show
Bug introduced by
The function __ was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

202
                throw new NotFoundException(/** @scrutinizer ignore-call */ __('Arguments for method %method% is not enough. Expected: %required%, got: %current%.', [
Loading history...
203
                    'method' => $callMethod,
204
                    'required' => $requiredArgCount,
205
                    'current' => count($arguments)
206
                ]));
207
            }
208
209
            // make callback call to action in controller and get response
210
            $response = call_user_func_array([$callClass, $callMethod], $arguments);
211
212
            // if no response - throw 404 not found
213
            if (!$response) {
214
                throw new NotFoundException('Page not found: 404 error');
215
            }
216
        } catch (\Exception $e) {
217
            // check if exception is system-based throw
218
            if ($e instanceof TemplateException) {
219
                $response = $e->display();
220
            } else { // or hook exception to system based :)))
221
                if (App::$Debug) {
222
                    $msg = $e->getMessage() . $e->getTraceAsString();
223
                    $response = (new NativeException($msg))->display();
224
                } else {
225
                    $response = (new NativeException($e->getMessage()))->display();
226
                }
227
            }
228
        }
229
230
        // set full rendered content to response builder
231
        self::$Response->setContent($response);
232
        // echo full response to user via symfony http foundation
233
        self::$Response->send();
234
    }
235
236
    /**
237
     * Get callback class instance
238
     * @return Controller
239
     * @throws NotFoundException
240
     */
241
    private function getCallbackClass()
242
    {
243
        // define callback class namespace/name full path
244
        $cName = (self::$Request->getCallbackAlias() ?? '\Apps\Controller\\' . env_name . '\\' . self::$Request->getController());
0 ignored issues
show
Bug introduced by
The constant Ffcms\Core\env_name was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
245
        if (!class_exists($cName)) {
246
            throw new NotFoundException('Callback class not found: ' . App::$Security->strip_tags($cName));
247
        }
248
249
        return new $cName;
250
    }
251
}
252