Robert 1 month ago
parent 3fbb9bc3c8
commit 254cc88a42
  1. 7
      src/Framework/Common.php
  2. 31
      src/Framework/Dollars.php
  3. 100
      src/Framework/Http/App/App.php
  4. 31
      src/Framework/Http/App/AppHandler.php
  5. 31
      src/Framework/Http/App/MiddlewareHandler.php
  6. 21
      src/Framework/Http/App/MiddlewareInterface.php
  7. 12
      src/Framework/Http/Kernel.php
  8. 236
      src/Framework/MoneyFormatter.php
  9. 16
      src/Framework/View.php

@ -126,11 +126,14 @@ final class Common
return $combined;
}
public static function doesAllOfTheArrayHaveData(array $a, array $skipKeys = []): false|array
public static function doesAllOfTheArrayHaveData(
array $a, array $skipKeys = [],
bool $failOnEmpty = false
): false|array
{
if (self::isArrayWithData($a)) {
foreach ($a as $key => $value) {
if ($value === false) {
if ($value === false || ($failOnEmpty === true && empty($value))) {
if (
self::isArrayWithData($skipKeys) && in_array($key, $skipKeys)
) {

@ -1,31 +0,0 @@
<?php
declare(strict_types = 1);
/**
* @author Robert Strutts
* @copyright (c) 2026, Robert Strutts
* @license MIT
*/
namespace IOcornerstone\Framework;
class Dollars
{
private function moneyAsFloat(Number $money): float {
// Remove any existing currency symbols and thousands separators
$t = preg_replace('/[^\d.-]/', '', $money);
return (float)$t;
}
public function formatAsUSD(Number $money): string {
return '$' . number_format($this->moneyAsFloat($money), 2, '.', ',');
}
public function intl_USD(Number $money): string|false {
if (class_exists('NumberFormatter')) {
$formatter = new \NumberFormatter('en_US', \NumberFormatter::CURRENCY);
return $formatter->formatCurrency($this->moneyAsFloat($money), 'USD');
}
return false;
}
}

@ -13,6 +13,7 @@ namespace IOcornerstone\Framework\Http\App;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use IOcornerstone\Framework\Http\{
App\MiddlewareHandler,
Contract\MiddlewareAwareInterface,
Request,
HttpFactory
@ -27,7 +28,7 @@ use IOcornerstone\Framework\{
};
use Exception;
class App implements MiddlewareAwareInterface
class App implements MiddlewareAwareInterface
{
private $file;
@ -39,7 +40,10 @@ class App implements MiddlewareAwareInterface
private bool $testing = false;
private string $dirClass = ""; // Testing, CLI, or empty
public function __construct() {}
public function __construct()
{
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
@ -48,16 +52,16 @@ class App implements MiddlewareAwareInterface
if ($ret === false) {
return $this->local404();
}
// Make sure $ret is a ResponseInterface
if (!$ret instanceof ResponseInterface) {
// You might need to convert it, for example:
return (new HttpFactory())->createResponse(200, [], $ret);
return (new HttpFactory())->createResponse(body: $ret);
}
return $ret;
}
public function getMiddleware(): array
{
return $this->middleWare;
@ -94,7 +98,7 @@ class App implements MiddlewareAwareInterface
/**
* Do not declare a return type here, as it will Error out!!
*/
private function startUp()
private function startUp(): mixed
{
$full_route = $this->request->getUri()->getPath();
$noFileExt = $this->GetWithoutFileExtension($full_route);
@ -170,14 +174,14 @@ class App implements MiddlewareAwareInterface
if ($dir === false) {
return false;
}
}
}
$file = Requires::saferFileExists(basename($safeFile) . 'Controller.php', $dir);
if ($file === false || $file === null) {
return false;
}
$class = Security::filterClass($safeFolders) . "\\" . Security::filterClass($safeFile . "Controller");
if (empty(trim($method))) {
$method = ""; // Clear out null if exists
}
@ -200,6 +204,47 @@ class App implements MiddlewareAwareInterface
return (new HttpFactory())->createResponse(404, [], $myView);
}
private function RunIt(object $ctrl, string $method, $params): mixed
{
try {
if (! method_exists($ctrl, $method)) {
return false;
}
$reflection = new \ReflectionMethod($ctrl, $method);
if ($reflection->isPrivate()) {
return false;
}
if ($reflection->isProtected()) {
return false;
}
if ($reflection->isPublic()) {
return $this->doMiddleware($ctrl, $method, $params);
}
} catch (\ReflectionException $e) {
return false;
}
}
private function doMiddleware($appController, string $method, $params)
{
$middleware = $this->getMiddleware();
// Does it NOT contain an array of middleware?
if (! Common::isArrayWithData($middleware)) {
return $appController->$method($params);
}
$handler = new MiddlewareHandler();
return $handler->run(
$appController,
$middleware,
fn ($appController) => $appController->$method($params)
);
}
/**
* Do controller action
* @param string $file
@ -207,7 +252,7 @@ class App implements MiddlewareAwareInterface
* @param string $method
* @retval type
*/
private function action(string $file, string $class, string $method, $params)
private function action(string $file, string $class, string $method, $params): mixed
{
$safer_file = Requires::saferFileExists($file);
if (!$safer_file) {
@ -217,41 +262,24 @@ class App implements MiddlewareAwareInterface
return false;
}
$use_api = false; // Misc::isApi();
$callClass = "\\Project\\" . $this->dirClass . "Controllers\\" . $class;
$controller = new $callClass($this->request);
// Collect controller-level middleware Directly from the controller file, IE: public static array $middleware = [ \Project\classes\auth_middleware::class ];
$this->middleWare = $controller::$middleware ?? [];
// Collect controller-level middleware Directly from the controller file, IE: public static array $middleware = [ AuthMiddleware::class ];
$this->middleWare = $controller::$middleware ?? [];
if ($method === "error" && str_contains($class, "App") &&
method_exists($controller, $method) === false
method_exists($controller, $method) === false
) {
//Broken_error();
return false;
}
if ($use_api) {
if (empty($method)) {
$method = "index";
}
$method .= "_api";
if (method_exists($controller, $method)) {
return $controller->$method($params);
} else {
return false;
}
} else {
if (!empty($method) && method_exists($controller, $method)) {
return $controller->$method($params);
} else {
if (empty($method) && method_exists($controller, 'index')) {
return $controller->index($params);
}
}
if (empty($method)) {
$method = "Index";
}
return false;
return $this->RunIt($controller, $method, $params);
}
}

@ -27,18 +27,27 @@ final class AppHandler implements RequestHandlerInterface
{
$result = $this->app->handle($request);
// Convert to ResponseInterface if needed
if ($result === false) {
return new Response(
body: Stream::fromString('404 Not Found'),
status: 404
);
}
if (is_array($result)) {
return new Response(
body: $result,
headers: ['Content-Type' => 'application/json']
);
}
if ($result instanceof ResponseInterface) {
return $result;
}
// If it returns false (for 404), create a response
if ($result === false) {
return new Response('404 Not Found', 404);
}
$stream = Stream::fromString($result);
// If it returns something else, wrap it
return new Response(body: $result);
return new Response(
body: Stream::fromString((string)$result)
);
}
}
}

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright (c) 2026, Robert Strutts
* @license MIT
*/
namespace IOcornerstone\Framework\Http\App;
class MiddlewareHandler
{
public function run(mixed $request, array $middleware, callable $destination): mixed
{
$pipeline = array_reduce(
array_reverse($middleware),
function ($next, string $middlewareClass) {
return function ($request) use ($middlewareClass, $next) {
$instance = new $middlewareClass();
return $instance->handle($request, $next);
};
},
$destination
);
return $pipeline($request);
}
}

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright (c) 2026, Robert Strutts
* @license MIT
*/
namespace IOcornerstone\Framework\Http\App;
/**
* Description of MiddlewareInterface
*
* @author Robert Strutts
*/
interface MiddlewareInterface
{
public function handle(mixed $request, callable $next): mixed;
}

@ -45,7 +45,7 @@ class Kernel {
return; // Running CLI from Framework test script
}
if (! class_exists(\Project\Providers\RouteServiceProvider::class)) {
throw new NoRouteProviderFoundException("Class NOT found: Project\Providers\RouteServiceProvider");
return; // No Routes Defined which is fine....
}
try {
$routeProviders = new \Project\Providers\RouteServiceProvider();
@ -61,7 +61,7 @@ class Kernel {
throw new RouteProviderException("Unable to call register on Route ServiceProvider \r\n" . $e->getMessage());
}
}
private function buildKernel()
{
$router = new Router();
@ -82,13 +82,7 @@ class Kernel {
// Combine global middleware
$globalMiddleware = $this->getGlobalMiddleware();
// Add app-specific middleware if any
$appMiddleware = $app->getMiddleware();
if (!empty($appMiddleware)) {
array_splice($globalMiddleware, -1, 0, $appMiddleware);
}
return new MiddlewareQueueHandler(
$globalMiddleware,
$routingHandler

@ -0,0 +1,236 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright (c) 2026, Robert Strutts
* @license MIT
*/
namespace IOcornerstone\Framework;
use NumberFormatter;
/**
* Description of MoneyFormatter
* Requries php8.5-intl
* @TODO install intl
* sudo apt install php8.5-intl
* @author Robert Strutts
*/
class MoneyFormatter
{
private const CURRENCIES = [
'USD' => ['symbol' => '$', 'locale' => 'en_US', 'name' => 'US Dollar'],
'CAD' => ['symbol' => '$', 'locale' => 'en_CA', 'name' => 'Canadian Dollar'],
'EUR' => ['symbol' => '€', 'locale' => 'de_DE', 'name' => 'Euro'],
'GBP' => ['symbol' => '£', 'locale' => 'en_GB', 'name' => 'British Pound'],
'JPY' => ['symbol' => '¥', 'locale' => 'ja_JP', 'name' => 'Japanese Yen'],
'CNY' => ['symbol' => '¥', 'locale' => 'zh_CN', 'name' => 'Chinese Yuan'],
'AUD' => ['symbol' => '$', 'locale' => 'en_AU', 'name' => 'Australian Dollar'],
'NZD' => ['symbol' => '$', 'locale' => 'en_NZ', 'name' => 'New Zealand Dollar'],
'CHF' => ['symbol' => 'CHF','locale' => 'de_CH', 'name' => 'Swiss Franc'],
'SEK' => ['symbol' => 'kr', 'locale' => 'sv_SE', 'name' => 'Swedish Krona'],
'NOK' => ['symbol' => 'kr', 'locale' => 'nb_NO', 'name' => 'Norwegian Krone'],
'DKK' => ['symbol' => 'kr', 'locale' => 'da_DK', 'name' => 'Danish Krone'],
'PLN' => ['symbol' => 'zł', 'locale' => 'pl_PL', 'name' => 'Polish Zloty'],
'CZK' => ['symbol' => 'Kč', 'locale' => 'cs_CZ', 'name' => 'Czech Koruna'],
'HUF' => ['symbol' => 'Ft', 'locale' => 'hu_HU', 'name' => 'Hungarian Forint'],
'RON' => ['symbol' => 'lei','locale' => 'ro_RO', 'name' => 'Romanian Leu'],
'BGN' => ['symbol' => 'лв', 'locale' => 'bg_BG', 'name' => 'Bulgarian Lev'],
'TRY' => ['symbol' => '₺', 'locale' => 'tr_TR', 'name' => 'Turkish Lira'],
'RUB' => ['symbol' => '₽', 'locale' => 'ru_RU', 'name' => 'Russian Ruble'],
'UAH' => ['symbol' => '₴', 'locale' => 'uk_UA', 'name' => 'Ukrainian Hryvnia'],
'INR' => ['symbol' => '₹', 'locale' => 'hi_IN', 'name' => 'Indian Rupee'],
'PKR' => ['symbol' => '₨', 'locale' => 'ur_PK', 'name' => 'Pakistani Rupee'],
'BDT' => ['symbol' => '৳', 'locale' => 'bn_BD', 'name' => 'Bangladeshi Taka'],
'LKR' => ['symbol' => 'Rs', 'locale' => 'si_LK', 'name' => 'Sri Lankan Rupee'],
'NPR' => ['symbol' => '₨', 'locale' => 'ne_NP', 'name' => 'Nepalese Rupee'],
'THB' => ['symbol' => '฿', 'locale' => 'th_TH', 'name' => 'Thai Baht'],
'VND' => ['symbol' => '₫', 'locale' => 'vi_VN', 'name' => 'Vietnamese Dong'],
'MYR' => ['symbol' => 'RM', 'locale' => 'ms_MY', 'name' => 'Malaysian Ringgit'],
'SGD' => ['symbol' => '$', 'locale' => 'en_SG', 'name' => 'Singapore Dollar'],
'IDR' => ['symbol' => 'Rp', 'locale' => 'id_ID', 'name' => 'Indonesian Rupiah'],
'PHP' => ['symbol' => '₱', 'locale' => 'en_PH', 'name' => 'Philippine Peso'],
'KRW' => ['symbol' => '₩', 'locale' => 'ko_KR', 'name' => 'South Korean Won'],
'HKD' => ['symbol' => '$', 'locale' => 'zh_HK', 'name' => 'Hong Kong Dollar'],
'TWD' => ['symbol' => 'NT$','locale' => 'zh_TW', 'name' => 'New Taiwan Dollar'],
'AED' => ['symbol' => 'د.إ','locale' => 'ar_AE', 'name' => 'UAE Dirham'],
'SAR' => ['symbol' => '﷼', 'locale' => 'ar_SA', 'name' => 'Saudi Riyal'],
'QAR' => ['symbol' => '﷼', 'locale' => 'ar_QA', 'name' => 'Qatari Riyal'],
'KWD' => ['symbol' => 'د.ك','locale' => 'ar_KW', 'name' => 'Kuwaiti Dinar'],
'BHD' => ['symbol' => '.د.ب','locale' => 'ar_BH', 'name' => 'Bahraini Dinar'],
'OMR' => ['symbol' => '﷼', 'locale' => 'ar_OM', 'name' => 'Omani Rial'],
'ILS' => ['symbol' => '₪', 'locale' => 'he_IL', 'name' => 'Israeli New Shekel'],
'ZAR' => ['symbol' => 'R', 'locale' => 'en_ZA', 'name' => 'South African Rand'],
'NGN' => ['symbol' => '₦', 'locale' => 'en_NG', 'name' => 'Nigerian Naira'],
'EGP' => ['symbol' => 'E£', 'locale' => 'ar_EG', 'name' => 'Egyptian Pound'],
'KES' => ['symbol' => 'KSh','locale' => 'sw_KE', 'name' => 'Kenyan Shilling'],
'GHS' => ['symbol' => '₵', 'locale' => 'en_GH', 'name' => 'Ghanaian Cedi'],
'BRL' => ['symbol' => 'R$', 'locale' => 'pt_BR', 'name' => 'Brazilian Real'],
'ARS' => ['symbol' => '$', 'locale' => 'es_AR', 'name' => 'Argentine Peso'],
'CLP' => ['symbol' => '$', 'locale' => 'es_CL', 'name' => 'Chilean Peso'],
'COP' => ['symbol' => '$', 'locale' => 'es_CO', 'name' => 'Colombian Peso'],
'MXN' => ['symbol' => '$', 'locale' => 'es_MX', 'name' => 'Mexican Peso'],
'PEN' => ['symbol' => 'S/', 'locale' => 'es_PE', 'name' => 'Peruvian Sol'],
'UYU' => ['symbol' => '$U', 'locale' => 'es_UY', 'name' => 'Uruguayan Peso'],
];
/**
* SQL for money:
* balance BIGINT NOT NULL DEFAULT 0,
* currency CHAR(3) NOT NULL DEFAULT 'USD'
*/
public static function getBalance(
float $input,
string $currency = 'USD',
string $locale = 'en_US'
): string
{
$locale = self::getLocaleFromCode($currency, $locale);
$formatter = new NumberFormatter(
$locale,
NumberFormatter::CURRENCY
);
$amount = $formatter->formatCurrency(
abs($input),
$currency
);
if ($input < 0.00) {
return "-{$amount}";
}
if ($input > 0.01) {
return "+{$amount}";
}
return $amount;
}
public static function getColorClass(float $input): string
{
if ($input < 0.00) {
return " money-negitive ";
} else if ($input > 0.01) {
return " money-positive ";
} else {
return " money-zero ";
}
}
/**
* To store into the Database as whole integer
* This is For Migrations ONLY!!!
* Please use parseInputToMinorUnits on USER INPUT.
*/
public static function amountToMinorUnits(
float $amount,
string $currency = 'USD',
string $locale = 'en_US'
): int
{
$locale = self::getLocaleFromCode($currency, $locale);
$formatter = new NumberFormatter(
$locale,
NumberFormatter::CURRENCY
);
$formatter->setTextAttribute(
NumberFormatter::CURRENCY_CODE,
$currency
);
$digits = $formatter->getAttribute(
NumberFormatter::FRACTION_DIGITS
);
return (int) round( $amount * (10 ** $digits));
}
/**
* To get back from the Database into Decimal Numbers for Output
*/
public static function minorUnitsToAmount(
int $amount,
string $currency = 'USD',
string $locale = 'en_US'
): float
{
$locale = self::getLocaleFromCode($currency, $locale);
$formatter = new NumberFormatter(
$locale,
NumberFormatter::CURRENCY
);
$formatter->setTextAttribute(
NumberFormatter::CURRENCY_CODE,
$currency
);
$digits = $formatter->getAttribute(
NumberFormatter::FRACTION_DIGITS
);
return $amount / (10 ** $digits);
}
/**
* Parse User Input Safely, to be saved into Database
*/
public static function parseInputToMinorUnits(
string $input,
string $currency = 'USD',
string $locale = 'en_US'
): int
{
$locale = self::getLocaleFromCode($currency, $locale);
return self::amountToMinorUnits((float) $input, $currency, $locale);
}
public static function convertToUsd(
int $minorUnits,
float $exchangeRate,
string $currency,
): int {
return (int) round($minorUnits * $exchangeRate);
}
public static function getCurrencyCodes(string $unit = "USD"): string
{
$ret = "";
foreach (self::CURRENCIES as $code => $currency) {
$ret .= sprintf(
'<option value="%s" %s>%s (%s)</option>',
$code,
($code === $unit) ? "selected" : "",
htmlspecialchars($currency['name']),
$code
);
}
return $ret;
}
public static function getLocaleFromCode(string $currencyCode = "USD", string $locale = "en_US"): ?string
{
if ($currencyCode === "USD" && $locale === "en_US") {
return $locale;
}
if ($currencyCode !== "USD" && ! empty($locale) && $locale !== "en_US") {
return $locale;
}
foreach (self::CURRENCIES as $code => $currency) {
if ($code === $currencyCode) {
return $currency['locale'] ?? null;
}
}
return null;
}
}

@ -20,7 +20,7 @@ use IOcornerstone\Framework\{
final class View
{
const POST_VIEW_FILE_NAME = "View"; // Add this to the end of view file
public bool $whiteSpaceControl = false;
public string $pageOutput;
private array $vars = [];
@ -102,9 +102,15 @@ final class View
if ($viewFile === null) {
return;
}
$addPostFixedView = self::POST_VIEW_FILE_NAME;
$justFileName = pathinfo($viewFile, PATHINFO_FILENAME);
if (stripos($justFileName, self::POST_VIEW_FILE_NAME) !== false) {
$addPostFixedView = ""; // Give it an empty string as it already has the view in the FileName!
}
$fileExt = ViewType::getFileExtensionFor($type);
$file = $this->findViewPath($viewFile . "View" . $fileExt);
$file = $this->findViewPath($viewFile . $addPostFixedView . $fileExt);
if ($type == ViewType::TWIG) {
$this->useTemplateEngineTwig = true;
$this->templateType = str_replace('.', '', $fileExt);
@ -122,8 +128,8 @@ final class View
}
if ($file === false) {
echo "No view file exists for: {$viewFile}View!";
throw new \Exception("View File does not exist: " . $viewFile. "View");
echo "No view file exists for: {$viewFile}{$addPostFixedView}!";
throw new \Exception("View File does not exist: " . $viewFile . $addPostFixedView);
} else {
$this->files[] = array('file' => $file, 'path' => UseDir::PROJECT, 'fileType' => $fileExt);
}

Loading…
Cancel
Save