Passed
Push — master ( 1bc6cd...cbf361 )
by Mihail
04:19
created

App::getCallbackClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 9
rs 9.6666
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
     * @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 (!Any::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 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

172
                /** @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...
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
    public function run(): void
184
    {
185
        try {
186
            /** @var \Ffcms\Core\Arch\Controller $callClass */
187
            $callClass = $this->getCallbackClass();
188
            $callMethod = 'action' . self::$Request->getAction();
189
            $arguments = $this->getArguments();
190
191
            // check if callback method (action) is exist in class object
192
            if (!method_exists($callClass, $callMethod)) {
193
                throw new NotFoundException('Method "' . App::$Security->strip_tags($callMethod) . '()" not founded in "' . get_class($callClass) . '"');
194
            }
195
196
            // check if method arguments counts equals passed count
197
            $requiredArgCount = $this->getMethodRequiredArgCount($callClass, $callMethod);
198
199
            // compare method arg count with passed
200
            if (count($arguments) < $requiredArgCount) {
201
                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

201
                throw new NotFoundException(/** @scrutinizer ignore-call */ __('Arguments for method %method% is not enough. Expected: %required%, got: %current%.', [
Loading history...
202
                    'method' => $callMethod,
203
                    'required' => $requiredArgCount,
204
                    'current' => count($arguments)
205
                ]));
206
            }
207
208
            $this->startMeasure(get_class($callClass) . '::' . $callMethod);
209
            // make callback call to action in controller and get response
210
            $actionResponse = call_user_func_array([$callClass, $callMethod], $arguments);
211
            $this->stopMeasure(get_class($callClass) . '::' . $callMethod);
212
213
            // set response to controller attribute
214
            if (!Str::likeEmpty($actionResponse)) {
215
                $callClass->setOutput($actionResponse);
216
            }
217
218
            // build full compiled output html data with default layout and widgets
219
            $html = $callClass->buildOutput();
220
        } catch (\Exception $e) {
221
            // check if exception is system-based throw
222
            if ($e instanceof TemplateException) {
223
                $html = $e->display();
224
            } else { // or hook exception to system based :)))
225
                if (App::$Debug) {
226
                    $msg = $e->getMessage() . $e->getTraceAsString();
227
                    $html = (new NativeException($msg))->display();
228
                } else {
229
                    $html = (new NativeException($e->getMessage()))->display();
230
                }
231
            }
232
        }
233
234
        // set full rendered content to response builder
235
        self::$Response->setContent($html);
236
        // echo full response to user via symfony http foundation
237
        self::$Response->send();
238
    }
239
240
    /**
241
     * Get callback class instance
242
     * @return Controller
243
     * @throws NotFoundException
244
     */
245
    private function getCallbackClass()
246
    {
247
        // define callback class namespace/name full path
248
        $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...
249
        if (!class_exists($cName)) {
250
            throw new NotFoundException('Callback class not found: ' . App::$Security->strip_tags($cName));
251
        }
252
253
        return new $cName;
254
    }
255
256
    /**
257
     * Get method arguments from request
258
     * @return array
259
     */
260
    private function getArguments(): array
261
    {
262
        $args = [];
263
        if (!Str::likeEmpty(self::$Request->getID())) {
264
            $args[] = self::$Request->getID();
265
            if (!Str::likeEmpty(self::$Request->getAdd())) {
266
                $args[] = self::$Request->getAdd();
267
            }
268
        }
269
270
        return $args;
271
    }
272
}
273