]> git.mxchange.org Git - core.git/blob - framework/main/classes/feature/class_FrameworkFeature.php
3d2fbeb7f227f5f43e4f4a93ef10106ee86693c9
[core.git] / framework / main / classes / feature / class_FrameworkFeature.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Feature;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Generic\FrameworkInterface;
9 use Org\Mxchange\CoreFramework\Loader\NoClassException;
10 use Org\Mxchange\CoreFramework\Object\BaseFrameworkSystem;
11 use Org\Mxchange\CoreFramework\Utils\Strings\StringUtils;
12
13 // Import SPL stuff
14 use \BadMethodCallException;
15 use \InvalidArgumentException;
16
17 /**
18  * The general feature management class. No instance is needed as this class
19  * has only public methods that are static.
20  *
21  * @author              Roland Haeder <webmaster@ship-simu.org>
22  * @version             0.0.0
23  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
24  * @license             GNU GPL 3.0 or any newer version
25  * @link                http://www.ship-simu.org
26  *
27  * This program is free software: you can redistribute it and/or modify
28  * it under the terms of the GNU General Public License as published by
29  * the Free Software Foundation, either version 3 of the License, or
30  * (at your option) any later version.
31  *
32  * This program is distributed in the hope that it will be useful,
33  * but WITHOUT ANY WARRANTY; without even the implied warranty of
34  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
35  * GNU General Public License for more details.
36  *
37  * You should have received a copy of the GNU General Public License
38  * along with this program. If not, see <http://www.gnu.org/licenses/>.
39  */
40 class FrameworkFeature extends BaseFrameworkSystem {
41         // Exception code constants
42         const EXCEPTION_FEATURE_METHOD_NOT_CALLABLE = 0x400;
43
44         /**
45          * "Cache" for enabled, available feature instances
46          *
47          * A typical available entry looks like this:
48          *
49          * array(
50          *     'is_enabled'   => true,
51          *     'is_available' => true,
52          *     'instance'     => SomeFeature Object
53          * )
54          *
55          * And a typical disabled entry looks like this:
56          *
57          * array(
58          *     'is_enabled'   => false,
59          *     'is_available' => false,
60          *     'instance'     => NULL
61          * )
62          */
63         private static $enabledFeatures = [];
64
65         /**
66          * Protected constructor
67          *
68          * @return      void
69          */
70         private function __construct () {
71                 // Call parent constructor
72                 parent::__construct(__CLASS__);
73         }
74
75         /**
76          * Checks whether the given feature is enabled in configuration. The user
77          * shall be able to disable features, even when they *could* be available.
78          *
79          * @param       $featureName    Name of the feature to be checked
80          * @return      $isEnabled              Whether the given feature is enabled
81          * @throws      InvalidArgumentException        If a parameter is invalid
82          */
83         public static function isFeatureEnabled (string $featureName) {
84                 // Check parameter
85                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('FRAMEWORK-FEATURE: featureName=%s - CALLED!', $featureName));
86                 if (empty($featureName)) {
87                         // Throw IAE
88                         throw new InvalidArgumentException('Parameter "featureName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
89                 }
90
91                 // Is the cache set?
92                 if (!isset(self::$enabledFeatures[$featureName]['is_enabled'])) {
93                         // Generate config key
94                         $configKey = sprintf('enable_feature_%s', $featureName);
95
96                         // Check configuration
97                         self::$enabledFeatures[$featureName]['is_enabled'] = (FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($configKey) === 'Y');
98                 }
99
100                 // Return "cached" status
101                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('FRAMEWORK-FEATURE: is_enabled[%s]=%d - EXIT!', $featureName, intval(self::$enabledFeatures[$featureName]['is_enabled'])));
102                 return self::$enabledFeatures[$featureName]['is_enabled'];
103         }
104
105         /**
106          * Checks whether the given feature is enabled and available. It may be
107          * enabled by the user, but is not available due to e.g. a missing PECL
108          * extension or whatever is needed to have this feature available. If you
109          * don't write a pre filters for checking PHP requirements, this is the
110          * method you want to use.
111          *
112          * @param       $featureName    Name of the feature to be checked on availability
113          * @return      $isAvailable    Whether the given feature is available
114          * @throws      InvalidArgumentException        If a parameter is invalid
115          */
116         public static function isFeatureAvailable (string $featureName) {
117                 // Check parameter
118                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('FRAMEWORK-FEATURE: featureName=%s - CALLED!', $featureName));
119                 if (empty($featureName)) {
120                         // Throw IAE
121                         throw new InvalidArgumentException('Parameter "featureName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
122                 }
123
124                 // Is the cache set?
125                 if (!isset(self::$enabledFeatures[$featureName]['is_available'])) {
126                         // Default is not available
127                         self::$enabledFeatures[$featureName]['is_available'] = false;
128                         self::$enabledFeatures[$featureName]['instance']     = NULL;
129
130                         // Is the feature enabled?
131                         if (!self::isFeatureEnabled($featureName)) {
132                                 // Then it can't be available
133                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('FRAMEWORK-FEATURE: Feature "%s"is not enabled. - EXIT!', $featureName));
134                                 return false;
135                         }
136
137                         // Create config key (for feature class lookup)
138                         $configKey = sprintf('feature_%s_class', $featureName);
139
140                         // Now try to get the instance
141                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('FRAMEWORK-FEATURE: configKey=%s', $configKey));
142                         try {
143                                 // Try to get an instance
144                                 self::$enabledFeatures[$featureName]['instance'] = ObjectFactory::createObjectByConfiguredName($configKey);
145
146                                 // Now let the feature test itself's availability
147                                 self::$enabledFeatures[$featureName]['is_available'] = self::$enabledFeatures[$featureName]['instance']->isFeatureAvailable();
148                         } catch (NoClassException $e) {
149                                 // Feature class not found
150                                 self::createDebugInstance(__CLASS__, __LINE__)->warningMessage(sprintf('FRAMEWORK-FEATURE: Feature "%s"is not available due to missing feature class. Disabling feature ...', $featureName));
151                         }
152                 }
153
154                 // Return "cached" status
155                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('FRAMEWORK-FEATURE: featureName=%s,isAvailable=%d - EXIT!', $featureName, intval(self::$enabledFeatures[$featureName]['is_available'])));
156                 return self::$enabledFeatures[$featureName]['is_available'];
157         }
158
159         /**
160          * Calls the feature's method and handles some arguments (if not given,
161          * NULL) to it. Any returned value is being forwarded to the caller, even
162          * when the doc-tag says 'void' as returned value.
163          *
164          * @param       $featureName    Name of the feature, it must be available at this point
165          * @param       $featureMethod  Method name of the feature's class
166          * @param       $args                   Any arguments that should be handled over
167          * @return      $return                 Anything the feature's method has returned
168          * @throws      InvalidArgumentException        If a parameter has an invalid value
169          * @throws      BadMethodCallException  If this method has been invoked but the feature isn't available
170          * @throws      FeatureMethodNotCallableException       If the requested method cannot be called
171          */
172         public static function callFeature (string $featureName, string $featureMethod, array $args = NULL) {
173                 // Check parameter
174                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('FRAMEWORK-FEATURE: featureName=%s,featureMethod=%s,args[]=%s - CALLED!', $featureName, $featureMethod, gettype($args)));
175                 if (empty($featureName)) {
176                         // Throw IAE
177                         throw new InvalidArgumentException('Parameter "featureName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
178                 } elseif (empty($featureMethod)) {
179                         // Throw IAE
180                         throw new InvalidArgumentException('Parameter "featureMethod" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
181                 } elseif (!self::isFeatureAvailable($featureName)) {
182                         // Throw BMCE
183                         throw new BadMethodCallException(sprintf('Feature "%s" is not available but method "%s" should be invoked.', $featureName, $featureMethod), FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
184                 }
185
186                 // Array for call-back
187                 $callable = array(
188                         self::$enabledFeatures[$featureName]['instance'],
189                         sprintf('featureMethod%s', StringUtils::convertToClassName($featureMethod))
190                 );
191
192                 // So is the feature's method callable?
193                 if (!is_callable($callable)) {
194                         // Not callable method requested
195                         throw new FeatureMethodNotCallableException(array(self::$enabledFeatures[$featureName]['instance'], $featureMethod), self::EXCEPTION_FEATURE_METHOD_NOT_CALLABLE);
196                 }
197
198                 // Then call it
199                 $return = call_user_func_array($callable, $args);
200
201                 // Return any returned value
202                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('FRAMEWORK-FEATURE: return[]=%s - EXIT!', gettype($return)));
203                 return $return;
204         }
205
206 }