Helpers — traits and helper services

SkillDev tools

Catalog of OrangeHRM's framework-wide helper traits and helper services — the trait composition pattern (services, decorators, subscribers, commands, endpoints all `use` these), the `core helper services` (`DateTimeHelperService`, `NumberHelperService`, `TextHelperService`, `NormalizerService`, `MenuService`), the framework-level traits (`ServiceContainerTrait`, `EventDispatcherTrait`, `LoggerTrait`, `CacheTrait`, `ValidatorTrait`, `ClassHelperTrait`, `ETagHelperTrait`, `ModuleScreenHelperTrait`, `ControllerTrait`, `UserRoleManagerTrait`, `AuthUserTrait`, `EntityManagerHelperTrait`/`EntityManagerTrait`), and the `Helper/` plain-class helpers (`ClassHelper`, `VueControllerHelper`, `ModuleScreenHelper`, `LocalizedDateFormatter`). Use whenever the user is about to write a util function for something common (date formatting, string operations, number formatting, current-user lookup, cache access, etc.) — to find out if there's already a helper to `use` instead. Companion to `services` (the canonical trait consumer), `daos` (which uses `EntityManagerHelperTrait`), `authorization` (uses `UserRoleManagerTrait` / `AuthUserTrait`).

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Helpers — traits and helper services skill

What this skill tells your AI

The instructions your AI receives, as published by orangehrm/orangehrm in .agents/skills/helpers/SKILL.md and read by ahel’s review.

OrangeHRM has a lot of helpers scattered across the core plugin. The naming convention is consistent: most are use-able traits, and each trait typically wraps one helper service. Before writing a util function, check this catalog — the answer is usually "already exists."

This skill is a reference catalog. Each entry shows what to use, what method(s) it exposes, and the common case. For deeper documentation on specific helpers, see the linked skill.

How the trait pattern works (recap)

The codebase consistently uses traits as the DI access layer:

class MyClass
{
    use DateTimeHelperTrait;        // ← gives $this->getDateTimeHelper()
    use TextHelperTrait;            // ← gives $this->getTextHelper()
    use UserRoleManagerTrait;       // ← gives $this->getUserRoleManager()

    public function doSomething(): void
    {
        $now = $this->getDateTimeHelper()->getNow();
        $clean = $this->getTextHelper()->strip(...);
    }
}

Traits typically delegate to the DI container or instantiate the helper lazily. Same pattern in services, decorators, subscribers, commands, endpoints, validators — everywhere. See services skill for the full pattern.

Core helper services + their traits

These are the "everyday" helpers — formatters, normalizers, cross-cutting utilities. Each has a service class in orangehrmCorePlugin/Service/ and a matching trait in orangehrmCorePlugin/Traits/Service/.

TraitServiceExposesUse for
DateTimeHelperTraitDateTimeHelperServicegetNow(), formatDate(), formatDateTime(), getDateTimeHelper() (returns the service), TIMEZONE_UTC constantAnything date/time. Always use this rather than new DateTime() directly — it respects the user's timezone.
TextHelperTraitTextHelperServicestrStartsWith(), strEndsWith(), strContains(), truncate(), stripTags(), more string opsPolyfills for older PHP str_* methods and shared text munging.
NumberHelperTraitNumberHelperServiceNumber formatting, roundingCurrency-like display, fixed-decimal output.
NormalizerServiceTraitNormalizerServicenormalize($entityOrCollection, $class)Convert a Doctrine entity to a Model's array output without going through EndpointResult. Used by services that need to emit Model-shaped data outside the REST flow.
ConfigServiceTraitConfigServiceThe full config getter/setter catalogSee config skill.
MenuServiceTraitMenuServiceSide panel + top menu renderingInternal — used by the Twig layout. Rarely called from feature code.
ReportGeneratorServiceTraitReportGeneratorServiceReport renderingInternal to the reports system.

Plugin-specific service traits (representative)

Every plugin has its own Traits/Service/<Name>ServiceTrait.php for each of its services. The pattern is universal — see services skill. Most-frequently used from outside their owning plugin:

TraitUse for
UserServiceTrait (Admin)Current user lookups, user management
EmployeeServiceTrait (Pim)Employee operations
LeaveServiceTrait (Leave)Leave allocation, approval flows
CompanyStructureServiceTrait (Admin)Subunit tree operations
EmailServiceTrait (Core, via Mail)Sending emails — see mail skill

When pulling in a cross-plugin service, you use that plugin's trait. The dependency between plugins is fine; the trait keeps consumers from instantiating the service directly.

Framework-level traits (no separate service)

Most of these wrap framework-provided objects (event dispatcher, logger, cache, validator) rather than OHRM-specific services.

TraitExposesUse for
ServiceContainerTraitgetContainer(): ContainerRaw DI container access — rare in feature code; mostly used by other traits internally. Avoid unless you specifically need it.
EventDispatcherTraitgetEventDispatcher()Dispatch events. See events skill.
LoggerTraitgetLogger()Monolog logger writing to src/log/orangehrm.log. Use for error, warning, info, debug log messages.
CacheTraitgetCache($namespace)Symfony Cache adapter access. Per-namespace; common namespace is orangehrm for app-level caching.
ValidatorTraitvalidate($values, ParamRuleCollection)Programmatic invocation of the validator — same call the REST framework makes. Use for validating data outside the REST flow (e.g. in a CLI command, in a CSV importer). See rest-validation.
ClassHelperTraitgetClassHelper(): ClassHelperReflection + class name resolution. Used internally by the home-page enabler lookup, the menu configurator resolver, etc.
ETagHelperTraitgenerateETag($data), setETag($response, $etag)ETag generation for HTTP caching headers — used by file controllers and the OXD logo endpoint.

Authentication / authorization traits

TraitExposesUse for
AuthUserTraitgetAuthUser(): AuthUserCurrent authenticated user — session-bound. getUserId(), getEmpNumber(), isAuthenticated(), attribute storage.
UserRoleManagerTraitgetUserRoleManager()The whole RBAC system — see authorization skill. getAccessibleEntityIds(), getApiPermissions(), getScreenPermissions(), etc.

Doctrine / ORM traits

Already covered in detail in daos skill:

TraitExposesUse for
EntityManagerTraitgetEntityManager()Bare EM access.
EntityManagerHelperTrait(extends EntityManagerTrait) getRepository(), persist(), remove(), createQueryBuilder(), getPaginator(), getQueryBuilderWrapper(), fetchOne(), getReference(), beginTransaction()/commitTransaction()/rollBackTransaction()The DAO surface — but also usable in services, decorators, validator rules, listeners.

Controller / module traits

TraitExposesUse for
ControllerTraitforward($controllerSpec) for sub-controller dispatchUsed by exception subscribers to render error pages via another controller (e.g. ForbiddenController from ScreenAuthorizationSubscriber). Rare in feature code.
ModuleScreenHelperTraitgetCurrentModuleAndScreen()Resolves the active module + screen from the URL — used by ScreenAuthorizationSubscriber. Rare in feature code.
Controller/VueComponentPermissionTraitHelpers for AbstractVueController to filter component props by permissionInside preRender() to decide what data to pass to the Vue component.

Encryption / security traits

See the security-primitives skill for the full treatment. Quick references:

TraitExposesUse for
EncryptionHelperTrait (under Utility/)encryptionEnabled(), getCryptographer()Used inside EntityListener classes to encrypt/decrypt sensitive columns.

Plain Helper/ classes (not traits)

orangehrmCorePlugin/Helper/ has a few non-trait helpers — instantiate with new or accessed via a service.

ClassUse for
ClassHelperclassExists($className, $namespace = ''), getClass(). Reflection-style class resolution with fallback namespace. Used by HomePageService and menu configurators.
VueControllerHelperBuilds the context passed to the Vue Twig template (baseUrl, user, permissions, etc.). Internal to AbstractVueController.
ModuleScreenHelperStatic utility for module/screen URL parsing. Used by ModuleScreenHelperTrait.
LocalizedDateFormatterLocale-aware date formatting. Used inside DateTimeHelperService.

Utility/ plain classes

orangehrmCorePlugin/Utility/ has security and infrastructure utilities. Mostly covered in other skills:

ClassSkill
Cryptographersecurity-primitives
KeyHandlersecurity-primitives
PasswordHashsecurity-primitives
Mailer, MailMessage, MailTransportmail
SanitizerHTML sanitization for user-generated content. Used in Buzz posts.
Base64UrlURL-safe base64 encoding (for tokens, query params).

"Is there a helper for…" cheat sheet

If you need…use this
Current date/timeDateTimeHelperTraitgetDateTimeHelper()->getNow()
Format a date for the userDateTimeHelperTraitgetDateTimeHelper()->formatDate($date)
Check if a string starts with XTextHelperTraitgetTextHelper()->strStartsWith($str, $prefix)
Truncate a stringTextHelperTraitgetTextHelper()->truncate($str, $max)
Strip HTML tagsTextHelperTraitgetTextHelper()->stripTags()
Format a number with currencyNumberHelperTraitgetNumberHelper()->...
Log an errorLoggerTraitgetLogger()->error('msg', $context)
Log info during a long operationLoggerTraitgetLogger()->info('progress: 50%')
Get the current userAuthUserTraitgetAuthUser()->getEmpNumber() / getUserId()
Check if user has a permissionUserRoleManagerTraitgetUserRoleManager()->getApiPermissions(...) (see authorization)
Get accessible entity IDs (e.g. employees this user can see)UserRoleManagerTraitgetUserRoleManager()->getAccessibleEntityIds(Employee::class)
Read a config valueConfigServiceTraitgetConfigService()->getX() (see config)
Dispatch an eventEventDispatcherTraitgetEventDispatcher()->dispatch(...) (see events)
Run the validator on arbitrary dataValidatorTraitvalidate($values, $rules) (see rest-validation)
Cache somethingCacheTraitgetCache('orangehrm')->get(key, callback)
Generate an ETag for a responseETagHelperTraitgenerateETag($data), setETag($response, $etag)
Encrypt a sensitive field on save(in an EntityListener) EncryptionHelperTraitgetCryptographer()->encrypt(...) (see security-primitives)
Create a database queryEntityManagerHelperTraitcreateQueryBuilder(Entity::class, 'e') (see daos)
Persist an entityEntityManagerHelperTraitpersist($entity) (see daos)
Start a transactionEntityManagerHelperTraitbeginTransaction() (see daos)

If your need isn't in the table: search src/plugins/orangehrmCorePlugin/Traits/ and Traits/Service/ for matching trait names. The naming is predictable — XHelperTrait, XServiceTrait. If nothing exists for the operation you need, the right answer is to add a helper service + trait, not to inline the logic in a feature.

When to add a new helper

A new helper is justified when:

  1. The same logic appears in two places already (3rd time = definitely extract). One-off logic stays inline.
  2. The logic is non-trivial — formatting, parsing, computation worth more than 5 lines.
  3. It needs DI — accessing config, the EM, the dispatcher, etc. Goes in a service; trait wraps it.
  4. It's pure — no I/O, no state. Goes in a static utility class or a plain helper.

Adding a new helper service + trait

Mirror an existing example like TextHelperService / TextHelperTrait:

// src/plugins/orangehrmCorePlugin/Service/SlugHelperService.php
namespace OrangeHRM\Core\Service;

class SlugHelperService
{
    public function slugify(string $input): string
    {
        return strtolower(preg_replace('/[^a-z0-9]+/i', '-', trim($input)));
    }
}
// src/plugins/orangehrmCorePlugin/Traits/Service/SlugHelperTrait.php
namespace OrangeHRM\Core\Traits\Service;

use OrangeHRM\Core\Service\SlugHelperService;

trait SlugHelperTrait
{
    protected ?SlugHelperService $slugHelper = null;

    public function getSlugHelper(): SlugHelperService
    {
        if (!$this->slugHelper instanceof SlugHelperService) {
            $this->slugHelper = new SlugHelperService();
        }
        return $this->slugHelper;
    }
}

For pure helpers like this, no container registration is needed — the trait instantiates on demand. For helpers that need DI (config, EM, dispatcher), follow the services skill's full registration pattern.


Recipes

Recipe 1 — Service that uses several common traits

namespace OrangeHRM\X\Service;

use OrangeHRM\Core\Traits\EventDispatcherTrait;
use OrangeHRM\Core\Traits\LoggerTrait;
use OrangeHRM\Core\Traits\Auth\AuthUserTrait;
use OrangeHRM\Core\Traits\Service\ConfigServiceTrait;
use OrangeHRM\Core\Traits\Service\DateTimeHelperTrait;
use OrangeHRM\Core\Traits\Service\TextHelperTrait;
use OrangeHRM\Core\Traits\UserRoleManagerTrait;

class WidgetService
{
    use EventDispatcherTrait;
    use LoggerTrait;
    use AuthUserTrait;
    use ConfigServiceTrait;
    use DateTimeHelperTrait;
    use TextHelperTrait;
    use UserRoleManagerTrait;

    public function buildSlug(string $name): string
    {
        $clean = $this->getTextHelper()->stripTags($name);
        return strtolower(preg_replace('/[^a-z0-9]+/i', '-', $clean));
    }

    public function logOperation(string $operation): void
    {
        $this->getLogger()->info(sprintf(
            '[%s] User %d: %s',
            $this->getDateTimeHelper()->getNow()->format('Y-m-d H:i:s'),
            $this->getAuthUser()->getUserId(),
            $operation,
        ));
    }

    public function getAccessibleWidgets(): array
    {
        if (!$this->getConfigService()->isWidgetFancyModeEnabled()) {
            return [];
        }
        return $this->getUserRoleManager()->getAccessibleEntityIds(Employee::class);
    }
}

The bigger the service, the more traits it composes — that's fine. The trait list is the service's dependency declaration. Reading from the top quickly tells you what it depends on.

Recipe 2 — Add a new helper

Goal: a MoneyHelperService for formatting currency strings consistently.

namespace OrangeHRM\Core\Service;

class MoneyHelperService
{
    public function format(float $amount, string $currencyCode = 'USD'): string
    {
        return $currencyCode . ' ' . number_format($amount, 2, '.', ',');
    }
}
namespace OrangeHRM\Core\Traits\Service;

use OrangeHRM\Core\Service\MoneyHelperService;

trait MoneyHelperTrait
{
    protected ?MoneyHelperService $moneyHelper = null;

    public function getMoneyHelper(): MoneyHelperService
    {
        return $this->moneyHelper ??= new MoneyHelperService();
    }
}

Now any class can use MoneyHelperTrait and call $this->getMoneyHelper()->format($amount, $currency). No further registration needed — pure helper, no DI dependencies.

Recipe 3 — Pick the right trait by responsibility

The mental model: think about what you're doing, then pick the matching trait.

  • "Format a date for the UI" → date formatting → DateTimeHelperTrait
  • "Log that this happened" → logging → LoggerTrait
  • "Send an event so other plugins can react" → events → EventDispatcherTrait
  • "Check if the user is allowed to do this" → permissions → UserRoleManagerTrait + see authorization
  • "Encrypt this before saving" → encryption (entity listener) → EncryptionHelperTrait + see security-primitives
  • "Read a feature flag" → config → ConfigServiceTrait + see config
  • "Run a DQL query" → ORM → EntityManagerHelperTrait + see daos

If you find yourself doing two of these at once, you use two traits. The trait list grows with the class's responsibilities. That's normal — services routinely use 4-8 traits.


Checklists

Before writing a new helper

  • Search Traits/ and Traits/Service/ in orangehrmCorePlugin for matching trait names
  • Search the "Is there a helper for…" cheat sheet above
  • Check other plugins' Traits/Service/ — sometimes a plugin owns a helper that's broadly useful
  • Only if nothing exists, add a new helper service + trait — mirror existing pattern from TextHelperService or similar

Compose helpers into a class

  • use <Helper>Trait; at the top of the class
  • Call $this->get<Helper>() to access the helper service
  • Need multiple? List them all — no upper limit, services routinely use 4-8 traits

Add a new helper service + trait

  • Service class in src/plugins/orangehrm{X}Plugin/Service/<Name>HelperService.php
  • Matching trait in Traits/Service/<Name>HelperTrait.php with ?<Name>HelperService $field and lazy get<Name>Helper() method
  • If the helper needs DI (config, EM, dispatcher) — register in plugin's PluginConfiguration::initialize() and have the trait fetch from the container instead of new-ing
  • If pure (no DI), the lazy new is fine — no container registration needed
  • Add a row to your project's "Is there a helper for…" table so future devs find it

Things that bite

  • Don't bypass the trait and instantiate helpers directly with new ConfigService(). Some traits cache the service per-class; bypassing means an extra instantiation per call. More importantly, it breaks the convention — other devs look for use ConfigServiceTrait and don't find it.
  • Don't put business logic in a helper — helpers are utilities (formatters, normalizers, lookups). Business logic that involves the domain belongs in a Service (see services skill).
  • AuthUserTrait only works inside a request lifecycle. In console commands or migrations, the session isn't bound — getAuthUser()->getUserId() returns null. Either pass a user ID as a command argument or work without one.
  • getEntityManager() is fine in any context that's run after Framework is bootstrapped (which is everywhere except very early install bootstrap). Use it freely.
  • use-ing two traits with the same method name is a fatal error in PHP. Rare here because OHRM trait method names are distinctive, but if you ever see Trait method getX has not been applied, you've got a name collision — rename one of the methods.
  • Helpers are stateless. Don't store request-specific data on a helper service — different callers might share the same instance. If you need request state, use the service itself, not a helper.
  • ServiceContainerTrait is internal plumbing — don't use it directly from feature code. Use the higher-level traits that wrap container access.

Signals

GitHub stars
1k
Forks
749
Last commit
Jun 2026
Advanced
Catalog kind
skill
Gateway key
helpers
Source
github.com/orangehrm/orangehrm