diff --git a/src/Application.php b/src/Application.php
index d40018d..8a9159c 100644
--- a/src/Application.php
+++ b/src/Application.php
@@ -55,11 +55,14 @@
use Framework\Supports\Facades\File;
use Framework\Supports\Str;
use Framework\Supports\Traits\Macroable;
-use Exception;
use Framework\Supports\Arr;
use InvalidArgumentException;
use RuntimeException;
+use function Framework\throw_anyway;
+use function Framework\throw_if;
+use function Framework\throw_unless;
+
class Application extends Container
{
use Macroable;
@@ -497,9 +500,7 @@ public static function configure(string $base_path)
*/
public function use_routing(string $path)
{
- if (!file_exists($path)) {
- throw new Exception("Route file not found: $path");
- }
+ throw_unless(file_exists($path), "Route file not found: $path");
include $path;
@@ -914,15 +915,15 @@ protected function register_app_defined_providers()
}
foreach ($providers as $provider) {
- if (!class_exists($provider) || !is_subclass_of($provider, ServiceProvider::class)) {
- throw new InvalidArgumentException(
- sprintf(
- 'Class %s must be a subclass of %s.',
- $provider,
- ServiceProvider::class
- )
- );
- }
+ throw_if(
+ !class_exists($provider) || !is_subclass_of($provider, ServiceProvider::class),
+ sprintf(
+ 'Class %s must be a subclass of %s.',
+ $provider,
+ ServiceProvider::class
+ ),
+ InvalidArgumentException::class
+ );
$this->register(new $provider($this));
}
@@ -1084,7 +1085,7 @@ public function get_namespace_for_path($relative_path)
}
}
- throw new RuntimeException(sprintf('Unable to detect namespace for path [%s].', $relative_path));
+ throw_anyway(sprintf('Unable to detect namespace for path [%s].', $relative_path), RuntimeException::class);
}
/**
diff --git a/src/Cache/CacheManager.php b/src/Cache/CacheManager.php
index f97adf9..80b0c72 100644
--- a/src/Cache/CacheManager.php
+++ b/src/Cache/CacheManager.php
@@ -28,6 +28,8 @@
use function Framework\app;
use function Framework\config;
+use function Framework\throw_anyway;
+use function Framework\throw_if;
class CacheManager
{
@@ -202,11 +204,11 @@ public function store_config(string $name)
$configured = config('cache.stores');
$configured = is_array($configured) ? $configured : [];
- if (!isset($defaults[$name]) && !isset($configured[$name])) {
- throw new InvalidArgumentException(
- sprintf('Cache store [%s] is not configured. Check the "cache.stores" configuration.', $name)
- );
- }
+ throw_if(
+ !isset($defaults[$name]) && !isset($configured[$name]),
+ sprintf('Cache store [%s] is not configured. Check the "cache.stores" configuration.', $name),
+ InvalidArgumentException::class
+ );
return array_merge($defaults[$name] ?? [], $configured[$name] ?? []);
}
@@ -278,8 +280,9 @@ protected function resolve(string $name)
return $this->create_file_repository($name, $config);
}
- throw new InvalidArgumentException(
- sprintf('Unsupported cache driver [%s] for store [%s].', (string) $driver, $name)
+ throw_anyway(
+ sprintf('Unsupported cache driver [%s] for store [%s].', (string) $driver, $name),
+ InvalidArgumentException::class
);
}
diff --git a/src/Cache/Repository.php b/src/Cache/Repository.php
index dc19ee8..dd60826 100644
--- a/src/Cache/Repository.php
+++ b/src/Cache/Repository.php
@@ -27,6 +27,8 @@
use Throwable;
use function Framework\app;
+use function Framework\throw_if;
+use function Framework\throw_unless;
use function Framework\value;
class Repository implements ArrayAccess
@@ -234,9 +236,7 @@ public function string($key, $default = null)
{
$value = $this->get($key, $default);
- if (!is_string($value)) {
- throw new InvalidArgumentException($this->type_error($key, 'a string', $value));
- }
+ throw_unless(is_string($value), $this->type_error($key, 'a string', $value), InvalidArgumentException::class);
return $value;
}
@@ -257,9 +257,11 @@ public function integer($key, $default = null)
{
$value = $this->get($key, $default);
- if (filter_var($value, FILTER_VALIDATE_INT) === false) {
- throw new InvalidArgumentException($this->type_error($key, 'an integer', $value));
- }
+ throw_if(
+ filter_var($value, FILTER_VALIDATE_INT) === false,
+ $this->type_error($key, 'an integer', $value),
+ InvalidArgumentException::class
+ );
return (int) $value;
}
@@ -280,9 +282,11 @@ public function float($key, $default = null)
{
$value = $this->get($key, $default);
- if (filter_var($value, FILTER_VALIDATE_FLOAT) === false) {
- throw new InvalidArgumentException($this->type_error($key, 'a float', $value));
- }
+ throw_if(
+ filter_var($value, FILTER_VALIDATE_FLOAT) === false,
+ $this->type_error($key, 'a float', $value),
+ InvalidArgumentException::class
+ );
return (float) $value;
}
@@ -303,9 +307,7 @@ public function boolean($key, $default = null)
{
$value = $this->get($key, $default);
- if (!is_bool($value)) {
- throw new InvalidArgumentException($this->type_error($key, 'a boolean', $value));
- }
+ throw_unless(is_bool($value), $this->type_error($key, 'a boolean', $value), InvalidArgumentException::class);
return $value;
}
@@ -326,9 +328,7 @@ public function array($key, $default = null)
{
$value = $this->get($key, $default);
- if (!is_array($value)) {
- throw new InvalidArgumentException($this->type_error($key, 'an array', $value));
- }
+ throw_unless(is_array($value), $this->type_error($key, 'an array', $value), InvalidArgumentException::class);
return $value;
}
@@ -612,11 +612,11 @@ public function flexible($key, array $ttl, Closure $callback)
{
$key = (string) $key;
- if (count($ttl) !== 2) {
- throw new InvalidArgumentException(
- sprintf('The flexible lifetime for key [%s] must be a fresh and stale pair.', $key)
- );
- }
+ throw_if(
+ count($ttl) !== 2,
+ sprintf('The flexible lifetime for key [%s] must be a fresh and stale pair.', $key),
+ InvalidArgumentException::class
+ );
$fresh = (int) $this->seconds_until($ttl[0]);
$stale = (int) $this->seconds_until($ttl[1]);
diff --git a/src/Cache/Stores/FileStore.php b/src/Cache/Stores/FileStore.php
index f3a090e..f545bc5 100644
--- a/src/Cache/Stores/FileStore.php
+++ b/src/Cache/Stores/FileStore.php
@@ -21,6 +21,8 @@
use Framework\Filesystem\Filesystem;
use Throwable;
+use function Framework\throw_if;
+
class FileStore implements Store, CacheEntryProvider
{
use HashesKeys;
@@ -142,11 +144,11 @@ public static function guard_supported()
$method = get_filesystem_method();
- if ($method !== 'direct') {
- throw new StoreUnavailableException(
- sprintf('The file cache store requires direct filesystem access, got [%s].', (string) $method)
- );
- }
+ throw_if(
+ $method !== 'direct',
+ sprintf('The file cache store requires direct filesystem access, got [%s].', (string) $method),
+ StoreUnavailableException::class
+ );
}
/**
diff --git a/src/Collections/Concerns/EnumeratesValues.php b/src/Collections/Concerns/EnumeratesValues.php
index 9c0c26f..7242a31 100644
--- a/src/Collections/Concerns/EnumeratesValues.php
+++ b/src/Collections/Concerns/EnumeratesValues.php
@@ -14,6 +14,7 @@
use Framework\Collections\HigherOrderCollectionProxy;
use function Framework\deep_get;
+use function Framework\throw_unless;
/**
* Trait to enumerate values of the collection.
@@ -113,9 +114,11 @@ protected function value_retriever($value)
*/
public function __get($key)
{
- if (!in_array($key, static::$proxies, true)) {
- throw new Exception(sprintf('Property [%s] does not exist on this collection.', $key));
- }
+ throw_unless(
+ in_array($key, static::$proxies, true),
+ sprintf('Property [%s] does not exist on this collection.', $key),
+ Exception::class
+ );
return new HigherOrderCollectionProxy($this, $key);
}
diff --git a/src/Concerns/DependencyResolvable.php b/src/Concerns/DependencyResolvable.php
index dfcf185..5277c69 100644
--- a/src/Concerns/DependencyResolvable.php
+++ b/src/Concerns/DependencyResolvable.php
@@ -18,6 +18,7 @@
use ReflectionParameter;
use function Framework\app;
+use function Framework\throw_anyway;
trait DependencyResolvable
{
@@ -38,11 +39,11 @@ protected function resolve_primitive(ReflectionParameter $parameter)
return $parameter->getDefaultValue();
}
- throw new ReflectionException(sprintf(
+ throw_anyway(sprintf(
'Unable to resolve primitive parameter "%s" in class "%s".',
$parameter->getName(),
$parameter->getDeclaringClass()->getName()
- ));
+ ), ReflectionException::class);
}
/**
diff --git a/src/Console/CommandManager.php b/src/Console/CommandManager.php
index 99ee483..21abc46 100644
--- a/src/Console/CommandManager.php
+++ b/src/Console/CommandManager.php
@@ -15,6 +15,7 @@
use RuntimeException;
use function Framework\app;
+use function Framework\throw_unless;
class CommandManager
{
@@ -59,18 +60,20 @@ public function register(string $name, $command)
protected function resolve($command)
{
if (is_object($command)) {
- if (!$command instanceof CommandBase) {
- throw new RuntimeException(
- sprintf("Command [%s] must extend [%s]", get_class($command), CommandBase::class)
- );
- }
+ throw_unless(
+ $command instanceof CommandBase,
+ sprintf("Command [%s] must extend [%s]", get_class($command), CommandBase::class),
+ RuntimeException::class
+ );
return $command;
}
- if (!class_exists($command)) {
- throw new RuntimeException(sprintf("Command class [%s] not found", $command));
- }
+ throw_unless(
+ class_exists($command),
+ sprintf("Command class [%s] not found", $command),
+ RuntimeException::class
+ );
return app()->make($command);
}
diff --git a/src/Container.php b/src/Container.php
index 0cd07aa..2dfe021 100644
--- a/src/Container.php
+++ b/src/Container.php
@@ -21,6 +21,9 @@
use LogicException;
use ReflectionNamedType;
+use function Framework\throw_anyway;
+use function Framework\throw_if;
+
class Container implements ContainerContract
{
/**
@@ -188,9 +191,7 @@ public function instance(string $abstract, $instance)
*/
public function alias(string $alias, string $abstract)
{
- if ($alias === $abstract) {
- throw new LogicException(sprintf('[%s] is aliased to itself.', $abstract));
- }
+ throw_if($alias === $abstract, sprintf('[%s] is aliased to itself.', $abstract), LogicException::class);
$this->aliases[$alias] = $abstract;
}
@@ -328,7 +329,7 @@ protected function autowire(string $class, array $parameters = [])
// Check for circular dependencies
if ($this->resolved($class)) {
$chain = implode(' → ', $this->resolved) . " → {$class}";
- throw new Exception(sprintf(
+ throw_anyway(sprintf(
'Circular dependency detected: %s',
$chain
));
@@ -416,7 +417,7 @@ protected function resolve_primitive(ReflectionParameter $parameter, array $prim
return $parameter->getDefaultValue();
}
- throw new Exception(sprintf(
+ throw_anyway(sprintf(
'Unable to resolve primitive parameter "%s" in class "%s".',
$param_name,
$parameter->getDeclaringClass()->getName()
diff --git a/src/CoreServiceProvider.php b/src/CoreServiceProvider.php
index 0f84530..4993dfe 100644
--- a/src/CoreServiceProvider.php
+++ b/src/CoreServiceProvider.php
@@ -37,6 +37,7 @@
use Framework\View\ViewContext;
use function Framework\config;
+use function Framework\throw_anyway;
class CoreServiceProvider extends ServiceProvider
{
@@ -146,8 +147,9 @@ protected function register_session_services()
return new ArraySessionHandler();
}
- throw new InvalidArgumentException(
- sprintf('Unsupported session driver [%s]. Use "database" or "array".', (string) $driver)
+ throw_anyway(
+ sprintf('Unsupported session driver [%s]. Use "database" or "array".', (string) $driver),
+ InvalidArgumentException::class
);
});
diff --git a/src/Database/Concerns/HasAttributes.php b/src/Database/Concerns/HasAttributes.php
index 6b3abe9..40c1e87 100644
--- a/src/Database/Concerns/HasAttributes.php
+++ b/src/Database/Concerns/HasAttributes.php
@@ -28,6 +28,8 @@
use function Framework\collection;
use function Framework\Polyfill\str_contains;
use function Framework\tap;
+use function Framework\throw_anyway;
+use function Framework\throw_if;
trait HasAttributes
{
@@ -199,20 +201,18 @@ protected function get_relationship_from_method($method)
$relation = $this->$method();
if (!$relation instanceof Relation) {
- if (is_null($relation)) {
- throw new LogicException(sprintf(
- '%s::%s must return a relationship instance, '
- . 'but "null" was returned. Was the "return" keyword used?',
- static::class,
- $method,
- ));
- }
+ throw_if(is_null($relation), sprintf(
+ '%s::%s must return a relationship instance, '
+ . 'but "null" was returned. Was the "return" keyword used?',
+ static::class,
+ $method,
+ ), LogicException::class);
- throw new LogicException(sprintf(
+ throw_anyway(sprintf(
'%s::%s must return a relationship instance.',
static::class,
$method,
- ));
+ ), LogicException::class);
}
return tap($relation->get_results(), function ($results) use ($method) {
@@ -547,9 +547,7 @@ protected function cast_attribute_as_json($key, $value)
{
$value = $this->as_json($value);
- if ($value === false) {
- throw new InvalidCastException($this, $key, 'json');
- }
+ throw_if($value === false, $this, InvalidCastException::class, $key, 'json');
return $value;
}
@@ -992,7 +990,7 @@ protected function is_class_castable($key)
return true;
}
- throw new InvalidCastException($this, $key, $cast_type);
+ throw_anyway($this, InvalidCastException::class, $key, $cast_type);
}
/**
diff --git a/src/Database/Concerns/HasRelationships.php b/src/Database/Concerns/HasRelationships.php
index 292a5a4..3d8d902 100644
--- a/src/Database/Concerns/HasRelationships.php
+++ b/src/Database/Concerns/HasRelationships.php
@@ -106,6 +106,7 @@ protected function belongs_to($related, $foreign_key = null, $owner_key = null,
*/
protected function guess_belongs_to_relation()
{
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace -- Introspects the calling method name to guess a relation; not debugging output.
[, , $caller] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
return $caller['function'];
@@ -173,6 +174,7 @@ protected function belongs_to_many(
*/
protected function guess_belongs_to_many_relation()
{
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace -- Introspects the calling method name to guess a relation; not debugging output.
$caller = Arr::first(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function ($trace) {
return !in_array(
$trace['function'],
diff --git a/src/Database/Connection/Connection.php b/src/Database/Connection/Connection.php
index 1a602f1..13a038d 100644
--- a/src/Database/Connection/Connection.php
+++ b/src/Database/Connection/Connection.php
@@ -28,6 +28,8 @@
use function Framework\collection;
use function Framework\Polyfill\str_contains;
+use function Framework\throw_anyway;
+use function Framework\throw_if;
class Connection
{
@@ -111,7 +113,7 @@ protected function connect()
global $wpdb;
$this->db = $wpdb;
} catch (Exception $error) {
- throw new Exception("Database connection failed: " . $error->getMessage());
+ throw_anyway("Database connection failed: " . $error->getMessage());
}
}
@@ -358,16 +360,17 @@ protected function run_query_callback($query, array $bindings, Closure $callback
return $result;
} catch (Exception $error) {
- if ($this->is_unique_constraint_error($error)) {
- throw new UniqueConstraintViolationException(
- $query,
- $this->prepare_bindings($bindings),
- $error
- );
- }
+ throw_if(
+ $this->is_unique_constraint_error($error),
+ $query,
+ UniqueConstraintViolationException::class,
+ $this->prepare_bindings($bindings),
+ $error
+ );
- throw new QueryException(
+ throw_anyway(
$query,
+ QueryException::class,
$this->prepare_bindings($bindings),
$error
);
diff --git a/src/Database/Migrations/Migrator.php b/src/Database/Migrations/Migrator.php
index 0222773..ee70d7e 100644
--- a/src/Database/Migrations/Migrator.php
+++ b/src/Database/Migrations/Migrator.php
@@ -16,6 +16,8 @@
use Framework\Supports\Facades\Schema;
use Exception;
+use function Framework\throw_unless;
+
class Migrator
{
/**
@@ -244,23 +246,23 @@ protected function get_short_name(string $class_name)
*/
protected function validate_migration($migration, string $class_name)
{
- if (!class_exists($class_name)) {
- throw new Exception(
- sprintf(
- 'Class [%s] does not exist',
- $class_name
- )
- );
- }
-
- if (!$migration instanceof Migration) {
- throw new Exception(
- sprintf(
- 'Class [%s] must implements [%s]',
- $class_name,
- Migration::class
- )
- );
- }
+ throw_unless(
+ class_exists($class_name),
+ sprintf(
+ 'Class [%s] does not exist',
+ $class_name
+ ),
+ Exception::class
+ );
+
+ throw_unless(
+ $migration instanceof Migration,
+ sprintf(
+ 'Class [%s] must implements [%s]',
+ $class_name,
+ Migration::class
+ ),
+ Exception::class
+ );
}
}
diff --git a/src/Database/Query/Model.php b/src/Database/Query/Model.php
index 1fb49eb..08d7e6b 100644
--- a/src/Database/Query/Model.php
+++ b/src/Database/Query/Model.php
@@ -33,6 +33,7 @@
use function Framework\app;
use function Framework\Polyfill\str_contains;
+use function Framework\throw_anyway;
abstract class Model implements Arrayable, Jsonable, ArrayAccess, JsonSerializable
{
@@ -859,12 +860,13 @@ public function fill(array $attributes)
if (isset(static::$discarded_attribute_callback)) {
call_user_func(static::$discarded_attribute_callback, $this, [$key]);
} else {
- throw new MassAssignmentException(
+ throw_anyway(
sprintf(
'Add [%s] to fillable array to allow mass assignment on [%s].',
$key,
get_class($this)
- )
+ ),
+ MassAssignmentException::class
);
}
}
@@ -876,12 +878,13 @@ public function fill(array $attributes)
if (isset(static::$discarded_attribute_callback)) {
call_user_func(static::$discarded_attribute_callback, $this, $keys);
} else {
- throw new MassAssignmentException(
+ throw_anyway(
sprintf(
'Add [%s] to fillable array to allow mass assignment on [%s].',
implode(', ', $keys),
get_class($this)
- )
+ ),
+ MassAssignmentException::class
);
}
}
diff --git a/src/Database/Query/QueryBuilder.php b/src/Database/Query/QueryBuilder.php
index 8756d05..d0fb8a8 100644
--- a/src/Database/Query/QueryBuilder.php
+++ b/src/Database/Query/QueryBuilder.php
@@ -37,6 +37,9 @@
use function Framework\Polyfill\array_last;
use function Framework\Polyfill\str_contains;
use function Framework\tap;
+use function Framework\throw_anyway;
+use function Framework\throw_if;
+use function Framework\throw_unless;
use function Framework\value;
/**
@@ -482,9 +485,11 @@ public function from_raw($expression, array $bindings = [])
*/
public function set_bindings(array $bindings, $type = 'where')
{
- if (!array_key_exists($type, $this->bindings)) {
- throw new InvalidArgumentException("Invalid binding type: $type");
- }
+ throw_unless(
+ array_key_exists($type, $this->bindings),
+ "Invalid binding type: $type",
+ InvalidArgumentException::class
+ );
$this->bindings[$type] = $bindings;
@@ -507,9 +512,11 @@ public function add_bindings($value, $type = 'where')
{
$type = strtolower($type);
- if (!array_key_exists($type, $this->bindings)) {
- throw new InvalidArgumentException(sprintf('Invalid binding type: %s', $type));
- }
+ throw_unless(
+ array_key_exists($type, $this->bindings),
+ sprintf('Invalid binding type: %s', $type),
+ InvalidArgumentException::class
+ );
if (is_array($value)) {
$this->bindings[$type] = array_values(
@@ -2828,7 +2835,7 @@ public function first_or_fail($columns = ['*'])
return $model;
}
- throw new ModelNotFoundException(get_class($this->model));
+ throw_anyway(get_class($this->model), ModelNotFoundException::class);
}
/**
@@ -3591,9 +3598,7 @@ public function sole($columns = ['*'])
throw new RecordNotFoundException();
}
- if ($count > 1) {
- throw new MultipleRecordsFoundException($count);
- }
+ throw_if($count > 1, $count, MultipleRecordsFoundException::class);
return $result->first();
}
diff --git a/src/Database/Query/Relations/BelongsToMany.php b/src/Database/Query/Relations/BelongsToMany.php
index 0a4c16d..597ebc8 100644
--- a/src/Database/Query/Relations/BelongsToMany.php
+++ b/src/Database/Query/Relations/BelongsToMany.php
@@ -1035,7 +1035,7 @@ protected function update_existing_pivot($id, array $attributes)
*/
protected function add_timestamps_to_pivot(array &$record, $update = false)
{
- $timestamp = date('Y-m-d H:i:s');
+ $timestamp = gmdate('Y-m-d H:i:s');
if (!$update && !isset($record['created_at'])) {
$record['created_at'] = $timestamp;
diff --git a/src/Database/Schema/Compiler.php b/src/Database/Schema/Compiler.php
index 710a8a2..4b989c2 100644
--- a/src/Database/Schema/Compiler.php
+++ b/src/Database/Schema/Compiler.php
@@ -18,6 +18,10 @@
use Exception;
use Framework\Database\Connection\Connection;
+use function Framework\throw_anyway;
+use function Framework\throw_if;
+use function Framework\throw_unless;
+
class Compiler
{
/**
@@ -340,14 +344,14 @@ public function compile_alter(Structure $structure)
$this->compile_alter_drops($structure)
);
- if (empty($clauses)) {
- throw new Exception(
- sprintf(
- 'No changes were defined for table [%s].',
- $structure->get_table()
- )
- );
- }
+ throw_if(
+ empty($clauses),
+ sprintf(
+ 'No changes were defined for table [%s].',
+ $structure->get_table()
+ ),
+ Exception::class
+ );
return sprintf(
'ALTER TABLE %s %s',
@@ -521,12 +525,13 @@ protected function get_existing_column_definition(Structure $structure, string $
}
}
- throw new Exception(
+ throw_anyway(
sprintf(
'Column [%s] does not exist on table [%s].',
$column,
$structure->get_table()
- )
+ ),
+ Exception::class
);
}
@@ -938,9 +943,11 @@ protected function get_column_type($column)
$column_type = $column->type;
$getter_method = 'type_' . strtolower($column_type);
- if (!method_exists($this, $getter_method)) {
- throw new Exception(sprintf('Method %s not found.', $getter_method));
- }
+ throw_unless(
+ method_exists($this, $getter_method),
+ sprintf('Method %s not found.', $getter_method),
+ Exception::class
+ );
return $this->$getter_method($column);
}
diff --git a/src/Database/Schema/SchemaManager.php b/src/Database/Schema/SchemaManager.php
index cf44af2..379cf79 100644
--- a/src/Database/Schema/SchemaManager.php
+++ b/src/Database/Schema/SchemaManager.php
@@ -16,6 +16,8 @@
use Framework\Database\Connection\Connection;
use Exception;
+use function Framework\throw_if;
+
class SchemaManager
{
/**
@@ -61,9 +63,11 @@ public function create($table, Closure $callback)
$create_sql = $structure->get_table_structure();
$this->connection->get_db()->query($create_sql);
- if (!empty($this->connection->get_db()->last_error)) {
- throw new Exception($this->connection->get_db()->last_error);
- }
+ throw_if(
+ !empty($this->connection->get_db()->last_error),
+ $this->connection->get_db()->last_error,
+ Exception::class
+ );
}
/**
@@ -86,9 +90,11 @@ public function table($table, Closure $callback)
$alter_sql = $structure->get_table_structure();
$this->connection->get_db()->query($alter_sql);
- if (!empty($this->connection->get_db()->last_error)) {
- throw new Exception($this->connection->get_db()->last_error);
- }
+ throw_if(
+ !empty($this->connection->get_db()->last_error),
+ $this->connection->get_db()->last_error,
+ Exception::class
+ );
}
/**
@@ -115,9 +121,11 @@ public function rename(string $from, string $to)
)
);
- if (!empty($this->connection->get_db()->last_error)) {
- throw new Exception($this->connection->get_db()->last_error);
- }
+ throw_if(
+ !empty($this->connection->get_db()->last_error),
+ $this->connection->get_db()->last_error,
+ Exception::class
+ );
}
/**
diff --git a/src/Database/Schema/Structure.php b/src/Database/Schema/Structure.php
index 438d9f2..80828af 100644
--- a/src/Database/Schema/Structure.php
+++ b/src/Database/Schema/Structure.php
@@ -18,6 +18,8 @@
use Framework\Database\Schema\Definitions\ForeignKeyDefinition;
use Exception;
+use function Framework\throw_unless;
+
class Structure
{
/**
@@ -267,9 +269,7 @@ public function engine(string $engine)
{
$available_engines = ['InnoDB', 'MyISAM'];
- if (!in_array($engine, $available_engines)) {
- throw new Exception("Invalid engine: $engine");
- }
+ throw_unless(in_array($engine, $available_engines), "Invalid engine: $engine");
$this->engine = $engine;
}
@@ -869,14 +869,14 @@ public function foreign(string $column, $name = null)
*/
protected function guard_altering(string $operation)
{
- if (!$this->is_altering()) {
- throw new Exception(
- sprintf(
- 'The [%s] operation is only available when altering an existing table.',
- $operation
- )
- );
- }
+ throw_unless(
+ $this->is_altering(),
+ sprintf(
+ 'The [%s] operation is only available when altering an existing table.',
+ $operation
+ ),
+ Exception::class
+ );
}
/**
diff --git a/src/Discovery/ListenerDiscovery.php b/src/Discovery/ListenerDiscovery.php
index a685db7..da71b36 100644
--- a/src/Discovery/ListenerDiscovery.php
+++ b/src/Discovery/ListenerDiscovery.php
@@ -234,6 +234,7 @@ public function cache(?string $path = null)
$path,
"listeners(), true) . ';'
);
diff --git a/src/Discovery/PolicyDiscovery.php b/src/Discovery/PolicyDiscovery.php
index e6e2410..7fe423f 100644
--- a/src/Discovery/PolicyDiscovery.php
+++ b/src/Discovery/PolicyDiscovery.php
@@ -208,6 +208,7 @@ public function cache(?string $path = null)
$path,
"policies(), true) . ';'
);
diff --git a/src/Filesystem/File.php b/src/Filesystem/File.php
index 49cdb3b..f23f4d5 100644
--- a/src/Filesystem/File.php
+++ b/src/Filesystem/File.php
@@ -16,6 +16,10 @@
use InvalidArgumentException;
use SplFileInfo;
+use function Framework\throw_anyway;
+use function Framework\throw_if;
+use function Framework\throw_unless;
+
class File extends SplFileInfo
{
/**
@@ -32,9 +36,11 @@ class File extends SplFileInfo
*/
public function __construct(string $path, bool $check_path = true)
{
- if ($check_path && !file_exists($path)) {
- throw new InvalidArgumentException("File does not exist at path: {$path}");
- }
+ throw_if(
+ $check_path && !file_exists($path),
+ "File does not exist at path: {$path}",
+ InvalidArgumentException::class
+ );
parent::__construct($path);
}
@@ -55,6 +61,7 @@ public function move(string $directory, ?string $name = null)
{
$target = $this->get_target_file($directory, $name);
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler -- Captures a PHP warning from rename()/move_uploaded_file() into a catchable value; restored in the finally block immediately after.
set_error_handler(static function ($type, $msg) use (&$error) {
$error = $msg;
});
@@ -65,16 +72,16 @@ public function move(string $directory, ?string $name = null)
restore_error_handler();
}
- if (!$renamed) {
- throw new Exception(
- sprintf(
- 'Could not move the file "%s" to "%s" (%s).',
- $this->getPathname(),
- $target,
- strip_tags($error ?? '')
- )
- );
- }
+ throw_unless(
+ $renamed,
+ sprintf(
+ 'Could not move the file "%s" to "%s" (%s).',
+ $this->getPathname(),
+ $target,
+ strip_tags($error ?? '')
+ ),
+ Exception::class
+ );
@chmod($target, 0666 & ~umask());
@@ -94,9 +101,7 @@ public function get_content()
{
$content = file_get_contents($this->getPathname());
- if ($content === false) {
- throw new Exception(sprintf('Unable to read the file "%s".', $this->getPathname()));
- }
+ throw_if($content === false, sprintf('Unable to read the file "%s".', $this->getPathname()));
return $content;
}
@@ -116,15 +121,15 @@ public function get_content()
protected function get_target_file(string $directory, ?string $name = null)
{
if (!is_dir($directory) && !@mkdir($directory, 0777, true) && !is_dir($directory)) {
- if (is_file($directory)) {
- throw new Exception(
- sprintf('Unable to create the "%s" directory. A similar named file exists.', $directory)
- );
- }
+ throw_if(
+ is_file($directory),
+ sprintf('Unable to create the "%s" directory. A similar named file exists.', $directory),
+ Exception::class
+ );
- throw new Exception(sprintf('Unable to create the "%s" directory.', $directory));
+ throw_anyway(sprintf('Unable to create the "%s" directory.', $directory));
} elseif (!is_writable($directory)) {
- throw new Exception(sprintf('Unable to write in the "%s" directory.', $directory));
+ throw_anyway(sprintf('Unable to write in the "%s" directory.', $directory));
}
$target = rtrim($directory, '/\\')
diff --git a/src/Filesystem/Fileable.php b/src/Filesystem/Fileable.php
index 01525ff..6d4a954 100644
--- a/src/Filesystem/Fileable.php
+++ b/src/Filesystem/Fileable.php
@@ -10,6 +10,8 @@
defined('ABSPATH') || exit;
+use function Framework\throw_unless;
+
/**
* Fileable interface for filesystem operations.
*
@@ -115,9 +117,11 @@ public function __call($method, $parameters)
{
$filesystem = new Filesystem();
- if (!method_exists($filesystem, $method)) {
- throw new \BadMethodCallException("Method [$method] does not exist on [Filesystem].");
- }
+ throw_unless(
+ method_exists($filesystem, $method),
+ "Method [$method] does not exist on [Filesystem].",
+ \BadMethodCallException::class
+ );
return $filesystem->{$method}(...$this->parameters($method, $parameters));
}
diff --git a/src/Filesystem/Filesystem.php b/src/Filesystem/Filesystem.php
index 11d6d08..e29363d 100644
--- a/src/Filesystem/Filesystem.php
+++ b/src/Filesystem/Filesystem.php
@@ -12,7 +12,6 @@
defined('ABSPATH') || exit;
-use Exception;
use Framework\Exceptions\AuthorizationException;
use Framework\Exceptions\NotFoundException;
use Framework\Sanitizer;
@@ -22,6 +21,8 @@
use function Framework\Polyfill\str_starts_with;
use function Framework\message;
+use function Framework\throw_if;
+use function Framework\throw_unless;
class Filesystem
{
@@ -319,15 +320,11 @@ public function put($path, $data)
*/
public function get($path)
{
- if (!$this->is_file($path)) {
- throw new NotFoundException(message('filesystem.file_not_found', $path));
- }
+ throw_unless($this->is_file($path), message('filesystem.file_not_found', $path), NotFoundException::class);
$contents = $this->filesystem->get_contents($path);
- if ($contents === false) {
- throw new NotFoundException(message('filesystem.file_not_found', $path));
- }
+ throw_if($contents === false, message('filesystem.file_not_found', $path), NotFoundException::class);
return $contents;
}
@@ -566,9 +563,11 @@ public function make(string $path)
*/
public function upload(string $path, UploadedFile $file, $name = null)
{
- if (!current_user_can(Capabilities::UPLOAD_FILES)) {
- throw new AuthorizationException(message('auth.upload_forbidden'));
- }
+ throw_unless(
+ current_user_can(Capabilities::UPLOAD_FILES),
+ message('auth.upload_forbidden'),
+ AuthorizationException::class
+ );
if (is_null($name)) {
$name = $file->get_client_original_name();
@@ -604,17 +603,17 @@ protected function make_upload_directory(string $path)
$upload_directory = wp_upload_dir()['basedir'];
$base = realpath($upload_directory);
- if ($base === false) {
- throw new Exception(message('upload.directory_unavailable'));
- }
+ throw_if($base === false, message('upload.directory_unavailable'));
$relative = Path::normalize($path);
$directory = str_replace('\\', '/', Path::join($base, $relative));
$base_normalized = str_replace('\\', '/', $base);
- if ($directory !== $base_normalized && !str_starts_with($directory, $base_normalized . '/')) {
- throw new AuthorizationException(message('auth.invalid_upload_path'));
- }
+ throw_if(
+ $directory !== $base_normalized && !str_starts_with($directory, $base_normalized . '/'),
+ message('auth.invalid_upload_path'),
+ AuthorizationException::class
+ );
return str_replace('/', DIRECTORY_SEPARATOR, $directory);
}
diff --git a/src/Filesystem/UploadedFile.php b/src/Filesystem/UploadedFile.php
index 07a886a..c87fbeb 100644
--- a/src/Filesystem/UploadedFile.php
+++ b/src/Filesystem/UploadedFile.php
@@ -20,6 +20,8 @@
use function Framework\Polyfill\str_starts_with;
use function Framework\message;
+use function Framework\throw_anyway;
+use function Framework\throw_unless;
class UploadedFile extends File implements JsonSerializable
{
@@ -260,6 +262,7 @@ public function move(string $directory, ?string $name = null)
if ($this->is_valid()) {
$target = $this->get_target_file($directory, $name);
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler -- Captures a PHP warning from rename()/move_uploaded_file() into a catchable value; restored in the finally block immediately after.
set_error_handler(static function ($type, $msg) use (&$error) {
$error = $msg;
});
@@ -270,23 +273,23 @@ public function move(string $directory, ?string $name = null)
restore_error_handler();
}
- if (!$moved) {
- throw new Exception(
- message(
- 'upload.move_failed',
- $this->getPathname(),
- $target,
- strip_tags($error ?? '')
- )
- );
- }
+ throw_unless(
+ $moved,
+ message(
+ 'upload.move_failed',
+ $this->getPathname(),
+ $target,
+ strip_tags($error ?? '')
+ ),
+ Exception::class
+ );
@chmod($target, 0666 & ~umask());
return $target;
}
- throw new Exception($this->get_error_message());
+ throw_anyway($this->get_error_message());
}
/**
diff --git a/src/Http/Client/MultipartStream.php b/src/Http/Client/MultipartStream.php
index d67965f..3557fb9 100644
--- a/src/Http/Client/MultipartStream.php
+++ b/src/Http/Client/MultipartStream.php
@@ -16,6 +16,7 @@
use UnexpectedValueException;
use function Framework\collection;
+use function Framework\throw_unless;
class MultipartStream
{
@@ -119,9 +120,7 @@ protected function create_stream(array $data)
protected function create_stream_item(array $item)
{
foreach (['contents', 'name'] as $key) {
- if (!array_key_exists($key, $item)) {
- throw new UnexpectedValueException("Missing {$key} in item");
- }
+ throw_unless(array_key_exists($key, $item), "Missing {$key} in item", UnexpectedValueException::class);
}
$name = $item['name'];
diff --git a/src/Http/Client/Request.php b/src/Http/Client/Request.php
index 536006a..4bda12c 100644
--- a/src/Http/Client/Request.php
+++ b/src/Http/Client/Request.php
@@ -22,6 +22,8 @@
use function Framework\collection;
use function Framework\Polyfill\str_contains;
+use function Framework\throw_anyway;
+use function Framework\throw_unless;
class Request
{
@@ -729,9 +731,11 @@ public function send(string $method, string $url, array $options = [])
*/
protected function send_request(string $method, string $url, array $options = [])
{
- if (!$this->is_valid_method($method)) {
- throw new RuntimeException(sprintf('Invalid HTTP method: %s', $method));
- }
+ throw_unless(
+ $this->is_valid_method($method),
+ sprintf('Invalid HTTP method: %s', $method),
+ RuntimeException::class
+ );
$data = $this->parse_request_data($method, $url, $options);
$url = $this->prepare_request_url($method, $url, $data);
@@ -840,9 +844,7 @@ protected function prepare_request_body(string $method, array $data)
case 'multipart':
return $this->make_multipart_body($data);
default:
- throw new RuntimeException(
- sprintf('Invalid body format: %s', $this->body_format)
- );
+ throw_anyway(sprintf('Invalid body format: %s', $this->body_format), RuntimeException::class);
}
}
@@ -951,8 +953,6 @@ public function __call($method, $parameters)
return $this->call_macro($method, $parameters);
}
- throw new BadMethodCallException(
- sprintf('Call to undefined method %s::%s', static::class, $method)
- );
+ throw_anyway(sprintf('Call to undefined method %s::%s', static::class, $method), BadMethodCallException::class);
}
}
diff --git a/src/Http/Concerns/InteractsWithFiles.php b/src/Http/Concerns/InteractsWithFiles.php
index dfc33ad..3769e54 100644
--- a/src/Http/Concerns/InteractsWithFiles.php
+++ b/src/Http/Concerns/InteractsWithFiles.php
@@ -14,6 +14,7 @@
use Framework\Collections\Collection;
use Framework\Filesystem\UploadedFile;
+use Framework\Http\Superglobals;
use SplFileInfo;
use function Framework\deep_get;
@@ -67,8 +68,7 @@ public function file(?string $key = null, $default = null)
*/
protected function load_files_from_global()
{
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $files = $_FILES ?? [];
+ $files = Superglobals::files();
$keys = array_keys($files);
$files = array_map(function ($file) {
diff --git a/src/Http/Cookie.php b/src/Http/Cookie.php
index ff325ab..a68334f 100644
--- a/src/Http/Cookie.php
+++ b/src/Http/Cookie.php
@@ -14,6 +14,9 @@
use InvalidArgumentException;
+use function Framework\throw_if;
+use function Framework\throw_unless;
+
class Cookie
{
/**
@@ -425,11 +428,11 @@ protected function normalize_same_site($same_site)
$normalized = strtolower((string) $same_site);
- if (!isset($supported[$normalized])) {
- throw new InvalidArgumentException(
- sprintf('The same site attribute "%s" is invalid.', $same_site)
- );
- }
+ throw_unless(
+ isset($supported[$normalized]),
+ sprintf('The same site attribute "%s" is invalid.', $same_site),
+ InvalidArgumentException::class
+ );
return $supported[$normalized];
}
@@ -451,10 +454,10 @@ protected function validate_name(string $name)
throw new InvalidArgumentException('The cookie name cannot be empty.');
}
- if (strpbrk($name, static::RESERVED_CHARACTERS) !== false) {
- throw new InvalidArgumentException(
- sprintf('The cookie name "%s" contains invalid characters.', $name)
- );
- }
+ throw_if(
+ strpbrk($name, static::RESERVED_CHARACTERS) !== false,
+ sprintf('The cookie name "%s" contains invalid characters.', $name),
+ InvalidArgumentException::class
+ );
}
}
diff --git a/src/Http/JsonResponse.php b/src/Http/JsonResponse.php
index d5ba9fb..4a9a9ad 100644
--- a/src/Http/JsonResponse.php
+++ b/src/Http/JsonResponse.php
@@ -20,6 +20,8 @@
use JsonSerializable;
use WP_REST_Response;
+use function Framework\throw_unless;
+
class JsonResponse extends WP_REST_Response
{
use InteractsWithCookies;
@@ -163,9 +165,7 @@ public function set_content($data)
break;
}
- if (!$this->is_valid_json(json_last_error())) {
- throw new InvalidArgumentException(json_last_error_msg());
- }
+ throw_unless($this->is_valid_json(json_last_error()), json_last_error_msg(), InvalidArgumentException::class);
return $this;
}
diff --git a/src/Http/Request.php b/src/Http/Request.php
index 93af0e1..c59cfd9 100644
--- a/src/Http/Request.php
+++ b/src/Http/Request.php
@@ -26,6 +26,7 @@
use function Framework\app;
use function Framework\config;
use function Framework\message;
+use function Framework\throw_unless;
use function Framework\user;
use function Framework\value;
@@ -212,11 +213,11 @@ public function __call(string $name, array $arguments)
{
$name = strtolower($name);
- if (!in_array($name, static::$types, true)) {
- throw new BadMethodCallException(
- sprintf('Method %s::%s does not exist.', static::class, $name)
- );
- }
+ throw_unless(
+ in_array($name, static::$types, true),
+ sprintf('Method %s::%s does not exist.', static::class, $name),
+ BadMethodCallException::class
+ );
$method_name = 'get_' . $name;
@@ -260,8 +261,7 @@ public function make_request(WP_REST_Request $request)
$this->route_params = $request->get_url_params();
// WP_REST_Request carries no cookie params, so read them from the superglobal.
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $this->cookies = $this->unslash_array($_COOKIE ?? []);
+ $this->cookies = Superglobals::cookie();
return $this;
}
@@ -275,8 +275,14 @@ public function make_request(WP_REST_Request $request)
*/
public static function capture()
{
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- return (new static())->make_from_http($_GET, $_POST, $_FILES, $_SERVER, [], $_COOKIE);
+ return (new static())->make_from_http(
+ Superglobals::query(),
+ Superglobals::post(),
+ Superglobals::files(),
+ Superglobals::server(),
+ [],
+ Superglobals::cookie()
+ );
}
/**
@@ -305,8 +311,8 @@ public function make_from_http(
$body = $this->unslash_array($body);
$this->attributes = array_merge($query, $body, $route_params);
- $this->method = strtoupper($server['REQUEST_METHOD'] ?? 'GET');
- $this->route = $this->resolve_request_path($server);
+ $this->method = static::resolve_method($server);
+ $this->route = static::resolve_request_path($server);
$this->headers = $this->extract_headers($server);
$this->server = $server;
$this->route_params = $route_params;
@@ -382,13 +388,13 @@ protected function unslash_array(array $values)
*
* @since 1.0.0
*/
- protected function resolve_request_path(array $server)
+ public static function resolve_request_path(array $server)
{
$request_uri = isset($server['REQUEST_URI']) ? (string) $server['REQUEST_URI'] : '';
- $path = (string) parse_url($request_uri, PHP_URL_PATH);
+ $path = (string) wp_parse_url($request_uri, PHP_URL_PATH);
if (function_exists('home_url')) {
- $home_path = (string) parse_url(home_url(), PHP_URL_PATH);
+ $home_path = (string) wp_parse_url(home_url(), PHP_URL_PATH);
if ($home_path !== '' && $home_path !== '/' && strpos($path, $home_path) === 0) {
$path = substr($path, strlen($home_path));
@@ -398,6 +404,20 @@ protected function resolve_request_path(array $server)
return trim($path, '/');
}
+ /**
+ * Resolve the HTTP method from server parameters.
+ *
+ * @param array $server Server parameters.
+ *
+ * @return string
+ *
+ * @since 1.0.0
+ */
+ public static function resolve_method(array $server)
+ {
+ return strtoupper($server['REQUEST_METHOD'] ?? 'GET');
+ }
+
/**
* Extract HTTP headers from server parameters.
*
@@ -894,8 +914,7 @@ public function attributes()
*/
public function ip()
{
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $server = !empty($this->server) ? $this->server : $_SERVER;
+ $server = !empty($this->server) ? $this->server : Superglobals::server();
$remote = isset($server['REMOTE_ADDR']) ? trim((string) $server['REMOTE_ADDR']) : null;
if (empty($remote) || !$this->is_trusted_proxy($remote)) {
@@ -1066,9 +1085,7 @@ protected function ip_in_range(string $ip, string $range)
*/
public function authorize_request()
{
- if (!$this->authorize()) {
- throw new AuthorizationException(message('auth.unauthorized_request'));
- }
+ throw_unless($this->authorize(), message('auth.unauthorized_request'), AuthorizationException::class);
return $this;
}
diff --git a/src/Http/Superglobals.php b/src/Http/Superglobals.php
new file mode 100644
index 0000000..94eb5d8
--- /dev/null
+++ b/src/Http/Superglobals.php
@@ -0,0 +1,154 @@
+get_current_user();
- if (!$user->is_logged_in()) {
- throw new AuthorizationException(message('auth.logged_in_required'));
- }
+ throw_unless(
+ $user->is_logged_in(),
+ message('auth.logged_in_required'),
+ AuthorizationException::class
+ );
$policy = $this->resolve_policy($model);
- if (!$policy) {
- throw new AuthorizationException(message('auth.no_policy'));
- }
+ throw_unless(
+ $policy,
+ message('auth.no_policy'),
+ AuthorizationException::class
+ );
if (method_exists($policy, 'before')) {
$before_result = $policy->before($user, $ability);
@@ -192,19 +198,19 @@ public function authorize(string $ability, $model = null, ...$arguments)
return true;
}
- if ($before_result === false) {
- throw new AuthorizationException(
- message('auth.unauthorized_action', $ability)
- );
- }
- }
-
- if (!method_exists($policy, $ability)) {
- throw new AuthorizationException(
- message('auth.ability_not_defined', $ability)
+ throw_if(
+ $before_result === false,
+ message('auth.unauthorized_action', $ability),
+ AuthorizationException::class
);
}
+ throw_unless(
+ method_exists($policy, $ability),
+ message('auth.ability_not_defined', $ability),
+ AuthorizationException::class
+ );
+
$dependencies = $this->resolve_method_dependencies(
$policy,
$ability,
@@ -213,11 +219,11 @@ public function authorize(string $ability, $model = null, ...$arguments)
$can_perform = $policy->$ability(...$dependencies);
- if (!$can_perform) {
- throw new AuthorizationException(
- message('auth.unauthorized_action', $ability)
- );
- }
+ throw_unless(
+ $can_perform,
+ message('auth.unauthorized_action', $ability),
+ AuthorizationException::class
+ );
return true;
}
diff --git a/src/Managers/SessionManager.php b/src/Managers/SessionManager.php
index 134ed87..2977c24 100644
--- a/src/Managers/SessionManager.php
+++ b/src/Managers/SessionManager.php
@@ -14,6 +14,8 @@
use Exception;
use Framework\Contracts\SessionHandler;
+use Framework\Http\Superglobals;
+use Framework\Sanitizer;
use Framework\Supports\Arr;
use function Framework\app;
@@ -274,14 +276,7 @@ protected function request_id()
protected function request_cookie(string $name)
{
// Reading the session id cookie; the value is format-validated before use.
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $value = $_COOKIE[$name] ?? null;
-
- if (!is_string($value)) {
- return null;
- }
-
- return function_exists('wp_unslash') ? wp_unslash($value) : $value;
+ return Superglobals::cookie($name, null, Sanitizer::KEY);
}
/**
diff --git a/src/Middlewares/AdminMiddleware.php b/src/Middlewares/AdminMiddleware.php
index 4e8022c..02aea9b 100644
--- a/src/Middlewares/AdminMiddleware.php
+++ b/src/Middlewares/AdminMiddleware.php
@@ -18,6 +18,7 @@
use Framework\Wordpress\Constants\Capabilities;
use function Framework\message;
+use function Framework\throw_anyway;
class AdminMiddleware implements Middleware
{
@@ -40,6 +41,6 @@ public function handle(Request $request, callable $next)
return $next($request);
};
- throw new AuthorizationException(message('auth.admin_required'), Response::FORBIDDEN);
+ throw_anyway(message('auth.admin_required'), AuthorizationException::class, Response::FORBIDDEN);
}
}
diff --git a/src/Middlewares/AuthMiddleware.php b/src/Middlewares/AuthMiddleware.php
index 0287264..d53b263 100644
--- a/src/Middlewares/AuthMiddleware.php
+++ b/src/Middlewares/AuthMiddleware.php
@@ -16,6 +16,7 @@
use Framework\Exceptions\AuthorizationException;
use function Framework\message;
+use function Framework\throw_anyway;
class AuthMiddleware implements Middleware
{
@@ -38,6 +39,6 @@ public function handle(Request $request, callable $next)
return $next($request);
};
- throw new AuthorizationException(message('auth.logged_in_required'));
+ throw_anyway(message('auth.logged_in_required'), AuthorizationException::class);
}
}
diff --git a/src/Middlewares/ThrottleRequests.php b/src/Middlewares/ThrottleRequests.php
index 8ed7805..4f6f1f5 100644
--- a/src/Middlewares/ThrottleRequests.php
+++ b/src/Middlewares/ThrottleRequests.php
@@ -24,6 +24,8 @@
use InvalidArgumentException;
use function Framework\app;
+use function Framework\throw_exception;
+use function Framework\throw_if;
class ThrottleRequests implements Middleware
{
@@ -104,13 +106,13 @@ protected function enforce(Request $request, Limit $limit)
$key = $this->resolve_key($request, $limit);
if ($limiter->too_many_attempts($key, $limit->max_attempts)) {
- throw $this->rejection($request, $limit, $key);
+ throw_exception($this->rejection($request, $limit, $key));
}
$hits = $limiter->increment($key, $limit->decay_seconds);
if ($hits > $limit->max_attempts) {
- throw $this->rejection($request, $limit, $key);
+ throw_exception($this->rejection($request, $limit, $key));
}
$this->record_headers([
@@ -186,11 +188,11 @@ protected function resolve_limits(Request $request, array $parameters)
$callback = $this->limiter()->limiter((string) $first);
- if (is_null($callback)) {
- throw new InvalidArgumentException(
- sprintf('Rate limiter [%s] is not registered.', $first)
- );
- }
+ throw_if(
+ is_null($callback),
+ sprintf('Rate limiter [%s] is not registered.', $first),
+ InvalidArgumentException::class
+ );
$limits = $callback($request);
diff --git a/src/Route.php b/src/Route.php
index 69c18b9..eab1e19 100644
--- a/src/Route.php
+++ b/src/Route.php
@@ -23,6 +23,7 @@
use Framework\Exceptions\InvalidRoutActionException;
use Framework\Exceptions\ModelNotFoundException;
use Framework\Http\Request;
+use Framework\Http\Superglobals;
use Framework\Routing\CurrentRoute;
use Framework\Routing\RouteParser;
use Framework\Routing\SiteRouter;
@@ -39,6 +40,8 @@
use function Framework\app;
use function Framework\Polyfill\array_first;
use function Framework\Polyfill\array_last;
+use function Framework\throw_if;
+use function Framework\throw_unless;
class Route
{
@@ -1198,19 +1201,21 @@ protected function make(string $abstract, array $resolving = [])
return $this->get_cached($abstract);
}
- if (in_array($abstract, $resolving, true)) {
- throw new Exception(sprintf('Circular dependency detected for class "%s".', $abstract));
- }
+ throw_if(
+ in_array($abstract, $resolving, true),
+ sprintf('Circular dependency detected for class "%s".', $abstract),
+ Exception::class
+ );
- if (!class_exists($abstract)) {
- throw new Exception(sprintf('Class "%s" does not exist.', $abstract));
- }
+ throw_unless(class_exists($abstract), sprintf('Class "%s" does not exist.', $abstract));
$reflector = new ReflectionClass($abstract);
- if ($reflector->isAbstract()) {
- throw new Exception(sprintf('Class "%s" is abstract and cannot be instantiated.', $abstract));
- }
+ throw_if(
+ $reflector->isAbstract(),
+ sprintf('Class "%s" is abstract and cannot be instantiated.', $abstract),
+ Exception::class
+ );
$constructor = $reflector->getConstructor();
@@ -1218,11 +1223,11 @@ protected function make(string $abstract, array $resolving = [])
return new $abstract();
}
- if (!$constructor->isPublic()) {
- throw new Exception(
- sprintf('Class "%s" has a non-public constructor and cannot be instantiated.', $abstract)
- );
- }
+ throw_unless(
+ $constructor->isPublic(),
+ sprintf('Class "%s" has a non-public constructor and cannot be instantiated.', $abstract),
+ Exception::class
+ );
$dependencies = [];
$resolving[] = $abstract;
@@ -1230,23 +1235,23 @@ protected function make(string $abstract, array $resolving = [])
foreach ($constructor->getParameters() as $parameter) {
$type = $parameter->getType();
- if (!$type) {
- throw new Exception(
- sprintf(
- 'Parameter "%s" is missing a type hint in the constructor. Please add a class type hint.',
- $parameter->getName()
- )
- );
- }
+ throw_unless(
+ $type !== null,
+ sprintf(
+ 'Parameter "%s" is missing a type hint in the constructor. Please add a class type hint.',
+ $parameter->getName()
+ ),
+ Exception::class
+ );
- if ($type->isBuiltin()) {
- throw new Exception(
- sprintf(
- 'Parameter "%s" must be a class type, not a built-in type. Please specify a valid class dependency.', // phpcs:ignore Generic.Files.LineLength.TooLong
- $parameter->getName()
- )
- );
- }
+ throw_if(
+ $type->isBuiltin(),
+ sprintf(
+ 'Parameter "%s" must be a class type, not a built-in type. Please specify a valid class dependency.', // phpcs:ignore Generic.Files.LineLength.TooLong
+ $parameter->getName()
+ ),
+ Exception::class
+ );
$dependencies[] = $this->is_cached($type->getName())
? $this->get_cached($type->getName())
@@ -1276,9 +1281,11 @@ protected function resolve_method_dependencies($abstract, $method)
{
$method_reflection = new ReflectionMethod($abstract, $method);
- if (!$method_reflection->isPublic()) {
- throw new Exception(sprintf('Method "%s" is not public and cannot be called.', $method));
- }
+ throw_unless(
+ $method_reflection->isPublic(),
+ sprintf('Method "%s" is not public and cannot be called.', $method),
+ Exception::class
+ );
$dependencies = $this->categorize_parameters($method_reflection->getParameters());
$this->assert_single_request_dependency($dependencies, $method);
@@ -1361,17 +1368,17 @@ protected function categorize_parameters(array $parameters)
*/
protected function assert_single_request_dependency(array $dependencies, string $handler)
{
- if (count($dependencies['requests']) < 1) {
- throw new InvalidArgumentException(
- sprintf('The method "%s" must have at least one request dependency.', $handler)
- );
- }
+ throw_if(
+ count($dependencies['requests']) < 1,
+ sprintf('The method "%s" must have at least one request dependency.', $handler),
+ InvalidArgumentException::class
+ );
- if (count($dependencies['requests']) > 1) {
- throw new InvalidArgumentException(
- sprintf('The method "%s" must have only one request dependency.', $handler)
- );
- }
+ throw_if(
+ count($dependencies['requests']) > 1,
+ sprintf('The method "%s" must have only one request dependency.', $handler),
+ InvalidArgumentException::class
+ );
}
/**
@@ -1714,17 +1721,12 @@ public function dispatch_site(array $route_params = [])
{
$request_class = $this->resolve_request_class();
$request = app()->make($request_class)->make_from_http(
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $_GET,
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $_POST,
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $_FILES,
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $_SERVER,
+ Superglobals::query(),
+ Superglobals::post(),
+ Superglobals::files(),
+ Superglobals::server(),
$route_params,
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $_COOKIE
+ Superglobals::cookie()
);
$request->authorize_request();
@@ -1895,31 +1897,33 @@ protected function dispatch_with_request(Request $request)
*/
protected function resolve_controller(Request $request)
{
- if (!is_array($this->action)) {
- throw new InvalidRoutActionException(
- sprintf('Invalid method registered for the route %s', $this->endpoint)
- );
- }
+ throw_unless(
+ is_array($this->action),
+ sprintf('Invalid method registered for the route %s', $this->endpoint),
+ InvalidRoutActionException::class
+ );
- if (count($this->action) !== 2) {
- throw new InvalidRoutActionException(
- sprintf('Invalid controller syntax for the route %s', $this->endpoint)
- );
- }
+ throw_if(
+ count($this->action) !== 2,
+ sprintf('Invalid controller syntax for the route %s', $this->endpoint),
+ InvalidRoutActionException::class
+ );
[$controller, $method] = $this->action;
- if (!class_exists($controller)) {
- throw new InvalidRoutActionException(sprintf('Controller %s not found', $controller));
- }
+ throw_unless(
+ class_exists($controller),
+ sprintf('Controller %s not found', $controller),
+ InvalidRoutActionException::class
+ );
$controller_instance = $this->make($controller);
- if (!method_exists($controller_instance, $method)) {
- throw new InvalidRoutActionException(
- sprintf('The method %s is missing in the controller %s', $method, $controller)
- );
- }
+ throw_unless(
+ method_exists($controller_instance, $method),
+ sprintf('The method %s is missing in the controller %s', $method, $controller),
+ InvalidRoutActionException::class
+ );
$dependencies = $this->resolve_method_dependencies($controller_instance, $method);
$first_request = array_first($dependencies['requests']);
@@ -1952,11 +1956,11 @@ function ($next, $middleware) {
return function ($request) use ($next, $middleware) {
[$class, $parameters] = static::parse_middleware($middleware);
- if (!is_subclass_of($class, Middleware::class)) {
- throw new InvalidArgumentException(
- sprintf('Middleware %s must implement the %s interface.', $class, Middleware::class)
- );
- }
+ throw_unless(
+ is_subclass_of($class, Middleware::class),
+ sprintf('Middleware %s must implement the %s interface.', $class, Middleware::class),
+ InvalidArgumentException::class
+ );
return (new $class())->handle($request, $next, ...$parameters);
};
@@ -2030,11 +2034,11 @@ protected static function parse_middleware($middleware)
return [static::$middleware_aliases[$name], $parameters];
}
- if (!class_exists($name)) {
- throw new InvalidMiddlewareException(
- sprintf('Middleware [%s] is not a registered alias and is not a resolvable class.', $name)
- );
- }
+ throw_unless(
+ class_exists($name),
+ sprintf('Middleware [%s] is not a registered alias and is not a resolvable class.', $name),
+ InvalidMiddlewareException::class
+ );
return [$name, $parameters];
}
diff --git a/src/Routing/SiteRouter.php b/src/Routing/SiteRouter.php
index e1b41fb..5f59b50 100644
--- a/src/Routing/SiteRouter.php
+++ b/src/Routing/SiteRouter.php
@@ -13,6 +13,8 @@
use Framework\Http\JsonResponse;
use Framework\Http\RedirectResponse;
+use Framework\Http\Request;
+use Framework\Http\Superglobals;
use Framework\Managers\CookieManager;
use Framework\Managers\SessionManager;
use Framework\Route;
@@ -268,7 +270,7 @@ public function intercept_parse_request($wp)
{
$path = isset($wp->request) && $wp->request !== ''
? trim((string) $wp->request, '/')
- : $this->resolve_request_path();
+ : Request::resolve_request_path(Superglobals::server());
foreach ($this->routes as $id => $route) {
if ($route->get_match_using() === Route::MATCH_PAGE) {
@@ -466,8 +468,7 @@ protected function match_current_request(string $expected_hook_name, int $expect
return null;
}
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
+ $method = Request::resolve_method(Superglobals::server());
if (strtoupper($route->get_method()) !== $method) {
SiteExceptionHandler::handle(new Exception('Method Not Allowed', 405));
@@ -662,8 +663,7 @@ protected function collect_route_params(Route $route)
*/
protected function get_matched_route(Route $route)
{
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $request_method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
+ $request_method = Request::resolve_method(Superglobals::server());
foreach ($this->routes as $route_entry) {
if (
@@ -804,27 +804,6 @@ protected function send_route_redirect(Route $route, array $params)
exit;
}
- /**
- * Resolve the current request path relative to the site home path.
- *
- * @return string
- *
- * @since 1.0.0
- */
- protected function resolve_request_path()
- {
- // phpcs:ignore Framework.NamingConventions.SnakeCaseVariable.NotSnakeCase
- $request_uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '';
- $path = (string) parse_url($request_uri, PHP_URL_PATH);
- $home_path = (string) parse_url(home_url(), PHP_URL_PATH);
-
- if ($home_path !== '' && $home_path !== '/' && strpos($path, $home_path) === 0) {
- $path = substr($path, strlen($home_path));
- }
-
- return trim($path, '/');
- }
-
/**
* Build a stable internal route id.
*
diff --git a/src/SiteExceptionHandler.php b/src/SiteExceptionHandler.php
index 74c784b..a044b48 100644
--- a/src/SiteExceptionHandler.php
+++ b/src/SiteExceptionHandler.php
@@ -15,6 +15,7 @@
use Framework\Exceptions\ModelNotFoundException;
use Framework\Exceptions\ValidationException;
use Framework\Http\Response;
+use Framework\Supports\Facades\Log;
use Exception;
class SiteExceptionHandler
@@ -57,9 +58,7 @@ public static function handle(Exception $exception)
$status = Response::INTERNAL_SERVER_ERROR;
}
- if (function_exists('error_log')) {
- error_log($exception->getMessage());
- }
+ Log::error($exception->getMessage());
static::fail($status, $exception->getMessage() ?: 'Internal Server Error');
}
@@ -101,6 +100,6 @@ protected static function fail(int $status, string $message)
{
status_header($status);
nocache_headers();
- wp_die(esc_html($message), esc_html($message), ['response' => $status]);
+ wp_die(esc_html($message), esc_html($message), ['response' => absint($status)]);
}
}
diff --git a/src/Supports/Somoy.php b/src/Supports/Somoy.php
index 3d8bee2..d582984 100644
--- a/src/Supports/Somoy.php
+++ b/src/Supports/Somoy.php
@@ -36,6 +36,10 @@
use Framework\Exceptions\InvalidDateFormatException;
use InvalidArgumentException;
+use function Framework\throw_anyway;
+use function Framework\throw_if;
+use function Framework\throw_unless;
+
class Somoy extends DateTime implements SomoyInterface
{
/**
@@ -303,12 +307,11 @@ public static function parse($time = null, $timezone = null)
$time = (string) $time;
}
- if ($time !== null && !is_string($time)) {
- throw new InvalidDateFormatException(sprintf(
- 'Could not parse a value of type %s as a date.',
- gettype($time)
- ));
- }
+ throw_if(
+ $time !== null && !is_string($time),
+ sprintf('Could not parse a value of type %s as a date.', gettype($time)),
+ InvalidDateFormatException::class
+ );
try {
return new static(
@@ -316,8 +319,9 @@ public static function parse($time = null, $timezone = null)
static::resolve_timezone($timezone)
);
} catch (Exception $exception) {
- throw new InvalidDateFormatException(
+ throw_anyway(
sprintf('Could not parse "%s" as a date.', $time),
+ InvalidDateFormatException::class,
0,
$exception
);
@@ -386,13 +390,15 @@ public static function create_from_format($format, $time, $timezone = null)
? DateTime::createFromFormat($format, $time)
: DateTime::createFromFormat($format, $time, $timezone);
- if (!$date instanceof DateTimeInterface) {
- throw new InvalidDateFormatException(sprintf(
+ throw_unless(
+ $date instanceof DateTimeInterface,
+ sprintf(
'Could not parse "%s" using the format "%s".',
is_scalar($time) ? $time : gettype($time),
$format
- ));
- }
+ ),
+ InvalidDateFormatException::class
+ );
return static::instance($date);
}
@@ -1421,12 +1427,11 @@ public function set_time_from_time_string($time)
$modified = false;
}
- if ($modified === false) {
- throw new InvalidDateFormatException(sprintf(
- 'Could not read "%s" as a time.',
- is_scalar($given) ? $given : gettype($given)
- ));
- }
+ throw_if(
+ $modified === false,
+ sprintf('Could not read "%s" as a time.', is_scalar($given) ? $given : gettype($given)),
+ InvalidDateFormatException::class
+ );
return $this;
}
@@ -1468,13 +1473,11 @@ public function __toString()
*/
public function __get($name)
{
- if (!isset(static::$readable_units[$name])) {
- throw new InvalidArgumentException(sprintf(
- 'Undefined property %s::$%s.',
- static::class,
- $name
- ));
- }
+ throw_unless(
+ isset(static::$readable_units[$name]),
+ sprintf('Undefined property %s::$%s.', static::class, $name),
+ InvalidArgumentException::class
+ );
return (int) $this->format(static::$readable_units[$name]);
}
@@ -1510,11 +1513,10 @@ public function __isset($name)
*/
public function __call($method, $parameters)
{
- throw new BadMethodCallException(sprintf(
- 'Call to undefined method %s::%s(). Date methods are snake_case.',
- static::class,
- $method
- ));
+ throw_anyway(
+ sprintf('Call to undefined method %s::%s(). Date methods are snake_case.', static::class, $method),
+ BadMethodCallException::class
+ );
}
/**
@@ -1531,11 +1533,10 @@ public function __call($method, $parameters)
*/
public static function __callStatic($method, $parameters)
{
- throw new BadMethodCallException(sprintf(
- 'Call to undefined method %s::%s(). Date methods are snake_case.',
- static::class,
- $method
- ));
+ throw_anyway(
+ sprintf('Call to undefined method %s::%s(). Date methods are snake_case.', static::class, $method),
+ BadMethodCallException::class
+ );
}
/**
@@ -1591,8 +1592,9 @@ protected static function resolve_timezone($timezone)
try {
return new DateTimeZone($timezone);
} catch (Exception $exception) {
- throw new InvalidDateFormatException(
+ throw_anyway(
sprintf('Unknown timezone "%s".', is_scalar($timezone) ? $timezone : gettype($timezone)),
+ InvalidDateFormatException::class,
0,
$exception
);
@@ -1637,18 +1639,18 @@ protected static function format_human_diff_unit($unit, $value, $short)
*/
protected static function from_timestamp($timestamp)
{
- if (!is_numeric($timestamp)) {
- throw new InvalidDateFormatException(sprintf(
- 'Could not create a date from a non numeric timestamp of type %s.',
- gettype($timestamp)
- ));
- }
+ throw_unless(
+ is_numeric($timestamp),
+ sprintf('Could not create a date from a non numeric timestamp of type %s.', gettype($timestamp)),
+ InvalidDateFormatException::class
+ );
try {
return new static('@' . sprintf('%.6F', (float) $timestamp));
} catch (Exception $exception) {
- throw new InvalidDateFormatException(
+ throw_anyway(
sprintf('Could not create a date from the timestamp "%s".', $timestamp),
+ InvalidDateFormatException::class,
0,
$exception
);
diff --git a/src/Validation/RuleFactory.php b/src/Validation/RuleFactory.php
index a9e2086..1f4af17 100644
--- a/src/Validation/RuleFactory.php
+++ b/src/Validation/RuleFactory.php
@@ -21,6 +21,7 @@
use function Framework\Polyfill\array_last;
use function Framework\Polyfill\str_contains;
use function Framework\Polyfill\str_starts_with;
+use function Framework\throw_if;
class RuleFactory
{
@@ -202,9 +203,11 @@ protected function build_rules_chain(array $rules)
$rule_class = $this->get_rule_class($rule_name, $arguments);
- if ($rule_class === null && $last_base_rule === null) {
- throw new InvalidValidationRuleException(message('validator.invalid_rule', [$rule]));
- }
+ throw_if(
+ $rule_class === null && $last_base_rule === null,
+ message('validator.invalid_rule', [$rule]),
+ InvalidValidationRuleException::class
+ );
if ($rule_class !== null) {
$last_base_rule = $rule_class->get_rule_name();
diff --git a/src/Validation/Rules/FileRule.php b/src/Validation/Rules/FileRule.php
index 28c2394..cd65575 100644
--- a/src/Validation/Rules/FileRule.php
+++ b/src/Validation/Rules/FileRule.php
@@ -18,6 +18,8 @@
use Framework\Supports\Str;
use InvalidArgumentException;
+use function Framework\throw_anyway;
+
/**
* File rule class.
@@ -256,7 +258,7 @@ protected function to_kilobytes($size)
case Str::ends_with($size, 'tb'):
return $value * 1024 * 1024 * 1024;
default:
- throw new InvalidArgumentException('Invalid file size: ' . $size);
+ throw_anyway('Invalid file size: ' . $size, InvalidArgumentException::class);
}
}
@@ -323,7 +325,7 @@ protected function make_file($value)
return new FilesystemFile($value, false);
}
- throw new InvalidArgumentException('Invalid file value: ' . $value);
+ throw_anyway('Invalid file value: ' . $value, InvalidArgumentException::class);
}
diff --git a/src/Validation/Validator.php b/src/Validation/Validator.php
index 2459546..6beaa63 100644
--- a/src/Validation/Validator.php
+++ b/src/Validation/Validator.php
@@ -19,6 +19,7 @@
use function Framework\deep_get;
use function Framework\message;
+use function Framework\throw_exception;
defined('ABSPATH') || exit;
@@ -353,7 +354,7 @@ public function errors()
public function validate()
{
if ($this->fails()) {
- throw ValidationException::with_errors($this->errors());
+ throw_exception(ValidationException::with_errors($this->errors()));
}
return $this->validated();
@@ -373,7 +374,7 @@ public function validated()
}
if (!empty($this->errors())) {
- throw ValidationException::with_errors($this->errors());
+ throw_exception(ValidationException::with_errors($this->errors()));
}
$results = [];
diff --git a/src/View/SectionManager.php b/src/View/SectionManager.php
index 1e0177a..33d66f1 100644
--- a/src/View/SectionManager.php
+++ b/src/View/SectionManager.php
@@ -15,6 +15,8 @@
use RuntimeException;
+use function Framework\throw_if;
+
class SectionManager
{
/**
@@ -48,15 +50,15 @@ class SectionManager
*/
public function start(string $name)
{
- if ($this->active_section !== null) {
- throw new RuntimeException(
- sprintf(
- 'Cannot start section [%s] while section [%s] is already being captured.',
- $name,
- $this->active_section
- )
- );
- }
+ throw_if(
+ $this->active_section !== null,
+ sprintf(
+ 'Cannot start section [%s] while section [%s] is already being captured.',
+ $name,
+ $this->active_section
+ ),
+ RuntimeException::class
+ );
$this->active_section = $name;
diff --git a/src/View/TemplateEngine.php b/src/View/TemplateEngine.php
index 2c6a850..7cc9bc2 100644
--- a/src/View/TemplateEngine.php
+++ b/src/View/TemplateEngine.php
@@ -14,6 +14,7 @@
use RuntimeException;
use function Framework\app;
+use function Framework\throw_if;
class TemplateEngine
{
@@ -88,9 +89,7 @@ public function render(string $view, array $data = [], $layout = true)
{
$path = $this->resolve_path($view);
- if ($path === '') {
- throw new RuntimeException(sprintf('View [%s] not found.', $view));
- }
+ throw_if($path === '', sprintf('View [%s] not found.', $view), RuntimeException::class);
$merged = array_merge($this->shared, $data);
$context = app(ViewContext::class);
@@ -138,9 +137,7 @@ protected function render_with_master_layout(string $child_path, string $master_
{
$master_path = $this->resolve_path($master_view);
- if ($master_path === '') {
- throw new RuntimeException(sprintf('Master layout [%s] not found.', $master_view));
- }
+ throw_if($master_path === '', sprintf('Master layout [%s] not found.', $master_view), RuntimeException::class);
$sections = app(SectionManager::class);
$sections->clear();
@@ -349,9 +346,7 @@ public function include(string $view, array $data = [], bool $once = true)
{
$path = $this->resolve_path($view);
- if ($path === '') {
- throw new RuntimeException(sprintf('View [%s] not found.', $view));
- }
+ throw_if($path === '', sprintf('View [%s] not found.', $view), RuntimeException::class);
$merged = array_merge($this->shared, $data);
$context = app(ViewContext::class);
diff --git a/src/View/ViewContext.php b/src/View/ViewContext.php
index d070488..c8f021d 100644
--- a/src/View/ViewContext.php
+++ b/src/View/ViewContext.php
@@ -216,6 +216,7 @@ protected function authorized_frame()
protected function trace_files()
{
$files = [];
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace -- Collects calling file paths to scope view-data access; not debugging output.
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
foreach ($trace as $frame) {
diff --git a/src/View/layout-wrapper.php b/src/View/layout-wrapper.php
index 4ac4ca4..b78cf02 100644
--- a/src/View/layout-wrapper.php
+++ b/src/View/layout-wrapper.php
@@ -19,47 +19,47 @@
use function Framework\app;
-$context = app(ViewContext::class);
-$active = $context->get_active();
+$framework_context = app(ViewContext::class);
+$framework_active = $framework_context->get_active();
-if ($active === null || empty($active['resolved_path'])) {
+if ($framework_active === null || empty($framework_active['resolved_path'])) {
return;
}
-$path = $active['resolved_path'];
-$engine = app(TemplateEngine::class);
+$framework_path = $framework_active['resolved_path'];
+$framework_engine = app(TemplateEngine::class);
// Master layout: child populates sections, then master layout renders around them.
-if (!empty($active['master_layout'])) {
- $master_path = $engine->resolve_path($active['master_layout']);
+if (!empty($framework_active['master_layout'])) {
+ $framework_master_path = $framework_engine->resolve_path($framework_active['master_layout']);
- if ($master_path === '') {
+ if ($framework_master_path === '') {
return;
}
- $sections = app(SectionManager::class);
- $sections->clear();
+ $framework_sections = app(SectionManager::class);
+ $framework_sections->clear();
// Execute the child template to populate sections.
ob_start();
- require $path;
+ require $framework_path;
ob_end_clean();
// Render the master layout which yields the captured sections.
ob_start();
- require $master_path;
+ require $framework_master_path;
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Assembled layout HTML; dynamic data is escaped in view templates via esc_*.
echo (string) ob_get_clean();
- $sections->clear();
+ $framework_sections->clear();
return;
}
// Standard theme layout: wrap with header/footer.
ob_start();
-require $path;
-$content = (string) ob_get_clean();
+require $framework_path;
+$framework_content = (string) ob_get_clean();
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Assembled layout HTML; dynamic data is escaped in view templates via esc_*.
-echo $engine->wrap_layout($content);
+echo $framework_engine->wrap_layout($framework_content);
diff --git a/src/Wordpress/Hooks/Actions/RegisterAdminMenu.php b/src/Wordpress/Hooks/Actions/RegisterAdminMenu.php
index b86356e..0e09a67 100644
--- a/src/Wordpress/Hooks/Actions/RegisterAdminMenu.php
+++ b/src/Wordpress/Hooks/Actions/RegisterAdminMenu.php
@@ -19,6 +19,7 @@
use Exception;
use function Framework\config;
+use function Framework\throw_if;
class RegisterAdminMenu extends BaseHook
{
@@ -66,9 +67,11 @@ public function handle(...$args)
}
foreach ($menus as $menu) {
- if (!class_exists($menu) || !is_subclass_of($menu, Menu::class)) {
- throw new Exception(sprintf('Menu class %s does not exist.', $menu));
- }
+ throw_if(
+ !class_exists($menu) || !is_subclass_of($menu, Menu::class),
+ sprintf('Menu class %s does not exist.', $menu),
+ Exception::class
+ );
$menu_instance = new $menu();
diff --git a/src/Wordpress/User.php b/src/Wordpress/User.php
index 380be9d..a694a95 100644
--- a/src/Wordpress/User.php
+++ b/src/Wordpress/User.php
@@ -17,6 +17,7 @@
use Exception;
use function Framework\Polyfill\str_starts_with;
+use function Framework\throw_unless;
use function Framework\with_prefix;
/**
@@ -390,9 +391,11 @@ public function cannot($ability, $model = null)
*/
public function __call($name, $arguments = [])
{
- if (!str_starts_with($name, 'can_')) {
- throw new Exception(sprintf('Method %s does not exist', $name));
- }
+ throw_unless(
+ str_starts_with($name, 'can_'),
+ sprintf('Method %s does not exist', $name),
+ Exception::class
+ );
$action = preg_replace('/^can_/', '', $name);
diff --git a/src/helpers.php b/src/helpers.php
index 8633993..85af7ae 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -1066,32 +1066,53 @@ function message($key, ...$args)
/**
* Throw an exception unconditionally
*
- * @param string $message The message to attach
+ * @param mixed $message The message to attach, or the primary constructor argument for exceptions with a non-string leading parameter
* @param string $exception_class The exception class
* @param array $params The rest of the params
- *
+ *
* @throws Exception
*/
- function throw_anyway(string $message = "", $exception_class = Exception::class, ...$params)
+ function throw_anyway($message = "", $exception_class = Exception::class, ...$params)
{
+ // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Escaping is handled centrally by the framework's exception handler at the point of output, not at the point of throwing.
throw new $exception_class($message, ...$params);
}
}
+if (!function_exists('Framework\throw_exception')) {
+ /**
+ * Throw an already-built exception instance unconditionally.
+ *
+ * For call sites that build the exception ahead of time (e.g. a helper method that may
+ * return one of several pre-existing exception instances) rather than constructing one
+ * from a class and arguments, so throw_anyway()'s `new $exception_class(...)` shape
+ * doesn't apply.
+ *
+ * @param Throwable $exception The exception instance to throw.
+ *
+ * @throws Throwable
+ */
+ function throw_exception($exception)
+ {
+ // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Escaping is handled centrally by the framework's exception handler at the point of output, not at the point of throwing.
+ throw $exception;
+ }
+}
+
if (!function_exists('Framework\throw_if')) {
/**
* Throw an exception if the condition is satisfied.
*
* @param bool $condition The condition to satisfy
- * @param string $message The message to show with the exception
+ * @param mixed $message The message to show with the exception, or the primary constructor argument for exceptions with a non-string leading parameter
* @param string $exception_class The exception class
* @param array $params The other params
- *
+ *
* @throws Exception
*
* @since 1.0.0
*/
- function throw_if(bool $condition, string $message = "", $exception_class = Exception::class, ...$params)
+ function throw_if(bool $condition, $message = "", $exception_class = Exception::class, ...$params)
{
if ($condition) {
throw_anyway($message, $exception_class, ...$params);
@@ -1104,15 +1125,15 @@ function throw_if(bool $condition, string $message = "", $exception_class = Exce
* Throw an exception if the condition is satisfied.
*
* @param bool $condition The condition to satisfy
- * @param string $message The message to show with the exception
+ * @param mixed $message The message to show with the exception, or the primary constructor argument for exceptions with a non-string leading parameter
* @param string $exception_class The exception class
* @param array $params The other params
- *
+ *
* @throws Exception
*
* @since 1.0.0
*/
- function throw_unless(bool $condition, string $message = "", $exception_class = Exception::class, ...$params)
+ function throw_unless(bool $condition, $message = "", $exception_class = Exception::class, ...$params)
{
if (!$condition) {
throw_anyway($message, $exception_class, ...$params);
diff --git a/tests/Support/StubsWordPressFunctions.php b/tests/Support/StubsWordPressFunctions.php
index 33f5a67..f25ebdd 100644
--- a/tests/Support/StubsWordPressFunctions.php
+++ b/tests/Support/StubsWordPressFunctions.php
@@ -98,6 +98,13 @@ function home_url($path = '')
}
}
+if (!function_exists('wp_parse_url')) {
+ function wp_parse_url($url, $component = -1)
+ {
+ return parse_url($url, $component);
+ }
+}
+
if (!function_exists('apply_filters')) {
function apply_filters($hook_name, $value, ...$args)
{
@@ -123,6 +130,13 @@ function wp_unslash($value)
}
}
+if (!function_exists('absint')) {
+ function absint($value)
+ {
+ return abs((int) $value);
+ }
+}
+
if (!function_exists('sanitize_key')) {
function sanitize_key($key)
{
diff --git a/tests/Unit/Http/SuperglobalsTest.php b/tests/Unit/Http/SuperglobalsTest.php
new file mode 100644
index 0000000..0e035ce
--- /dev/null
+++ b/tests/Unit/Http/SuperglobalsTest.php
@@ -0,0 +1,83 @@
+ "O\\'Brien test"];
+
+ $this->assertSame('O\'Brien test', Superglobals::post('name'));
+
+ $_POST = [];
+ }
+
+ public function test_single_key_read_uses_text_sanitization_by_default(): void
+ {
+ $_GET = ['q' => ' hello '];
+
+ $this->assertSame('alert(1)hello', Superglobals::query('q'));
+
+ $_GET = [];
+ }
+
+ public function test_missing_key_returns_default_unsanitized(): void
+ {
+ $_POST = [];
+
+ $this->assertSame('fallback value', Superglobals::post('missing', 'fallback value'));
+ $this->assertNull(Superglobals::post('missing'));
+ }
+
+ public function test_reading_does_not_mutate_the_superglobal(): void
+ {
+ $_COOKIE = ['session' => "abc\\'123"];
+
+ Superglobals::cookie('session', null, Sanitizer::KEY);
+ $first = $_COOKIE;
+
+ Superglobals::cookie('session', null, Sanitizer::KEY);
+ $second = $_COOKIE;
+
+ $this->assertSame(['session' => "abc\\'123"], $first);
+ $this->assertSame($first, $second);
+
+ $_COOKIE = [];
+ }
+
+ public function test_whole_array_read_is_unslashed_but_not_type_sanitized(): void
+ {
+ $_SERVER['REQUEST_URI'] = "/path?q=raw&name=O\\'Brien";
+
+ $server = Superglobals::server();
+
+ $this->assertSame("/path?q=raw&name=O'Brien", $server['REQUEST_URI']);
+
+ unset($_SERVER['REQUEST_URI']);
+ }
+
+ public function test_non_scalar_value_is_treated_as_absent(): void
+ {
+ $_POST = ['name' => ['unexpected' => 'array']];
+
+ $this->assertSame('fallback', Superglobals::post('name', 'fallback'));
+
+ $_POST = [];
+ }
+
+ public function test_files_returns_unslashed_whole_array(): void
+ {
+ $_FILES = ['upload' => ['name' => "photo\\'s.png", 'error' => 0]];
+
+ $files = Superglobals::files();
+
+ $this->assertSame("photo's.png", $files['upload']['name']);
+
+ $_FILES = [];
+ }
+}