Utility Libraries

All utilities use static methods and have no external dependencies. Their namespace is _Flexagon\Libs.

Class Key Features
StringUtil case conversion, UTF-8 cleanup, hex encoding, random string generation
ArrayUtil camelCase key conversion, array diff, nested-array key lookup
ValidUtil email, integer, float, alphanumeric, and ID validation
CryptoUtil AES-256-CBC encryption/decryption (deterministic encryptString() for columns; random-IV encrypt() for strings / arrays)
FileUtil directory traversal, recursive creation, extension detection
HttpUtil URL parsing, query-string parsing, request-path resolution
NetUtil GET/POST HTTP client, JSON response parsing, file uploads
HtmlUtil redirects, HTML tag/attribute removal, image URL extraction
TimeDateUtil timestamp conversion, timezone offsets, relative time formatting
SqlUtil parameter normalization/expansion, SQL escaping, SELECT → COUNT conversion
ClassUtil class name, path, property, and caller inspection
PhpDocUtil PHPDoc tag parsing (basis of annotation support)
AssetLoader CSS/JS management
TemplateLoader template loading
DataSourceManager connection reuse and global transactions
Env settings and secrets from the environment and .env.php

Global Helper Functions

The three functions defined by Libs/_Util.php are available globally without a namespace.

_echo('Hello, {name}', ['name' => $user->getName()]);   // substitute and output
$msg = _t('{count} results found', ['count' => 42]);    // substitute and return

___() is an alias of _t(). Placeholders are substituted in a single pass, so a value that itself contains {name} is not substituted again.

Translation (I18n)

The helpers translate a message through the translator registered with _Flexagon\I18n\Translator before substituting placeholders. If no translator is registered, the original message is used as is.

A translator implements a single method.

namespace _Flexagon\I18n;

interface TranslatorInterface
{
    public function translate(string $message): string;
}

Register it once, early in the request (public/_entry.php or application/_Config.php).

// public/_entry.php
use _Flexagon\I18n\Translator;

setlocale(LC_MESSAGES, 'ko_KR.UTF-8');
bindtextdomain('messages', LOCALE_ROOT);
textdomain('messages');

Translator::setTranslator(new \I18n\GettextTranslator());

The skeleton ships three example adapters in application/I18n/.

Class Translates with Requires
GettextTranslator gettext() ext-gettext
MoTranslator __() A library that defines __() (e.g. a .mo reader)
SymfonyTranslator trans() symfony/translation
Translator::setTranslator(new \I18n\SymfonyTranslator($symfonyTranslator));

LOCALE_ROOT defaults to PROJECT_ROOT . '/locale'. To change it, define() it before the framework boots.

Keep these points in mind:

  • Output is not escaped. _echo() prints the translated, substituted string as is. Escape user-supplied values with htmlspecialchars() before passing them in, and treat translation catalogs as trusted HTML sources.
  • The translator is static, per process. That fits the PHP-FPM model, where every request starts clean. In long-running workers (Swoole, RoadRunner, FrankenPHP worker mode), re-register the translator for every request. setlocale() is process-wide and not safe across threads.
  • The adapter determines the cost. The framework adds about 0.3 µs per call. gettext caches .mo files per process (restart PHP-FPM after updating them); some .mo libraries parse the file on every request; Symfony Translation reloads catalogs on each request unless cache_dir is set.

Enum Helpers

Add the EnumSupporter trait to a backed enum to enable convenience methods.

use _Flexagon\Supporters\EnumSupporter;

enum UserRole: string {
    use EnumSupporter;

    case ADMIN = 'ADMIN';
    case USER  = 'USER';
}

UserRole::keys();            // ['ADMIN', 'USER']
UserRole::values();          // ['ADMIN', 'USER']
UserRole::toArray();         // ['ADMIN' => 'ADMIN', 'USER' => 'USER']
UserRole::toCommaString();   // 'ADMIN,USER'

When generating DDL for an ENUM column, use toQuotedCommaString(). It quotes each value and escapes embedded quotes by doubling them.

UserRole::toQuotedCommaString();      // "'ADMIN','USER'"
UserRole::toQuotedCommaString('"');   // '"ADMIN","USER"'

// Safe even when values contain quotes or commas
// case A = "it's";  case B = 'a,b';
//   toCommaString()        → it's,a,b          (breaks DDL)
//   toQuotedCommaString()  → 'it''s','a,b'

For pure enums without backing values, keys() and toCommaString() return case names, while values() and toArray() return null values.

Enum-typed properties are also handled automatically by DAO mapping. Scalar database values are converted to matching enum cases when models are hydrated, and ->value is bound when values are stored.