如何在不使用required_once的情况下基于pico CMS设计一个简单的插件?
How to design a simple plugin based on pico CMS without using required_once?
我希望基于pico CMS and call it in a protected function in my main class, called App
设计一个简单的插件。
插件名为 AppHelper
。
尝试
我在App
上面加了一个required_once
class:
require_once "/Library/WebServer/usco/master/plugins/AppHelper.php";
并使用这个进行测试(第一个 AppHelper::isMobile
是不正确的):
// ********************MOBILE OR DESKTOP***********************//
if (AppHelper::isMobile($_SERVER['HTTP_USER_AGENT'])) {
$viewSetting = $_SESSION["view-setting"] = array("device" => "mobile"); // IF MOBILE
} else {
$viewSetting = $_SESSION["view-setting"] = array("device" => "desktop"); // IF DESKTOP
}
// ********************MOBILE OR DESKTOP***********************//
在受保护的方法中:
protected function loadConfig()
{
$config = null;
if (file_exists($this->getConfigDir() . 'config.php')) {
require $this->getConfigDir() . 'config.php';
}
// ********************MOBILE OR DESKTOP***********************//
if (AppHelper::isMobile($_SERVER['HTTP_USER_AGENT'])) {
$viewSetting = $_SESSION["view-setting"] = array("device" => "mobile"); // IF MOBILE
} else {
$viewSetting = $_SESSION["view-setting"] = array("device" => "desktop"); // IF DESKTOP
}
// ********************MOBILE OR DESKTOP***********************//
$defaultConfig = array(
'company_name' => 'USCO',
'page_title' => 'USCO',
'base_url' => '',
'rewrite_url' => null,
'date_format' => '%D %T',
'twig_config' => null,
'blog_content_dir' => null,
'blog_content_path' => null,
'blog_articles_dir' => null,
'blog_articles_path' => null,
'blog_content_ext' => '.md',
'device' => $viewSetting["device"],
'timezone' => '',
);
$this->config = is_array($this->config) ? $this->config : array();
$this->config += is_array($config) ? $config + $defaultConfig : $defaultConfig;
if (empty($this->config['base_url'])) {
$this->config['base_url'] = $this->getBaseUrl();
} else {
$this->config['base_url'] = rtrim($this->config['base_url'], '/') . '/';
}
if ($this->config['rewrite_url'] === null) {
$this->config['rewrite_url'] = $this->isUrlRewritingEnabled();
}
$local_domain = "usco.loc";
if ($_SERVER['SERVER_NAME'] === $local_domain) {
$defaultTwigConfig = array('cache' => false, 'autoescape' => false, 'debug' => true);
// $defaultTwigConfig = array('cache' => __DIR__ . "/../cache", 'autoescape' => false, 'debug' => false);
} else {
$defaultTwigConfig = array('cache' => __DIR__ . "/../cache", 'autoescape' => false, 'debug' => false);
}
if (!is_array($this->config['twig_config'])) {
$this->config['twig_config'] = $defaultTwigConfig;
} else {
$this->config['twig_config'] += $defaultTwigConfig;
}
$this->config['blog_content_path'] = $this->getAbsolutePath($this->config['blog_content_dir'], true);
$this->config['blog_articles_path'] = $this->config['blog_content_path'] . $this->config['blog_articles_dir'];
if (empty($this->config['timezone'])) {
// explicitly set a default timezone to prevent a E_NOTICE
// when no timezone is set; the `date_default_timezone_get()`
// function always returns a timezone, at least UTC
$this->config['timezone'] = @date_default_timezone_get();
}
date_default_timezone_set($this->config['timezone']);
}
并且工作正常,只是一个警告。然而,这是不对的。
How do I correctly design/add this plugin without required_once
and autoload it using composer install
, like all my other plugins that function so?
AppHelper
class AppHelper extends AbstractAppPlugin
{
public function __construct(App $app)
{
parent::__construct($app);
}
public function isMobile($useragent)
{
if (
preg_match('/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i', $useragent)
||
preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i', substr($useragent, 0, 4))
) {
return true; // is mobile
} else {
return false; // is not mobile
};
}
}
AbstractAppPlugin
abstract class AbstractAppPlugin implements AppPluginInterface
{
/**
* Current instance of App
*
* @see AppPluginInterface::getApp()
* @var App
*/
private $app;
/**
* Boolean indicating if this plugin is enabled (true) or disabled (false)
*
* @see AppPluginInterface::isEnabled()
* @see AppPluginInterface::setEnabled()
* @var boolean
*/
protected $enabled = true;
/**
* Boolean indicating if this plugin was ever enabled/disabled manually
*
* @see AppPluginInterface::isStatusChanged()
* @var boolean
*/
protected $statusChanged = false;
/**
* List of plugins which this plugin depends on
*
* @see AbstractAppPlugin::checkDependencies()
* @see AppPluginInterface::getDependencies()
* @var string[]
*/
protected $dependsOn = array();
/**
* List of plugin which depend on this plugin
*
* @see AbstractAppPlugin::checkDependants()
* @see AppPluginInterface::getDependants()
* @var object[]
*/
private $dependants;
/**
* @see AppPluginInterface::__construct()
*/
public function __construct(App $app)
{
$this->app = $app;
}
/**
* @see AppPluginInterface::handleEvent()
*/
public function handleEvent($eventName, array $params)
{
// plugins can be enabled/disabled using the config
if ($eventName === 'onConfigLoaded') {
$pluginEnabled = $this->getConfig(get_called_class() . '.enabled');
if ($pluginEnabled !== null) {
$this->setEnabled($pluginEnabled);
} else {
$pluginConfig = $this->getConfig(get_called_class());
if (is_array($pluginConfig) && isset($pluginConfig['enabled'])) {
$this->setEnabled($pluginConfig['enabled']);
} elseif ($this->enabled) {
// make sure dependencies are already fulfilled,
// otherwise the plugin needs to be enabled manually
try {
$this->checkDependencies(false);
} catch (RuntimeException $e) {
$this->enabled = false;
}
}
}
}
if ($this->isEnabled() || ($eventName === 'onPluginsLoaded')) {
if (method_exists($this, $eventName)) {
call_user_func_array(array($this, $eventName), $params);
}
}
}
/**
* @see AppPluginInterface::setEnabled()
*/
public function setEnabled($enabled, $recursive = true, $auto = false)
{
$this->statusChanged = (!$this->statusChanged) ? !$auto : true;
$this->enabled = (bool) $enabled;
if ($enabled) {
$this->checkDependencies($recursive);
} else {
$this->checkDependants($recursive);
}
}
/**
* @see AppPluginInterface::isEnabled()
*/
public function isEnabled()
{
return $this->enabled;
}
/**
* @see AppPluginInterface::isStatusChanged()
*/
public function isStatusChanged()
{
return $this->statusChanged;
}
/**
* @see AppPluginInterface::getApp()
*/
public function getApp()
{
return $this->app;
}
/**
* Passes all not satisfiable method calls to App
*
* @see App
* @param string $methodName name of the method to call
* @param array $params parameters to pass
* @return mixed return value of the called method
*/
public function __call($methodName, array $params)
{
if (method_exists($this->getApp(), $methodName)) {
return call_user_func_array(array($this->getApp(), $methodName), $params);
}
throw new BadMethodCallException(
'Call to undefined method ' . get_class($this->getApp()) . '::' . $methodName . '() '
. 'through ' . get_called_class() . '::__call()'
);
}
/**
* Enables all plugins which this plugin depends on
*
* @see AppPluginInterface::getDependencies()
* @param boolean $recursive enable required plugins automatically
* @return void
* @throws RuntimeException thrown when a dependency fails
*/
protected function checkDependencies($recursive)
{
foreach ($this->getDependencies() as $pluginName) {
try {
$plugin = $this->getPlugin($pluginName);
} catch (RuntimeException $e) {
throw new RuntimeException(
"Unable to enable plugin '" . get_called_class() . "': "
. "Required plugin '" . $pluginName . "' not found"
);
}
// plugins which don't implement AppPluginInterface are always enabled
if (is_a($plugin, 'AppPluginInterface') && !$plugin->isEnabled()) {
if ($recursive) {
if (!$plugin->isStatusChanged()) {
$plugin->setEnabled(true, true, true);
} else {
throw new RuntimeException(
"Unable to enable plugin '" . get_called_class() . "': "
. "Required plugin '" . $pluginName . "' was disabled manually"
);
}
} else {
throw new RuntimeException(
"Unable to enable plugin '" . get_called_class() . "': "
. "Required plugin '" . $pluginName . "' is disabled"
);
}
}
}
}
/**
* @see AppPluginInterface::getDependencies()
*/
public function getDependencies()
{
return (array) $this->dependsOn;
}
/**
* Disables all plugins which depend on this plugin
*
* @see AppPluginInterface::getDependants()
* @param boolean $recursive disabled dependant plugins automatically
* @return void
* @throws RuntimeException thrown when a dependency fails
*/
protected function checkDependants($recursive)
{
$dependants = $this->getDependants();
if (!empty($dependants)) {
if ($recursive) {
foreach ($this->getDependants() as $pluginName => $plugin) {
if ($plugin->isEnabled()) {
if (!$plugin->isStatusChanged()) {
$plugin->setEnabled(false, true, true);
} else {
throw new RuntimeException(
"Unable to disable plugin '" . get_called_class() . "': "
. "Required by manually enabled plugin '" . $pluginName . "'"
);
}
}
}
} else {
$dependantsList = 'plugin' . ((count($dependants) > 1) ? 's' : '') . ' ';
$dependantsList .= "'" . implode("', '", array_keys($dependants)) . "'";
throw new RuntimeException(
"Unable to disable plugin '" . get_called_class() . "': "
. "Required by " . $dependantsList
);
}
}
}
/**
* @see AppPluginInterface::getDependants()
*/
public function getDependants()
{
if ($this->dependants === null) {
$this->dependants = array();
foreach ($this->getPlugins() as $pluginName => $plugin) {
// only plugins which implement AppPluginInterface support dependencies
if (is_a($plugin, 'AppPluginInterface')) {
$dependencies = $plugin->getDependencies();
if (in_array(get_called_class(), $dependencies)) {
$this->dependants[$pluginName] = $plugin;
}
}
}
}
return $this->dependants;
}
}
我在您的 classes 中没有看到任何名称空间?您可以添加 classes 如何映射到文件系统吗?有一个 PHP 标准称为 PSR-4,它很常用。如果这不符合您的要求,composer 提供了一些替代的自动加载选项,您可以在插件的 composer.json.
中指定这些选项
参见:https://getcomposer.org/doc/04-schema.md#autoload
例如,如果您的插件仅包含保存在文件 src/AppHelper.php
中的 AppHelper class 和抽象基础 class,您可以添加一个 class将这 2 个文件映射到您的 composer.json:
...
"autoload" {
"classmap": ["src/AppHelper.php", "src/AbstractAppHelper.php"]
},
...
因为两个 classes 都映射到 classes 这与 PSR-0 一样有效:
...
"autoload" {
"psr-0": {
"\": "src/"
}
},
...
这会将 src 目录中的所有 php 文件添加到相应的 class,因此:
src/AppHelper.php -> class AppHelper
src/AbstractAppHelper.php -> class AbstractAppHelper
等等。你可能 运行 在映射到全局名称空间时遇到麻烦。为这些 classes 使用自定义命名空间可能是更好的选择,通常可以借助一些工具快速完成,例如 PhpStorm 中的重构助手。
编辑:抱歉,我错过了 link 到实际存储库。在您的情况下,它可能看起来像:
"autoload": {
"psr-0": {
"\": "master/plugins",
}
}
我希望基于pico CMS and call it in a protected function in my main class, called App
设计一个简单的插件。
插件名为 AppHelper
。
尝试
我在App
上面加了一个required_once
class:
require_once "/Library/WebServer/usco/master/plugins/AppHelper.php";
并使用这个进行测试(第一个 AppHelper::isMobile
是不正确的):
// ********************MOBILE OR DESKTOP***********************//
if (AppHelper::isMobile($_SERVER['HTTP_USER_AGENT'])) {
$viewSetting = $_SESSION["view-setting"] = array("device" => "mobile"); // IF MOBILE
} else {
$viewSetting = $_SESSION["view-setting"] = array("device" => "desktop"); // IF DESKTOP
}
// ********************MOBILE OR DESKTOP***********************//
在受保护的方法中:
protected function loadConfig()
{
$config = null;
if (file_exists($this->getConfigDir() . 'config.php')) {
require $this->getConfigDir() . 'config.php';
}
// ********************MOBILE OR DESKTOP***********************//
if (AppHelper::isMobile($_SERVER['HTTP_USER_AGENT'])) {
$viewSetting = $_SESSION["view-setting"] = array("device" => "mobile"); // IF MOBILE
} else {
$viewSetting = $_SESSION["view-setting"] = array("device" => "desktop"); // IF DESKTOP
}
// ********************MOBILE OR DESKTOP***********************//
$defaultConfig = array(
'company_name' => 'USCO',
'page_title' => 'USCO',
'base_url' => '',
'rewrite_url' => null,
'date_format' => '%D %T',
'twig_config' => null,
'blog_content_dir' => null,
'blog_content_path' => null,
'blog_articles_dir' => null,
'blog_articles_path' => null,
'blog_content_ext' => '.md',
'device' => $viewSetting["device"],
'timezone' => '',
);
$this->config = is_array($this->config) ? $this->config : array();
$this->config += is_array($config) ? $config + $defaultConfig : $defaultConfig;
if (empty($this->config['base_url'])) {
$this->config['base_url'] = $this->getBaseUrl();
} else {
$this->config['base_url'] = rtrim($this->config['base_url'], '/') . '/';
}
if ($this->config['rewrite_url'] === null) {
$this->config['rewrite_url'] = $this->isUrlRewritingEnabled();
}
$local_domain = "usco.loc";
if ($_SERVER['SERVER_NAME'] === $local_domain) {
$defaultTwigConfig = array('cache' => false, 'autoescape' => false, 'debug' => true);
// $defaultTwigConfig = array('cache' => __DIR__ . "/../cache", 'autoescape' => false, 'debug' => false);
} else {
$defaultTwigConfig = array('cache' => __DIR__ . "/../cache", 'autoescape' => false, 'debug' => false);
}
if (!is_array($this->config['twig_config'])) {
$this->config['twig_config'] = $defaultTwigConfig;
} else {
$this->config['twig_config'] += $defaultTwigConfig;
}
$this->config['blog_content_path'] = $this->getAbsolutePath($this->config['blog_content_dir'], true);
$this->config['blog_articles_path'] = $this->config['blog_content_path'] . $this->config['blog_articles_dir'];
if (empty($this->config['timezone'])) {
// explicitly set a default timezone to prevent a E_NOTICE
// when no timezone is set; the `date_default_timezone_get()`
// function always returns a timezone, at least UTC
$this->config['timezone'] = @date_default_timezone_get();
}
date_default_timezone_set($this->config['timezone']);
}
并且工作正常,只是一个警告。然而,这是不对的。
How do I correctly design/add this plugin without
required_once
and autoload it usingcomposer install
, like all my other plugins that function so?
AppHelper
class AppHelper extends AbstractAppPlugin
{
public function __construct(App $app)
{
parent::__construct($app);
}
public function isMobile($useragent)
{
if (
preg_match('/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i', $useragent)
||
preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i', substr($useragent, 0, 4))
) {
return true; // is mobile
} else {
return false; // is not mobile
};
}
}
AbstractAppPlugin
abstract class AbstractAppPlugin implements AppPluginInterface
{
/**
* Current instance of App
*
* @see AppPluginInterface::getApp()
* @var App
*/
private $app;
/**
* Boolean indicating if this plugin is enabled (true) or disabled (false)
*
* @see AppPluginInterface::isEnabled()
* @see AppPluginInterface::setEnabled()
* @var boolean
*/
protected $enabled = true;
/**
* Boolean indicating if this plugin was ever enabled/disabled manually
*
* @see AppPluginInterface::isStatusChanged()
* @var boolean
*/
protected $statusChanged = false;
/**
* List of plugins which this plugin depends on
*
* @see AbstractAppPlugin::checkDependencies()
* @see AppPluginInterface::getDependencies()
* @var string[]
*/
protected $dependsOn = array();
/**
* List of plugin which depend on this plugin
*
* @see AbstractAppPlugin::checkDependants()
* @see AppPluginInterface::getDependants()
* @var object[]
*/
private $dependants;
/**
* @see AppPluginInterface::__construct()
*/
public function __construct(App $app)
{
$this->app = $app;
}
/**
* @see AppPluginInterface::handleEvent()
*/
public function handleEvent($eventName, array $params)
{
// plugins can be enabled/disabled using the config
if ($eventName === 'onConfigLoaded') {
$pluginEnabled = $this->getConfig(get_called_class() . '.enabled');
if ($pluginEnabled !== null) {
$this->setEnabled($pluginEnabled);
} else {
$pluginConfig = $this->getConfig(get_called_class());
if (is_array($pluginConfig) && isset($pluginConfig['enabled'])) {
$this->setEnabled($pluginConfig['enabled']);
} elseif ($this->enabled) {
// make sure dependencies are already fulfilled,
// otherwise the plugin needs to be enabled manually
try {
$this->checkDependencies(false);
} catch (RuntimeException $e) {
$this->enabled = false;
}
}
}
}
if ($this->isEnabled() || ($eventName === 'onPluginsLoaded')) {
if (method_exists($this, $eventName)) {
call_user_func_array(array($this, $eventName), $params);
}
}
}
/**
* @see AppPluginInterface::setEnabled()
*/
public function setEnabled($enabled, $recursive = true, $auto = false)
{
$this->statusChanged = (!$this->statusChanged) ? !$auto : true;
$this->enabled = (bool) $enabled;
if ($enabled) {
$this->checkDependencies($recursive);
} else {
$this->checkDependants($recursive);
}
}
/**
* @see AppPluginInterface::isEnabled()
*/
public function isEnabled()
{
return $this->enabled;
}
/**
* @see AppPluginInterface::isStatusChanged()
*/
public function isStatusChanged()
{
return $this->statusChanged;
}
/**
* @see AppPluginInterface::getApp()
*/
public function getApp()
{
return $this->app;
}
/**
* Passes all not satisfiable method calls to App
*
* @see App
* @param string $methodName name of the method to call
* @param array $params parameters to pass
* @return mixed return value of the called method
*/
public function __call($methodName, array $params)
{
if (method_exists($this->getApp(), $methodName)) {
return call_user_func_array(array($this->getApp(), $methodName), $params);
}
throw new BadMethodCallException(
'Call to undefined method ' . get_class($this->getApp()) . '::' . $methodName . '() '
. 'through ' . get_called_class() . '::__call()'
);
}
/**
* Enables all plugins which this plugin depends on
*
* @see AppPluginInterface::getDependencies()
* @param boolean $recursive enable required plugins automatically
* @return void
* @throws RuntimeException thrown when a dependency fails
*/
protected function checkDependencies($recursive)
{
foreach ($this->getDependencies() as $pluginName) {
try {
$plugin = $this->getPlugin($pluginName);
} catch (RuntimeException $e) {
throw new RuntimeException(
"Unable to enable plugin '" . get_called_class() . "': "
. "Required plugin '" . $pluginName . "' not found"
);
}
// plugins which don't implement AppPluginInterface are always enabled
if (is_a($plugin, 'AppPluginInterface') && !$plugin->isEnabled()) {
if ($recursive) {
if (!$plugin->isStatusChanged()) {
$plugin->setEnabled(true, true, true);
} else {
throw new RuntimeException(
"Unable to enable plugin '" . get_called_class() . "': "
. "Required plugin '" . $pluginName . "' was disabled manually"
);
}
} else {
throw new RuntimeException(
"Unable to enable plugin '" . get_called_class() . "': "
. "Required plugin '" . $pluginName . "' is disabled"
);
}
}
}
}
/**
* @see AppPluginInterface::getDependencies()
*/
public function getDependencies()
{
return (array) $this->dependsOn;
}
/**
* Disables all plugins which depend on this plugin
*
* @see AppPluginInterface::getDependants()
* @param boolean $recursive disabled dependant plugins automatically
* @return void
* @throws RuntimeException thrown when a dependency fails
*/
protected function checkDependants($recursive)
{
$dependants = $this->getDependants();
if (!empty($dependants)) {
if ($recursive) {
foreach ($this->getDependants() as $pluginName => $plugin) {
if ($plugin->isEnabled()) {
if (!$plugin->isStatusChanged()) {
$plugin->setEnabled(false, true, true);
} else {
throw new RuntimeException(
"Unable to disable plugin '" . get_called_class() . "': "
. "Required by manually enabled plugin '" . $pluginName . "'"
);
}
}
}
} else {
$dependantsList = 'plugin' . ((count($dependants) > 1) ? 's' : '') . ' ';
$dependantsList .= "'" . implode("', '", array_keys($dependants)) . "'";
throw new RuntimeException(
"Unable to disable plugin '" . get_called_class() . "': "
. "Required by " . $dependantsList
);
}
}
}
/**
* @see AppPluginInterface::getDependants()
*/
public function getDependants()
{
if ($this->dependants === null) {
$this->dependants = array();
foreach ($this->getPlugins() as $pluginName => $plugin) {
// only plugins which implement AppPluginInterface support dependencies
if (is_a($plugin, 'AppPluginInterface')) {
$dependencies = $plugin->getDependencies();
if (in_array(get_called_class(), $dependencies)) {
$this->dependants[$pluginName] = $plugin;
}
}
}
}
return $this->dependants;
}
}
我在您的 classes 中没有看到任何名称空间?您可以添加 classes 如何映射到文件系统吗?有一个 PHP 标准称为 PSR-4,它很常用。如果这不符合您的要求,composer 提供了一些替代的自动加载选项,您可以在插件的 composer.json.
中指定这些选项参见:https://getcomposer.org/doc/04-schema.md#autoload
例如,如果您的插件仅包含保存在文件 src/AppHelper.php
中的 AppHelper class 和抽象基础 class,您可以添加一个 class将这 2 个文件映射到您的 composer.json:
...
"autoload" {
"classmap": ["src/AppHelper.php", "src/AbstractAppHelper.php"]
},
...
因为两个 classes 都映射到 classes 这与 PSR-0 一样有效:
...
"autoload" {
"psr-0": {
"\": "src/"
}
},
...
这会将 src 目录中的所有 php 文件添加到相应的 class,因此:
src/AppHelper.php -> class AppHelper
src/AbstractAppHelper.php -> class AbstractAppHelper
等等。你可能 运行 在映射到全局名称空间时遇到麻烦。为这些 classes 使用自定义命名空间可能是更好的选择,通常可以借助一些工具快速完成,例如 PhpStorm 中的重构助手。
编辑:抱歉,我错过了 link 到实际存储库。在您的情况下,它可能看起来像:
"autoload": {
"psr-0": {
"\": "master/plugins",
}
}