Page MenuHomePhabricator

No OneTemporary

diff --git a/core/exceptions/FailureException.php b/core/exceptions/FailureException.php
new file mode 100644
index 0000000..5e46972
--- /dev/null
+++ b/core/exceptions/FailureException.php
@@ -0,0 +1,46 @@
+<?php namespace spitfire\core\exceptions;
+
+use Exception;
+use spitfire\exceptions\PublicExceptionInterface;
+
+/**
+ * A failure exception is a standard exception that is thrown when the application
+ * wishes to report a failure to the user. This is a very generic type, and should
+ * whenever possible be replaced by more specific issues that allow the application
+ * to attempt to resolve the issue or give further details to the end-user.
+ *
+ * Spitfire will then use an error page that is appropriate for the issue that
+ * was raised. This is selected by the exception code. So a failure with a 500
+ * error code will show a system error, while a failure with a 404 code should
+ * present the user with a dialog that informs them that the resource can't be
+ * located.
+ *
+ * @author César de la Cal Bretschneider <cesar@magic3w.com>
+ */
+class FailureException extends Exception implements PublicExceptionInterface
+{
+
+ /**
+ * Creates a new failure exception. This indicates that the application can't
+ * continue and needs to be terminated.
+ *
+ * @param string $message
+ * @param int $code
+ * @param \Throwable $previous
+ */
+ public function __construct(string $message = "", int $code = 500, \Throwable $previous = NULL)
+ {
+ parent::__construct($message, $code, $previous);
+ }
+
+ /**
+ * In this kind of exception, the http code is directly correlated with the
+ * code of the exception.
+ *
+ * @return int
+ */
+ public function httpCode()
+ {
+ return $this->getCode();
+ }
+}
diff --git a/core/functions.php b/core/functions.php
index 4b92b80..a508a8a 100644
--- a/core/functions.php
+++ b/core/functions.php
@@ -1,520 +1,541 @@
<?php
use spitfire\App;
use spitfire\collection\Collection;
use spitfire\core\ContextInterface;
use spitfire\core\Environment;
use spitfire\core\http\URL;
use spitfire\exceptions\ExceptionHandler;
use spitfire\exceptions\ExceptionHandlerCLI;
use spitfire\cli\Console;
+use spitfire\core\exceptions\FailureException;
use spitfire\io\request\Request;
use spitfire\io\media\FFMPEGManipulator;
use spitfire\io\media\GDManipulator;
use spitfire\io\media\ImagickManipulator;
use spitfire\io\media\MediaDispatcher;
use spitfire\locale\Domain;
use spitfire\locale\DomainGroup;
use spitfire\locale\Locale;
use spitfire\SpitFire;
use spitfire\SpitFireCLI;
use spitfire\storage\database\Settings;
use spitfire\storage\objectStorage\DriveDispatcher;
use spitfire\storage\objectStorage\NodeInterface;
use spitfire\utils\Strings;
use spitfire\validation\rules\RegexValidationRule;
use spitfire\validation\ValidationException;
use spitfire\validation\ValidationRule;
/**
* This is a quick hand method to use Spitfire's main App class as a singleton.
* It allows you to quickly access many of the components the framework provides
* to make it easier to read and maintain the code being created.
*
* @staticvar type $sf
* @return \spitfire\SpitFire
*/
function spitfire() {
static $sf = null;
if ($sf !== null) {
return $sf;
} else {
$sf = php_sapi_name() === 'cli'? new SpitFireCLI() : new SpitFire();
return $sf;
}
}
/**
*
* Registers a new Application in Spitfire, allowing it to handle requests directed
* to it.
*
* @param string $name The name of the Application
* @param string $namespace The namespace in which the requests will be sent to
* the application.
* @return App The App created by the system, use this to pass parameters and
* configuration to the application.
*/
function app($name, $namespace) {
$appName = $name . 'App';
$app = new $appName(APP_DIRECTORY . $name . DIRECTORY_SEPARATOR, $namespace);
spitfire()->registerApp($app, $namespace);
return $app;
}
/**
* Raises an exception whenever the application runs into an scenario that was not
* expected. This function functions in a very similar manner to an assertion (with
* the exception of this being executed in production).
*
* The second parameter contains the name of the exception to be raised on failure,
* an exception instance or a closure to be executed, which should then either end
* execution or throw an exception itself.
*
* This is intended to reduce the boilerplate on code that looks like this:
* if (!$condition) { throw new Exception(); }
*
* The code becomes a bit more readable and drops the negation, making it look like this:
* assume($condition, Exception::class);
*
* Overall reducing the strain of visually analyzing the code.
*
* @param bool $condition If the condition is true, the code will continue being executed
* @param string|Closure|Exception $failure
* @return void
*/
function assume(bool $condition, $failure) : void
{
/*
* If the condition is met, the function doesn't do anything. It just returns and allows
* the application to continue.
*/
if ($condition) { return; }
/**
* Otherwise, we need to stop the execution. This should be done in the closure, but if the
* user does not raise any exception in the closure, our code will do.
*/
if ($failure instanceof Closure) {
$failure();
throw new Exception('Failed to meet assumption');
}
/**
* If the user provided an exception instance, we throw the provided exception.
*/
if ($failure instanceof Exception) { throw $failure; }
/**
* The last case (if the failure code was a string) will instance a new exception without message,
* this is the most common way to use this, since it will be the mechanism activated when using
* the ::class magic constant.
*/
throw new $failure();
}
+/**
+ * This function allows the developer to stop the execution of the application.
+ * It raises a failure exception that will prevent the code from continuing and
+ * display a message to the end user that explains the situation.
+ *
+ * Whenever your application runs into a more specific issue, you should avoid
+ * using this and instead raise a custom exception that offers the user support
+ * to resolve the issue or additional information.
+ *
+ *
+ * @param int $status
+ * @param string $message
+ * @return void
+ * @throws FailureException
+ */
+function fail(int $status, string $message = '') : void
+{
+ throw new FailureException($message, $status);
+}
+
/**
* Shorthand function to create / retrieve the model the application is using
* to store data. We could consider this a little DB handler factory.
*
* @param Settings $options
* @return \spitfire\storage\database\DB
*/
function db(Settings$options = null) {
static $db = null;
#If we're requesting the standard driver and have it cached, we use this
if ($options === null && $db !== null) { return $db; }
#If no options were passed, we try to fetch them from the environment
$settings = Settings::fromURL($options? : Environment::get('db'));
#Instantiate the driver
$driver = 'spitfire\storage\database\drivers\\' . $settings->getDriver() . '\Driver';
$driver = new $driver($settings);
#If no options were provided we will assume that this is the standard DB handler
if ($options === null) { $db = $driver; }
#Return the driver
return $driver;
}
/**
* Escapes quotes from HTML. This will replace both ' and " with &#039; and &quot;
* respectively. This does not escape the other HTML entities, for that refer to
* _e();
*
* Usually you should use this function like _q(_e($str));
*
* @param type $str
* @return type
*/
function _q($str) {
return \spitfire\utils\Strings::quote($str);
}
/**
* Escapes the HTML from a string. This function will not escape the quotes, please
* refer to the _q wrapper for that.
*
* @see _q()
* @param string $str
* @return string
*/
function _e($str) {
return \spitfire\utils\Strings::escape($str);
}
/**
* Helper to convert URLs in text to actual links. By default, the application will
* translate the URL to a pure HTML link, but you can pass a callable to the system
* to change the output.
*
* @param string $str
* @param Closure|callable $cb
* @return string
*/
function _u($str, $cb = null) {
return \spitfire\utils\Strings::urls($str, $cb);
}
/**
* Returns HTML escaped string and if desired it adds ellipsis. If the string is
* numeric it will reduce unnecessary decimals.
*
* @param String $str
* @param int $maxlength
* @return String
*/
function __($str, $maxlength = false) {
if ($maxlength) { $str = \spitfire\utils\Strings::ellipsis ($str, $maxlength); }
return _u($str);
}
/**
* Translation helper.
*
* Depending on the arguments this function receives, it will have one of several
* behaviors.
*
* If the first argument is a spitfire\locale\Locale and the function receives a
* optional second parameter, then it will assign the locale to either the global
* domain / the domain provided in the second parameter.
*
* Otherwise, if the first parameter is a string, it will call the default locale's
* say method. Which will translate the string using the standard locale.
*
* If no parameters are provided, this function returns a DomainGroup object,
* which provides access to the currency and date functions as well as the other
* domains that the system has for translations.
*
* @return string|DomainGroup
*/
function _t() {
static $domains = null;
#If there are no domains we need to set them up first
if ($domains === null) { $domains = new DomainGroup(); }
#Get the functions arguments afterwards
$args = func_get_args();
#If the first parameter is a Locale, then we proceed to registering it so it'll
#provide translations for the programs
if (isset($args[0]) && $args[0] instanceof Locale) {
$locale = array_shift($args);
$domain = array_shift($args);
return $domains->putDomain($domain, new Domain($domain, $locale));
}
#If the args is empty, then we give return the domains that allow for printing
#and localizing of the data.
if (empty($args)) {
return $domains;
}
return call_user_func_array(Array($domains->getDefault(), 'say'), $args);
}
function current_context(ContextInterface$set = null) {
static $context = null;
if ($set!==null) {$context = $set;}
return $context;
}
function console() {
static $console = null;
if ($console === null) {
$console = new Console();
}
return $console;
}
/**
* Tests a value against a provided set of rules, if the
*/
function validate($value, $rules) : void
{
$messages = collect();
collect($rules)
->each(function ($e) {
if ($e instanceof ValidationRule) { return $e; }
if ($e instanceof Closure) { return new ClosureValidationRule($e); }
if (is_string($e)) { return new RegexValidationRule($e, 'Invalid format'); }
throw new ValidationException('Invalid validation rules');
})
->each(function (ValidationRule $rule) use ($messages, $value) {
$messages->push($rule->test($value));
})->filter();
if (!$messages->isEmpty()) {
throw new ValidationException('Validation failure', 2102011631, $messages->toArray());
}
}
/**
* Retrieves the current path from the request. This will retrieve the path
* without query string or document root.
*
* @see http://www.spitfirephp.com/wiki/index.php/NgiNX_Configuration For NGiNX setup
* @return string
*/
function getPathInfo() {
$base_url = spitfire()->baseUrl();
list($path) = explode('?', substr($_SERVER['REQUEST_URI'], strlen($base_url)));
if (strlen($path) !== 0) { return $path; }
else { return '/'; }
}
function _def(&$a, $b) {
return ($a)? $a : $b;
}
/**
* This function is a shorthand for "new Collection" which also allows fluent
* usage of the collection in certain environments where the PHP version still
* limits that behavior.
*
* @param mixed $elements
* @return Collection
*/
function collect($elements = []) {
return new Collection($elements);
}
/**
* Creates a new URL. Use this class to generate dynamic URLs or to pass
* URLs as parameters. For consistency (double base prefixes and this
* kind of misshaps aren't funny) use this object to pass or receive URLs
* as paramaters.
*
* Please note that when passing a URL that contains the URL as a string like
* "/hello/world?a=b&c=d" you cannot pass any other parameters. It implies that
* you already have a full URL.
*
* You can pass any amount of parameters to this class,
* the constructor will try to automatically parse the URL as good as possible.
* <ul>
* <li>Arrays are used as _GET</li>
* <li>App objects are used to identify the namespace</li>
* <li>Strings that contain / or ? will be parsed and added to GET and path</li>
* <li>The rest of strings will be pushed to the path.</li>
* </ul>
*/
function url() {
#Get the parameters the first time
$params = func_get_args();
#Get the controller, and the action
$controller = null;
$action = null;
$object = Array();
#Get the object
while(!empty($params) && (!is_array(reset($params)) || (!$controller && $app->getControllerLocator()->hasController(reset($params))))) {
if (!$controller) { $controller = array_shift($params); }
elseif (!$action) { $action = array_shift($params); }
else { $object[] = array_shift($params); }
}
#Get potential environment variables that can be used for additional information
#like loccalization
$get = array_shift($params);
$environment = array_shift($params);
return new URL($controller, $action, $object, 'php', $get, $environment);
}
/**
* The clamp function is a math function that receives three arguments, a minimum,
* a bias and a maximum. Clamp will either return the bias or the closest value
* whenever the bias is outside the range of maximum and minimum.
*
* The first and the last parameter delimit the range. The second parameter is
* the bias being tested.
*
* <code>within(1, 50, 100); //Outputs: 50</code>
* <code>within(1, 500, 100); //Outputs: 100</code>
* <code>within(1, -50, 100); //Outputs: 1</code>
*
* @param number $min
* @param number $bias
* @param number $max
* @return number
*/
function clamp($min, $bias, $max) {
return min(max($min, $bias), $max);
}
/**
* This is the deprecated naming for the clamp function.
*
* @deprecated since version 0.1-dev 2020-09-29
* @see clamp
* @param number $min
* @param number $val
* @param number $max
* @return number
*/
function within($min, $val, $max) {
trigger_error('Function within() is deprecated, rename it to clamp()', E_USER_DEPRECATED);
return clamp($min, $val, $max);
}
function media() {
static $dispatcher = null;
if (!$dispatcher) {
$dispatcher = new MediaDispatcher();
$dispatcher->register('image/png', new GDManipulator());
$dispatcher->register('image/jpg', new GDManipulator());
$dispatcher->register('image/webp', new GDManipulator());
$dispatcher->register('image/psd', new ImagickManipulator());
$dispatcher->register('image/gif', new FFMPEGManipulator());
$dispatcher->register('video/mp4', new FFMPEGManipulator());
$dispatcher->register('video/quicktime', new FFMPEGManipulator());
$dispatcher->register('image/jpeg', new GDManipulator());
$dispatcher->register('image/vnd.adobe.photoshop', new ImagickManipulator());
}
return $dispatcher;
}
/**
*
* @staticvar type $dispatcher
* @param type $uri
* @return DriveDispatcher|NodeInterface
*/
function storage() {
static $dispatcher = null;
if (!$dispatcher) {
$dispatcher = new \spitfire\storage\objectStorage\DriveDispatcher();
$dispatcher->init();
}
return $dispatcher;
}
function request($url) {
return new Request($url);
}
function mime($file) {
if (function_exists('mime_content_type')) { return mime_content_type($file); }
else { return explode(';', system(sprintf('file -bi %s', escapeshellarg(realpath($file)))))[0]; }
}
function debug() {
static $instance = null;
return $instance? $instance : $instance = php_sapi_name() === 'cli'? new ExceptionHandlerCLI() : new ExceptionHandler();
}
/**
* Allows the application to manage a central event dispatching system, instead
* of relying on every component to build a custom one. If your component doesn't
* wish to share it's hooks and plugins please @see Target
*
* @staticvar \spitfire\core\event\EventDispatcher $dispatcher
* @return \spitfire\core\event\EventDispatcher
*/
function event(\spitfire\core\event\Event$event = null) {
static $dispatcher = null;
if ($dispatcher === null) {
$dispatcher = new \spitfire\core\event\EventDispatcher();
\spitfire\core\event\RecipeLoader::import();
}
if ($event !== null) {
$dispatcher->dispatch($event);
return $event;
}
return $dispatcher;
}
/**
*
* @staticvar \spitfire\io\lock\FileLockFactory $handler
* @param \spitfire\io\lock\FileLockFactory $set
* @return \spitfire\io\lock\FileLockFactory
*/
function lock($set = null) {
static $handler;
if ($set) { $handler = $set; }
if (!$handler) { $handler = new \spitfire\io\lock\FileLockFactory(dirname(dirname(__FILE__)) . '/bin/usr/lock'); }
return $handler;
}
function basedir($set = null)
{
static $override = null;
if ($set) { $override = $set; }
/*
* Three dirnames for:
* 1. engine folder
* 2. spitfire folder
* 3. vendor folder
*/
return $override?: rtrim(dirname(dirname(dirname(__DIR__))), '\/') . DIRECTORY_SEPARATOR;
}
function asset($name = null, $app = null) {
if ($app === null) { $app = current_context()->app; }
if ($name === null) {
return '/' . implode('/', array_filter([
trim(SpitFire::baseUrl(), '/'),
trim(Environment::get('assets.directory.deploy'), '/'),
trim($app->url()?? '', '/')]));
}
$path = pathinfo($name, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR . pathinfo($name, PATHINFO_FILENAME);
$extension = pathinfo($name, PATHINFO_EXTENSION);
$preprocessor = Environment::get('assets.preprocessors.' . $extension);
if ($preprocessor) { $extension = (new $preprocessor())->extension($extension); }
return '/' . implode('/', array_filter([
trim(SpitFire::baseUrl(), '/'),
trim(Environment::get('assets.directory.deploy'), '/'),
trim($app->url()?? '', '/'),
trim($path . '.' . $extension, '/')
])
);
}
diff --git a/tests/core/FailTest.php b/tests/core/FailTest.php
new file mode 100644
index 0000000..b2c7843
--- /dev/null
+++ b/tests/core/FailTest.php
@@ -0,0 +1,39 @@
+<?php
+
+use PHPUnit\Framework\TestCase;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2021 César de la Cal Bretschneider <cesar@magic3w.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+class FailTest extends TestCase
+{
+
+ public function testFail()
+ {
+ $this->expectException(\spitfire\core\exceptions\FailureException::class);
+ $this->expectExceptionCode(404);
+ fail(404, 'User not found');
+ }
+
+}

File Metadata

Mime Type
text/x-diff
Expires
Apr 13 2021, 12:50 AM (9 w, 20 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5870
Default Alt Text
(19 KB)

Event Timeline