Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.

Commit 3975da7

Browse files
authored
Merge pull request #41 from modulusphp/feature/extendable-support
Feature/extendable support
2 parents b2ef09b + e44a87c commit 3975da7

File tree

8 files changed

+313
-17
lines changed

8 files changed

+313
-17
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Modulus\Framework\Exceptions;
4+
5+
use Exception;
6+
7+
class CannotExtendClassException extends Exception
8+
{
9+
public function __construct(string $message)
10+
{
11+
$args = debug_backtrace()[1];
12+
13+
foreach ($args as $key => $value) {
14+
$this->{$key} = $value;
15+
}
16+
17+
$this->message = $message;
18+
}
19+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Modulus\Framework\Exceptions;
4+
5+
use Exception;
6+
7+
class PluginNotFoundException extends Exception
8+
{
9+
/**
10+
* Map the exception
11+
*
12+
* @param string $class
13+
*/
14+
public function __construct(string $class)
15+
{
16+
$file = config('app.dir') . 'config' . DS . 'app.php';
17+
18+
if (file_exists($file)) {
19+
$app = file($file);
20+
21+
foreach($app as $key => $line) {
22+
if ($this->contains($line, "plugins")) {
23+
foreach(debug_backtrace()[0] as $traceKey => $value) {
24+
$this->{$traceKey} = null;
25+
}
26+
27+
$this->line = $key + 1;
28+
}
29+
30+
$this->file = $file;
31+
$this->message = "Plugin \"{$class}::class\" does not exist";
32+
}
33+
}
34+
}
35+
36+
/**
37+
* Check if file exists
38+
*
39+
* @param string $str
40+
* @param string $word
41+
* @return bool
42+
*/
43+
private function contains($str, $word)
44+
{
45+
return !!preg_match('#\\b' . preg_quote($word, '#') . '\\b#i', $str);
46+
}
47+
}

Plugin/Load.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Modulus\Framework\Plugin;
4+
5+
use ReflectionClass;
6+
use Modulus\Framework\Application;
7+
use Modulus\Framework\Upstart\Prototype;
8+
use Modulus\Framework\Exceptions\PluginNotFoundException;
9+
10+
class Load
11+
{
12+
/**
13+
* Load plugins
14+
*
15+
* @param bool $isConsole
16+
* @return void
17+
*/
18+
public static function plugins(bool $isConsole)
19+
{
20+
$plugins = config('app.plugins');
21+
22+
if (env('DEV_AUTOLOAD_PLUGINS') == true) {
23+
$arguments = Application::prototype($isConsole);
24+
$extendable = new Prototype;
25+
26+
foreach($arguments as $key => $value) {
27+
$extendable->$key = $value;
28+
}
29+
30+
foreach($plugins as $plugin => $class) {
31+
if (!class_exists($class)) {
32+
throw new PluginNotFoundException($class);
33+
}
34+
35+
$class_info = new ReflectionClass($class);
36+
$DIR = dirname($class_info->getFileName());
37+
38+
$extension = new $class;
39+
40+
if (!Validate::check($extension, $class_info)) return;
41+
42+
$extension->instance($extendable, $DIR);
43+
$extension->boot($extendable);
44+
}
45+
}
46+
}
47+
}

Plugin/Validate.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
namespace Modulus\Framework\Plugin;
4+
5+
use ReflectionClass;
6+
use Modulus\Utility\Plugin;
7+
8+
class Validate
9+
{
10+
/**
11+
* Check if plugin is usable
12+
*
13+
* @param Plugin $plugin
14+
* @return bool
15+
*/
16+
public static function check(Plugin $plugin, ReflectionClass $info) : bool
17+
{
18+
/**
19+
* Check if plugin meets the minimum requirements
20+
*/
21+
if (
22+
array_key_exists('PACKAGE', $info->getConstants()) &&
23+
array_key_exists('VERSION', $info->getConstants()) &&
24+
(
25+
array_key_exists('AUTHORS', $info->getConstants()) &&
26+
is_array($info->getConstants()['AUTHORS'])
27+
) &&
28+
array_key_exists('FRAMEWORK', $info->getConstants())
29+
) {
30+
return self::isCompatable($plugin);
31+
}
32+
33+
/**
34+
* Plugin does not meet the minimum requirements
35+
*/
36+
return false;
37+
}
38+
39+
/**
40+
* Check if plugin supports the current version of the framework
41+
*
42+
* @param Plugin $plugin
43+
* @return bool
44+
*/
45+
private static function isCompatable(Plugin $plugin) : bool
46+
{
47+
$frameworkVersion = substr(self::getVersion(), 0, strrpos(self::getVersion(), '*'));
48+
$pluginVersions = explode('|', $plugin::FRAMEWORK);
49+
50+
foreach($pluginVersions as $version) {
51+
$version = substr($version, 0, strrpos($version, '*'));
52+
53+
return starts_with($frameworkVersion, $version);
54+
}
55+
56+
return false;
57+
}
58+
59+
/**
60+
* Get application version from composer file
61+
*
62+
* @return string
63+
*/
64+
private static function getVersion() : string
65+
{
66+
$composerJson = config('app.dir') . 'composer.json';
67+
68+
if (file_exists($composerJson)) {
69+
$composer = json_decode(file_get_contents($composerJson, true));
70+
$version = isset($composer->version) ? $composer->version : '1';
71+
$require = isset($composer->require) ? (array)$composer->require : false;
72+
73+
if (!is_array($require)) return $version;
74+
75+
if (isset($require['modulusphp/framework'])) {
76+
return $require['modulusphp/framework'];
77+
}
78+
79+
return $version;
80+
}
81+
82+
return '1';
83+
}
84+
}

Upstart.php

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace Modulus\Framework;
44

5+
use Modulus\Framework\Plugin\Load;
56
use App\Resolvers\AppServiceResolver;
67
use Modulus\Framework\Upstart\AppLogger;
8+
use Modulus\Framework\Upstart\Prototype;
79
use Modulus\Framework\Upstart\AppConnect;
810
use Modulus\Framework\Upstart\HandleCors;
911
use Modulus\Framework\Upstart\ErrorReport;
@@ -44,47 +46,86 @@ class Upstart
4446
*/
4547
public function boot(?bool $isConsole = false) : void
4648
{
49+
/**
50+
* Add cors to the request
51+
*/
4752
$this->addCors();
53+
54+
/**
55+
* Don't load framework components, if
56+
* the application has already started.
57+
*/
4858
if (Upstart::$isReady) return;
4959

60+
/**
61+
* Load application components
62+
*/
63+
$this->startCore($isConsole);
64+
$this->startApp($isConsole);
65+
$this->startRouter($isConsole);
66+
67+
/**
68+
* Mark "application" as ready to indicate, that
69+
* it has started.
70+
*/
71+
Upstart::$isReady = true;
72+
}
73+
74+
/**
75+
* Load the core components
76+
*
77+
* @param bool $isConsole
78+
* @return void
79+
*/
80+
private function startCore(bool $isConsole)
81+
{
5082
$this->bootEnv();
5183
$this->initialize();
5284
$this->logger();
5385
$this->errorHandling($isConsole);
5486
$this->bootRemember();
5587

88+
/**
89+
* Create class aliases
90+
*/
5691
$aliases = config('app.aliases');
5792

5893
foreach($aliases as $alias => $class) {
5994
class_alias($class, $alias);
6095
}
96+
}
6197

98+
/**
99+
* Boot the application
100+
*
101+
* @param bool $isConsole
102+
* @return void
103+
*/
104+
private function startApp(bool $isConsole)
105+
{
62106
(new AppServiceResolver)->start(Application::prototype($isConsole));
63107

64-
$this->autoload_plugins($isConsole);
108+
/**
109+
* Load framework plugins
110+
*/
111+
Load::plugins($isConsole);
65112

113+
/**
114+
* Extend the medusa templating language
115+
*/
66116
$this->directives();
67-
$this->handleSwish();
68-
$this->route($isConsole);
69-
70-
Upstart::$isReady = true;
71117
}
72118

73119
/**
74-
* autoload_plugins
120+
* Start the application router
75121
*
76122
* @param bool $isConsole
77123
* @return void
78124
*/
79-
public function autoload_plugins(bool $isConsole)
125+
private function startRouter(bool $isConsole)
80126
{
81-
$plugins = config('app.plugins');
82-
83-
if (env('DEV_AUTOLOAD_PLUGINS') == true) {
84-
foreach($plugins as $plugin => $class) {
85-
$class::boot((object)Application::prototype($isConsole));
86-
}
87-
}
127+
$this->handleSwish();
128+
$this->route($isConsole);
88129
}
89130

90131
/**

Upstart/Prototype.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Modulus\Framework\Upstart;
4+
5+
use Closure;
6+
use ReflectionClass;
7+
use Modulus\Support\Extendable;
8+
use Modulus\Framework\Exceptions\CannotExtendClassException;
9+
10+
class Prototype
11+
{
12+
/**
13+
* Add custom function
14+
*
15+
* @param string $class
16+
* @param string $method
17+
* @param Closure $closure
18+
* @return mixed
19+
*/
20+
public function bind(string $class, string $method, Closure $closure)
21+
{
22+
if (!in_array(
23+
Extendable::class,
24+
array_keys((new ReflectionClass($class))->getTraits()))
25+
) {
26+
throw new CannotExtendClassException("Cannot extend \"{$class}::class\"");
27+
}
28+
29+
return $class::bind($method, $closure);
30+
}
31+
32+
/**
33+
* Add custom static function
34+
*
35+
* @param string $class
36+
* @param string $method
37+
* @param Closure $closure
38+
* @return mixed
39+
*/
40+
public function static(string $class, string $method, Closure $closure)
41+
{
42+
if (!in_array(
43+
Extendable::class,
44+
array_keys((new ReflectionClass($class))->getTraits()))
45+
) {
46+
throw new CannotExtendClassException("Cannot extend \"{$class}::class\"");
47+
}
48+
49+
return $class::static($method, $closure);
50+
}
51+
}

Upstart/Service.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ public function getNamespace() : string
3737
*/
3838
public function start(?array $args = null)
3939
{
40-
$this->boot((object)$args);
40+
$arguments = (object)$args;
41+
$extendable = new Prototype;
42+
43+
foreach($arguments as $key => $value) {
44+
$extendable->$key = $value;
45+
}
46+
47+
$this->boot($extendable);
4148
}
4249
}

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "modulusphp/framework",
3-
"description": "Framework component for modulusPHP",
4-
"version": "1.9.8.9",
3+
"description": "Framework component for Modulus",
4+
"version": "1.9.8.10",
55
"license": "MIT",
66
"type": "package",
77
"authors": [{

0 commit comments

Comments
 (0)