Properly declare & handle functions/methods that never return (exit/die inside) and add Cli::fatal_error() helper method

This commit is contained in:
Benjamin Renard 2023-07-26 16:51:16 +02:00
parent 248ea089fa
commit 8c108d0ebb
Signed by: bn8
GPG key ID: 3E2E1CE1907115BC
17 changed files with 126 additions and 160 deletions

View file

@ -271,7 +271,7 @@ function cli_cron($command_args) {
);
}
}
exit($error?1:0);
return !$error;
}
Cli :: add_command(
'cron',

View file

@ -159,10 +159,8 @@ function handle_create($request) {
Tpl :: add_message(_("The element '% s' has been created."), $item['name']);
Url :: redirect('item/'.$item['id']);
}
else {
Tpl :: add_error(_("An error occurred while saving this item."));
}
}
Log :: debug('Validated data : '.vardump($info));
Log :: debug('Fields errors : '.vardump($field_errors));
if (isset($_POST['submit']) && !empty($field_errors))
@ -182,7 +180,8 @@ function handle_modify($request) {
global $status_list;
$item = get_item_from_url($request -> id);
if(is_array($item)) {
if(!is_array($item))
Url :: error_404();
if (!can_modify($item)) {
Tpl :: add_error(_('You cannot edit this item.'));
Url :: redirect('item/'.$item['id']);
@ -200,14 +199,12 @@ function handle_modify($request) {
Tpl :: add_message(_("You have not made any changes to element '% s'."), $item['name']);
Url :: redirect('item/'.$item['id']);
}
else if (update_item($item['id'], $changes) === true) {
if (update_item($item['id'], $changes) === true) {
Tpl :: add_message(_("The element '% s' has been updated successfully."), $item['name']);
Url :: redirect('item/'.$item['id']);
}
else {
Tpl :: add_error(_("An error occurred while updating this item."));
}
}
Log :: debug('Validated data : '.vardump($info));
Log :: debug('Fields errors : '.vardump($field_errors));
Tpl :: assign('submited', isset($_POST['submit']));
@ -219,22 +216,15 @@ function handle_modify($request) {
Tpl :: assign('item_id', $item['id']);
Tpl :: assign('field_errors', $field_errors);
Tpl :: assign('status_list', $status_list);
}
else {
Url :: error_404();
}
Tpl :: display(
"form.tpl", _("Element %s: Modification"),
(is_array($item)?$item['name']:"#".$request -> id)
);
Tpl :: display("form.tpl", _("Element %s: Modification"), $item['name']);
}
Url :: add_url_handler('|^item/(?P<id>[0-9]+)/modify$|', 'handle_modify');
/**
* Archive one item page handler
* @param EesyPHP\UrlRequest $request
* @return void
* @return never
*/
function handle_archive($request) {
$item = get_item_from_url($request -> id);
@ -242,7 +232,7 @@ function handle_archive($request) {
Tpl :: add_error(_("Item #% s not found."), $request -> id);
Url :: redirect('item');
}
elseif ($item['status'] == 'archived') {
if ($item['status'] == 'archived') {
Tpl :: add_message(_("This item is already archived."));
}
else if (!can_archive($item)) {

View file

@ -37,10 +37,8 @@ class App {
Config::register_extra_variable('root_directory_path', $root_directory_path);
$config_file = Config::replace_variables($config_file);
if ($config_file && !Config::load($config_file)) {
if ($config_file && !Config::load($config_file))
Log::fatal('Fail to load configuration file (%s)', $config_file);
exit(1);
}
// Set config default values
App :: set_defaults(

View file

@ -23,7 +23,7 @@ class Cas extends Method {
*/
public static function init() {
// In phpstan context, do not initialize
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__'))
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__')) // @phpstan-ignore-line
return true;
// Set config default values
App :: set_default(
@ -124,10 +124,8 @@ class Cas extends Method {
*/
public static function logout() {
if (App :: get('auth.cas.logout', null, 'bool') && !self :: $fake_authenticated_user) {
if (App :: get('auth.cas.logout_url')) {
if (App :: get('auth.cas.logout_url'))
Url :: redirect(App :: get('auth.cas.logout_url'));
exit();
}
phpCAS::logout();
}
else {
@ -139,7 +137,7 @@ class Cas extends Method {
/**
* The CAS callback view
* @param \EesyPHP\UrlRequest $request
* @return void
* @return never
*/
public static function handle_cas_callback($request) {
if (isset($_SESSION['cas_callback_url'])) {

View file

@ -113,7 +113,7 @@ class Http extends Method {
/**
* Force HTTP user authentification
* @return void
* @return never
*/
public static function force_login() {
header('HTTP/1.1 401 Authorization Required');

View file

@ -21,7 +21,6 @@ class Method {
*/
public static function login($force=false) {
Log :: fatal('login() is not implement for this authentication method.');
return null;
}
/**

View file

@ -113,7 +113,7 @@ Additionnal parameters:
* Show usage message
* @param string|false $error Error message to show (optional)
* @param array $extra_args Extra arguments to use to compute error message using sprintf
* @return void
* @return never
*/
public static function usage($error=false, ...$extra_args) {
global $argv;
@ -172,11 +172,12 @@ Additionnal parameters:
* Handle command line arguments
* @param array|null $args Command line argurment to handle (optional, default: $argv)
* @param bool|null $core_mode Force enable/disable EesyPHP core mode (optional, default: false)
* @return void
* @return never
*/
public static function handle_args($args=null, $core_mode=false) {
global $argv;
self :: core_mode($core_mode);
Log :: register_fatal_error_handler(array('\\EesyPHP\\Cli', 'fatal_error'));
$args = is_array($args)?$args:array_slice($argv, 1);
$log_level_set = false;
self :: $command = null;
@ -193,7 +194,6 @@ Additionnal parameters:
case '-h':
case '--help':
self :: usage();
break;
case '-d':
case '--debug':
Log :: set_level('DEBUG');
@ -257,7 +257,7 @@ Additionnal parameters:
* Command to create new project based on EesyPHP framework
*
* @param array $command_args The command arguments
* @return void
* @return true
*/
public static function cli_new_project($command_args) {
echo "This CLI tool permit to initialize a new project using the EesyPHP framework.\n";
@ -271,9 +271,9 @@ Additionnal parameters:
if (empty($input))
break;
if (!is_dir($input))
echo "Invalid root directory specified: not found or is not a directory\n";
self::fatal_error("Invalid root directory specified: not found or is not a directory");
else if (!is_writeable($input))
echo "Invalid root directory specified: not writeable\n";
self::fatal_error("Invalid root directory specified: not writeable");
else
$root_path = $input;
}
@ -289,23 +289,24 @@ Additionnal parameters:
$path = "$root_path/".$iterator->getSubPathname();
if ($item->isDir()) {
if (!mkdir($path))
die("Fail to create $path directory\n");
self::fatal_error("Fail to create $path directory");
}
else {
if (!copy($item, $path))
die("Fail to copy file to $path\n");
self::fatal_error("Fail to copy file to $path");
}
if (!chmod($path, $item->getPerms()))
die("Fail to chmod file $path\n");
self::fatal_error("Fail to chmod file $path");
}
echo "done. Start coding!\n";
return true;
}
/**
* Command to start PHP built-in HTTP server to serve the EesyPHP project
*
* @param array $command_args The command arguments
* @return void
* @return never
*/
public static function cli_serve($command_args) {
$listen_address = null;
@ -315,40 +316,32 @@ Additionnal parameters:
switch($command_args[$i]) {
case '-P':
case '--enable-profiler':
if (phpversion('xdebug') === false) {
if (phpversion('xdebug') === false)
self :: usage(I18n::_("The PHP XDEBUG extension is missing."));
return;
}
$enable_profiler = true;
break;
case '-O':
case '--profiler-output':
$i++;
$profiler_output_dir = $command_args[$i];
if (!is_dir($profiler_output_dir)) {
if (!is_dir($profiler_output_dir))
self :: usage(
I18n::_('Invalid profiler output directory "%s": not found'),
$profiler_output_dir
);
return;
}
if (!is_writable($profiler_output_dir)) {
if (!is_writable($profiler_output_dir))
self :: usage(
I18n::_('Invalid profiler output directory "%s": not writeable'),
$profiler_output_dir
);
return;
}
break;
default:
if (is_null($listen_address)) {
$i++;
$listen_address = $command_args[$i];
}
else {
else
self :: usage(I18n::_('Invalid parameter "%s"', $command_args[$i]));
return;
}
break;
}
}
@ -357,37 +350,29 @@ Additionnal parameters:
if (is_null($listen_address))
$listen_address = '127.0.0.1:8000';
$parts = explode(':', $listen_address);
if (count($parts) != 2) {
if (count($parts) != 2)
self :: usage(
I18n::_('Invalid listen address specify. Must be in formart host:port (or :port).')
);
return;
}
if (empty($parts[0])) {
$parts[0] = '0.0.0.0';
}
else if (!Check::ip_address($parts[0])) {
else if (!Check::ip_address($parts[0]))
self :: usage(
I18n::_('Invalid listen host specified. Must be an IPv4 or IPv6 address.')
);
return;
}
if (!Check::tcp_or_udp_port($parts[1])) {
if (!Check::tcp_or_udp_port($parts[1]))
self :: usage(
I18n::_('Invalid listen port specified. Must be a positive integer between 1 and 65535.')
);
return;
}
$listen_address = implode(':', $parts);
$public_html = App::get('root_directory_path')."/public_html";
chdir($public_html) or die(
sprintf(
chdir($public_html) or self :: fatal_error(
'Fail to enter in the public_html directory of the application (%s).',
$public_html
)
);
$args = array(
"-S", $listen_address,
@ -430,4 +415,23 @@ Additionnal parameters:
return $password;
}
/**
* Handle a fatal error in a CLI command
* @param string $error The error message
* @param array $extra_args Extra arguments to use to compute the error message using sprintf
* @return never
*/
public static function fatal_error($error, ...$extra_args) {
// If extra arguments passed, format error message using sprintf
if ($extra_args) {
$error = call_user_func_array(
'sprintf',
array_merge(array($error), $extra_args)
);
}
fwrite(STDERR, "FATAL ERROR : $error\n");
exit(1);
}
}

View file

@ -111,10 +111,8 @@ Class Config {
public static function isset($key, &$config=null) {
if (array_key_exists($key, self :: $extra_variables))
return true;
if (!is_array($config) && !self :: loaded()) {
if (!is_array($config) && !self :: loaded())
Log :: fatal('Configuration not loaded (on checking if %s is set)', $key);
exit(1);
}
$exploded_key = explode('.', $key);
if (!is_array($exploded_key)) return false;
$value = is_array($config)?$config:self :: $config;
@ -151,7 +149,6 @@ Class Config {
"Configuration not loaded (on getting %s):\n%s",
$key, Log :: get_debug_backtrace_context()
);
exit(1);
}
else {
$exploded_key = explode('.', $key);
@ -186,10 +183,8 @@ Class Config {
$config = is_null($config)?self :: $config:$config;
$iteration = 0;
while (preg_match('/\$\{([^\}]+)\}/', $value, $m)) {
if ($iteration > 20) {
if ($iteration > 20)
Log::fatal('Config::replace_variables(%s): max iteration reached');
return $value;
}
if (is_callable($config))
$replace_by = call_user_func($config, $m[1], '', 'string', false);
else

View file

@ -57,10 +57,8 @@ class Db {
*/
public function __construct($dsn, $user=null, $password=null, $options=null,
$date_format=null, $datetime_format=null, $locale_time=null) {
if (!$dsn) {
if (!$dsn)
Log :: fatal('Database DSN not configured');
return;
}
if ($date_format) $this -> date_format = $date_format;
if ($datetime_format) $this -> datetime_format = $datetime_format;
if ($locale_time) $this -> locale_time = $locale_time;

View file

@ -343,9 +343,8 @@ class I18n {
false, // do not escape command args (already done)
__DIR__ // Run from EesyPHP src directory
);
if (!is_array($eesyphp_php_files) || $eesyphp_php_files[0] != 0) {
if (!is_array($eesyphp_php_files) || $eesyphp_php_files[0] != 0)
Log :: fatal(self::_("Fail to list EesyPHP PHP files."));
}
// Extract messages from EesyPHP PHP files using xgettext
$pot_file = "$root_path/php-messages.pot";
@ -380,9 +379,8 @@ class I18n {
false, // do not escape command args (already done)
App :: root_directory_path() // Run from Application root directory
);
if (!is_array($php_files) || $php_files[0] != 0) {
if (!is_array($php_files) || $php_files[0] != 0)
Log :: fatal(self::_("Fail to list application PHP files."));
}
// Extract messages from PHP files using xgettext
$pot_file = "$root_path/php-messages.pot";
@ -416,10 +414,10 @@ class I18n {
false, // do not escape command args (already done)
dirname($static_directory) // Run from parent directory
);
if (!is_array($result) || $result[0] != 0) {
Log :: fatal(self::_("Fail to list JS files in the directory of static files '%s'."), $static_directory);
return;
}
if (!is_array($result) || $result[0] != 0)
Log :: fatal(
self::_("Fail to list JS files in the directory of static files '%s'."),
$static_directory);
// Extract messages from JS files using xgettext
$pot_file = "$root_path/js-$idx-messages.pot";
@ -512,11 +510,9 @@ class I18n {
foreach ($command_args as $arg) {
if (!file_exists($arg))
Log :: fatal(self::_("Compendium file %s not found."), $arg);
else {
$compendium_args[] = '-C';
$compendium_args[] = $arg;
}
}
$domain = Cli::core_mode()?self::CORE_TEXT_DOMAIN:self::TEXT_DOMAIN;
$root_path = Cli::core_mode()?self::$core_root_path:self::$root_path;
@ -587,7 +583,6 @@ class I18n {
}
Log :: fatal(self::_("Fail to open root lang directory (%s)."), App :: root_directory_path());
return false;
}
/**
@ -714,7 +709,6 @@ class I18n {
return !$error;
}
Log :: fatal(self::_("Fail to open root lang directory (%s)."), App :: root_directory_path());
return false;
}
}

View file

@ -82,7 +82,7 @@ class Log {
// Set log level:
// Note: in Phpstan context, force FATAL level
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__'))
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__')) // @phpstan-ignore-line
self :: set_level('FATAL');
else
self :: set_level(App::get('log.level', null, 'string'));
@ -150,7 +150,7 @@ class Log {
* @param string $level The message level (key of self :: $levels)
* @param string $message The message to log
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return true
* @return ($level is "FATAL" ? never : true)
*/
public static function log($level, $message, ...$extra_args) {
global $argv;
@ -295,10 +295,11 @@ class Log {
* Log an fatal message
* @param string $message The message to log
* @param array $extra_args Extra arguments to use to compute message using sprintf
* @return true
* @return never
*/
public static function fatal($message, ...$extra_args) {
return call_user_func_array(
// @phpstan-ignore-next-line
call_user_func_array(
array('EesyPHP\\Log', 'log'),
array_merge(array('FATAL', $message), $extra_args)
);

View file

@ -26,7 +26,7 @@ class SentryIntegration {
*/
public static function init() {
// In phpstan context, do not initialize
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__'))
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__')) // @phpstan-ignore-line
return;
// Set config default values
App :: set_default(

View file

@ -48,6 +48,7 @@ class SentrySpan {
*/
public function __construct($op, $name) {
// In phpstan context, do not initialize
// @phpstan-ignore-next-line
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__'))
return;
$this -> parent = \Sentry\SentrySdk::getCurrentHub()->getSpan();

View file

@ -28,6 +28,7 @@ class SentryTransaction {
*/
public function __construct($op=null, $name=null) {
// In phpstan context, do not initialize
// @phpstan-ignore-next-line
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__'))
return;
// Setup context for the full transaction
@ -54,6 +55,7 @@ class SentryTransaction {
*/
public function __destruct() {
// In phpstan context, do nothing
// @phpstan-ignore-next-line
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__'))
return;
SentrySpan :: finishAll();

View file

@ -89,6 +89,7 @@ class Tpl {
*/
public static function init() {
// In phpstan context, do not initialize
// @phpstan-ignore-next-line
if (defined('__PHPSTAN_RUNNING__') && constant('__PHPSTAN_RUNNING__'))
return;
@ -126,12 +127,10 @@ class Tpl {
// Handle and check templates_c directories
$templates_c_dir = App::get('templates.cache_directory', null, 'string');
if ($templates_c_dir) {
if (!is_dir($templates_c_dir) || !is_writable($templates_c_dir)) {
if (!is_dir($templates_c_dir) || !is_writable($templates_c_dir))
Log :: fatal(
"Template cache directory not found or not writable (%s)",
$templates_c_dir);
return;
}
}
else {
$public_root_url = Url :: public_root_url();
@ -140,13 +139,11 @@ class Tpl {
}
else {
$root_directory_path = App::root_directory_path();
if ($root_directory_path == '.') {
if ($root_directory_path == '.')
Log :: fatal(
'Fail to compute a unique templates cache directory for this application. An public '.
'root URL or an application root directory must be set if you do not provide it at '.
'initialization (or via config parameter).');
return;
}
$unique_name = $root_directory_path;
if (substr($unique_name, 0, 1) == '/')
$unique_name = substr($unique_name, 1);
@ -156,12 +153,10 @@ class Tpl {
$templates_c_dir = sys_get_temp_dir().'/'.str_replace(
'/', '_', "eesyphp_templates_cache_$unique_name"
);
if (!is_dir($templates_c_dir) && !mkdir($templates_c_dir)) {
if (!is_dir($templates_c_dir) && !mkdir($templates_c_dir))
Log :: fatal(
'Fail to create application templates cache directory (%s)',
$templates_c_dir);
return;
}
App :: set_default('templates.cache_directory', $templates_c_dir);
}
self :: $smarty = new Smarty();
@ -433,10 +428,8 @@ class Tpl {
* @return void
*/
public static function display($template, $pagetitle=null, ...$extra_args) {
if (!$template) {
if (!$template)
Log :: fatal(I18n::_("No template specified."));
return;
}
// If refresh parameter is present, remove it and redirect
if (isset($_GET['refresh'])) {
@ -445,7 +438,6 @@ class Tpl {
if (!empty($_GET))
$url .= '?'.http_build_query($_GET);
Url :: redirect($url);
return;
}
$sentry_span = new SentrySpan('smarty.display_template', "Display Smarty template");
@ -520,7 +512,7 @@ class Tpl {
* @param array|null $data AJAX returned data (optional)
* @param bool $pretty AJAX returned data
* (optional, default: true if $_REQUEST['pretty'] is set, False otherwise)
* @return void
* @return never
*/
public static function display_ajax_return($data=null, $pretty=false) {
if (!is_array($data))
@ -548,7 +540,7 @@ class Tpl {
* Handle a fatal error
* @param string $error The error message
* @param array $extra_args Extra arguments to use to compute the error message using sprintf
* @return void
* @return never
*/
public static function fatal_error($error, ...$extra_args) {
// If extra arguments passed, format error message using sprintf
@ -559,17 +551,18 @@ class Tpl {
);
}
if (php_sapi_name() == "cli")
die("FATAL ERROR : $error\n");
if (php_sapi_name() == "cli") {
if (App :: get('cli.enabled', null, 'bool'))
Cli :: fatal_error($error);
die("FATAL ERROR: $error\n");
}
// Set HTTP reponse code to 500
http_response_code(500);
// Handle API mode
if (Url :: api_mode()) {
if (Url :: api_mode())
self :: display_ajax_return(array('success' => false, 'error' => $error));
return;
}
self :: assign('fatal_error', $error);
self :: display('fatal_error.tpl');
@ -610,12 +603,10 @@ class Tpl {
* @return void
*/
public static function register_templates_directory($path, $priority=null) {
if (!is_dir($path)) {
if (!is_dir($path))
Log :: fatal(
'register_templates_directory(%s): this templates directory does not exists',
$path);
return;
}
if (substr($path, -1) == '/')
$path = substr($path, 0, -1);
if (is_null($priority)) {
@ -800,7 +791,7 @@ class Tpl {
* Note: this URL handler is registered in EesyPHP\Url by self::init().
* @see self::init()
* @param UrlRequest $request
* @return void
* @return never
*/
public static function handle_static_file($request) {
$path = self :: resolve_static_path($request->root_url, $request->path);

View file

@ -169,7 +169,7 @@ class Url {
* @param $request UrlRequest|null The request (optional, default: null)
* @param $error_code int|null The HTTP error code (optional, default: 400)
*
* @return void
* @return never
**/
public static function error_page($request=null, $error_code=null) {
$http_errors = array(
@ -213,7 +213,7 @@ class Url {
*
* @param UrlRequest|null $request The request (optional, default: null)
*
* @return void
* @return never
**/
public static function error_404($request=null) {
self :: error_page($request, 404);
@ -240,10 +240,11 @@ class Url {
/**
* Trigger a 404 HTTP error
* @param UrlRequest|null $request Current UrlRequest object (optional, default: null)
* @return void
* @return never
*/
public static function trigger_error_404($request=null) {
call_user_func_array(self :: $error_404_handler, array($request));
exit();
}
@ -257,17 +258,13 @@ class Url {
*/
protected static function get_request($default_url=null) {
$current_url = self :: get_current_url();
if ($current_url === false) {
if ($current_url === false)
Log :: fatal(
I18n::_('Unable to determine the requested page. '.
'If the problem persists, please contact support.')
);
exit();
}
if (!is_array(self :: $patterns)) {
if (!is_array(self :: $patterns))
Log :: fatal('URL : No URL patterns configured !');
exit();
}
Log :: debug("URL : current url = '$current_url'");
if (array_key_exists($_SERVER['REQUEST_METHOD'], self :: $patterns)) {
@ -295,7 +292,6 @@ class Url {
if ($default_url !== false) {
Log :: debug("Current url match with no pattern. Redirect to default url ('$default_url')");
self :: redirect($default_url);
exit();
}
// Error 404
$api_mode = self :: $_api_mode || (strpos($current_url, 'api/') === 0);
@ -388,7 +384,7 @@ class Url {
*
* @param string|false $go The destination URL
*
* @return void
* @return never
**/
public static function redirect($go=false) {
if ($go===false)
@ -415,7 +411,6 @@ class Url {
Log :: fatal(
I18n::_('Unable to determine the requested page (loop detected). '.
'If the problem persists, please contact support.'));
else
$_SESSION['last_redirect'] = $url;
Log :: debug("redirect($go) => Redirect to : <$url>");

View file

@ -136,7 +136,7 @@ function cast($value, $type, $split=false) {
* @param string|null $mime_type The file MIME type (optional, default: auto-detected)
* @param int|null $max_age Max age in second of this file in browser cache (optional,
* default: null=1h, set to False to disable Cache-Control header)
* @return void
* @return never
*/
function dump_file($file_path, $mime_type=null, $max_age=null) {
if (is_file($file_path)) {