Havenlytics is a real-estate plugin for WordPress built around a single, predictable entry point and a service-oriented core. This guide is written for professional WordPress developers who need to understand how the plugin loads, how its services are wired together, and where to hook in safely. If you are building an extension, an integration, or a child-theme override, understanding the architecture described here is the foundation for everything else in the Havenlytics Developer Documentation.
At the highest level, Havenlytics follows a strict layering: a procedural entry file (havenlytics.php) defines constants and loads shared code, a single final class HvnlyNab acts as the plugin singleton, and an orchestrator class HvnlyNab\Core\Bootstrap stands up every service. Nothing meaningful runs until WordPress fires plugins_loaded, and the plugin signals completion with a single action, hvnly_loaded.
The three architectural layers
Havenlytics deliberately separates concerns into three cooperating layers. Each layer has one job, and higher layers never reach around lower ones.
- Entry file layer (
havenlytics.php) — defines constants, loads migration dependencies and shared function files, wires the Composer autoloader, installs the global error handler, and defines the plugin singleton. - Singleton layer (
HvnlyNab) — performs environment checks, registers activation/deactivation hooks, and defers real work toinit_plugin()onplugins_loadedpriority 5. - Service layer (
HvnlyNab\Core\Bootstrap) — instantiates the property engine, shared services, settings system, cache and hook managers, and the admin/frontend split.
Note: The public accessor
HVNLY_NAB()returns theHvnlyNabsingleton. Prefer it over touching internal classes directly. See Service Container for the full accessor surface.
Namespace and autoloading
All PHP classes live under the HvnlyNab\ namespace, mapped via PSR-4 to the includes/ directory. The Composer autoloader is required early in the entry file, resolving __DIR__ . '/vendor/autoload.php' first and falling back to HVNLYNAB_PATH. Migration base types (MigrationTrait / MigrationInterface) are loaded via a dedicated bootstrap before the Composer autoloader so migration handlers can be discovered reliably.
<?php
// Access the plugin singleton anywhere after plugins_loaded (priority 5+).
$plugin = HVNLY_NAB();
echo $plugin->version(); // '3.3.1'
echo $plugin->plugin_path(); // absolute filesystem path (HVNLYNAB_PATH)
$helper = $plugin->get_helper();
$db = $plugin->database();
$engine = $plugin->engine();
Request lifecycle
Every request that loads WordPress with Havenlytics active follows the same deterministic path. The plugin does almost nothing at file-include time beyond defining constants and loading shared code — the real boot happens on plugins_loaded.
- Plugin load (file include).
havenlytics.phpdefines constants, loads the migration bootstrap and shared function files, requires the Composer autoloader, initializesErrorHandler, runsConflictChecker, and defines theHvnlyNabclass. The final line callsHVNLY_NAB(), instantiating the singleton. - Constructor.
HvnlyNab::__construct()runsbasic_environment_check()andEnvironmentChecker::check(), registers lifecycle hooks, then schedulesinit_plugin()onplugins_loadedpriority 5. - plugins_loaded (priority 5) —
init_plugin(). Bails if a fatal was recorded, initializes telemetry (silent-failing), creates and initializesBootstrap, wires the Elementor integration onplugins_loadedpriority 20, then firesdo_action('hvnly_loaded'). The whole body is wrapped in try/catch that records fatals. - Bootstrap::init(). Runs the ordered service startup: engine, shared services, settings system, version check, service split, hook registration, event scheduling, and enterprise features. See Bootstrap Process for the full step-by-step.
- HookManager::register_hooks(). Registers admin menu redirects, plugin action links, rewrite rules, cache AJAX handlers, cache invalidation, localization, and scheduled cleanup.
- do_action(‘hvnly_loaded’). The public signal that Havenlytics is fully initialized. This is the correct place to attach extensions that depend on the plugin being ready.
<?php
/**
* Run extension code only after Havenlytics has fully booted.
* hvnly_loaded fires at the end of init_plugin() on plugins_loaded priority 5.
*/
add_action( 'hvnly_loaded', function () {
if ( ! function_exists( 'HVNLY_NAB' ) ) {
return;
}
$engine = HVNLY_NAB()->engine();
// Safe to call engine, helper, and database accessors here.
} );
Tip: Do not hook your integration on
plugins_loadedat a priority earlier than 5 and expect Havenlytics services to exist. Use thehvnly_loadedaction instead — it is the contract that guarantees the container is populated.
Environment gates
Havenlytics enforces minimum platform requirements twice, defensively. The constructor first calls basic_environment_check() (PHP >= 7.4, WordPress >= 6.0). If that fails, the plugin queues admin notices and self-deactivates on admin_init, and the constructor aborts. A second gate, EnvironmentChecker::check(), repeats the PHP/WP check and aborts on failure. Both thresholds are hardcoded.
| Requirement | Minimum | Enforced by |
|---|---|---|
| PHP | 7.4 | basic_environment_check() + EnvironmentChecker::check() |
| WordPress | 6.0 | basic_environment_check() + EnvironmentChecker::check() |
Warning: Because the environment gate can abort the constructor, extensions must never assume
HVNLY_NAB()returns a fully initialized instance on unsupported platforms. Always guard withfunction_exists('HVNLY_NAB')and hook onhvnly_loaded.
Activation, deactivation, and uninstall
Lifecycle hooks are registered in the constructor via register_lifecycle_hooks().
Activation
The register_activation_hook closure is wrapped in try/catch and records fatals on failure. It calls Installer::activate(), re-arms the onboarding redirect (deletes hvnly_activation_redirect_done, sets hvnly_activation_redirect = 1 plus a transient), and runs RewriteRulesManager::activation_setup(). Installer::activate() is guarded for existing sites: if a stored DB version is present it runs migrations only, otherwise it runs a full install().
Deactivation
register_deactivation_hook calls HvnlyNab::deactivate(), which clears scheduled events via Scheduler::clear_scheduled_events(), deletes the redirect options and transient, deletes hvnly_flush_rewrite_once, and flushes rewrite rules.
Uninstall
register_uninstall_hook is commented out. Cleanup runs from uninstall.php instead, guarded by WP_UNINSTALL_PLUGIN. It deletes a curated list of options (cache options, demo-import flags, builder sections, hvnly_db_version, migration history, settings, install flags, page-id options), removes options and transients matching hvnly_% / _hvnly_% patterns and migration backups, deletes post/term/user meta with keys like _hvnly_% or _hvnlynab_%, unschedules the four cron events, and flushes the object cache.
Security:
uninstall.phponly runs data destruction whenWP_UNINSTALL_PLUGINis defined by WordPress. Never invoke it directly, and never reproduce its bulkDELETE ... LIKE 'hvnly_%'queries in your own code — they are intentionally broad and destructive.
Migrations run on activation and page-load
Data migrations are not activation-only. They run in three situations: on activation (Installer::install / activate call Migrator::run(true)); on page-load (Bootstrap::check_version_update on every init, when a migration is needed or the stored DB version is behind HVNLYNAB_VERSION); and on manual retry via the admin-post action hvnly_retry_migrations. This means an upgraded plugin folder self-heals its data the first time an admin loads a page — a full re-activation is not required.
Note: Page-load migrations only execute in eligible contexts (WP-CLI, or admin requests that are not AJAX, or when
HVNLY_FORCE_MIGRATIONSis set). This keeps front-end requests fast while still guaranteeing data consistency for admins.
Boot sequence at a glance
- Define constants (
HVNLYNAB_VERSION, paths, URLs). - Load migration bootstrap (
MigrationTrait/MigrationInterface). - Load shared function files (utility, email, field options, deletion safety, template bootstrap).
- Require Composer autoloader.
- Re-run migration dependency loading after autoload.
- Initialize
ErrorHandler(admin notice + return if the class is missing). - Instantiate
ConflictChecker. - Register
ContactAgentAjaxRegistraronplugins_loadedpriority 4. - Define
HvnlyNaband callHVNLY_NAB()to build the singleton. - Constructor: environment checks, lifecycle hooks, schedule
init_plugin()at priority 5. init_plugin(): telemetry,Bootstrap::init(), Elementor wiring at priority 20,do_action('hvnly_loaded').
Core constants reference
| Constant | Value / meaning |
|---|---|
HVNLYNAB_VERSION | 3.3.1 — current plugin version. |
HVNLYNAB_FILE | Absolute path to havenlytics.php (__FILE__). |
HVNLYNAB_BASENAME | Plugin basename (plugin_basename). |
HVNLYNAB_SLUG | havenlytics — plugin slug and text domain. |
HVNLYNAB_PATH | Absolute filesystem path to the plugin directory. |
HVNLYNAB_URL | URL to the plugin directory. |
HVNLYNAB_ASSETS_PATH / HVNLYNAB_ASSETS_URL | Path and URL to bundled assets. |
HVNLYNAB_BUILD_PATH / HVNLYNAB_BUILD_URL | Path and URL to compiled build output. |
HVNLYNAB_INCLUDES | Path to the includes/ directory (PSR-4 root). |
HVNLYNAB_LANG_DIR | Path to translation files. |
HVNLYNAB_TEMPLATE_DEBUG | false by default; toggles template resolution debugging. |
HVNLY_FORCE_MIGRATIONS | Runtime-checked; forces migrations to run when defined true. |
<?php
// Build a URL to a bundled asset using the plugin's own accessor.
$css_url = HVNLY_NAB()->assets_url( 'css/frontend.css' );
// Or use the constant directly for build output.
$script = HVNLYNAB_BUILD_URL . 'workspace/workspace.js';
Best practices
- Always access the plugin through
HVNLY_NAB()and its accessors, never by newing up core classes. - Attach extension logic on
hvnly_loaded, not on earlyplugins_loadedpriorities. - Use the defined path/URL constants instead of hardcoding directory locations.
- Guard every integration with
function_exists('HVNLY_NAB')so your code degrades cleanly if Havenlytics is deactivated or blocked by an environment gate.
Performance recommendations
- Because the file-include phase only defines constants and loads shared functions, keep your own plugin’s include phase equally light — defer heavy work to
hvnly_loaded. - Page-load migrations are gated to admin, non-AJAX requests, so they do not tax front-end traffic. Avoid forcing
HVNLY_FORCE_MIGRATIONSin production request paths.
Common mistakes
- Assuming activation runs migrations only. Migrations also run on admin page-load; test upgrades by loading an admin screen, not just re-activating.
- Expecting an uninstall hook.
register_uninstall_hookis commented out; cleanup lives inuninstall.php. - Reaching into private services. Services without a dedicated accessor live on the private container and are effectively internal.
- Ignoring the environment gate. On PHP < 7.4 or WP < 6.0 the constructor aborts and self-deactivates.