Skip to main content
Skip to main content

Developers Doc

Bootstrap Process

7 mins read 6 Views 3+

The Havenlytics bootstrap process is deterministic: the same ordered sequence runs on every request, from the entry file’s procedural setup through the HvnlyNab singleton and into Bootstrap::init(), which stands up every service in a fixed order. This guide walks the sequence step by step for professional WordPress developers who need to know exactly what exists at each moment and where to hook in. It complements Plugin Architecture & Lifecycle with the low-level detail.

Stage 1: havenlytics.php load order

When WordPress includes havenlytics.php, procedural code runs immediately — before any class bootstrap. This stage prepares the environment so the singleton can be built safely.

  1. Define constants (HVNLYNAB_VERSION, paths, URLs, slug).
  2. require_once the migration bootstrap, includes/Core/Migration/migration-bootstrap.php, loading MigrationTrait / MigrationInterface before the Composer autoloader.
  3. require_once the shared function files: utility-functions.phpemail-functions.phpfield-options.phpdeletion-safety.phptemplate-bootstrap.php.
  4. Define HVNLYNAB_TEMPLATE_DEBUG as false if not already set.
  5. require_once vendor/autoload.php (resolves __DIR__ first, falls back to HVNLYNAB_PATH).
  6. Re-run hvnly_load_migration_dependencies() after autoload.
  7. ErrorHandler::init(__FILE__) if the class exists; otherwise show a “Core class missing” admin notice and return.
  8. ConflictChecker::get_instance() if the class exists.
  9. add_action('plugins_loaded', ContactAgentAjaxRegistrar::register, 4).
  10. Define the final class HvnlyNab and the HVNLY_NAB() accessor.
  11. Call HVNLY_NAB() at the end of the file, instantiating the singleton.

Note: The contact-agent AJAX registrar hooks in at plugins_loaded priority 4 — one priority earlier than the main init_plugin() at priority 5 — so AJAX endpoints are ready before the rest of the plugin initializes.

Stage 2: HvnlyNab constructor and init_plugin

Calling HVNLY_NAB() runs HvnlyNab::__construct(), which performs guards and defers the real work.

  1. basic_environment_check() — PHP >= 7.4 (MIN_PHP), WP >= 6.0 (MIN_WP). On failure it queues admin notices, self-deactivates on admin_init, and the constructor aborts.
  2. EnvironmentChecker::check() — a duplicate PHP/WP gate; aborts on failure.
  3. register_lifecycle_hooks() — activation and deactivation hooks.
  4. add_action('plugins_loaded', [$this, 'init_plugin'], 5).

When WordPress fires plugins_loaded at priority 5, init_plugin() runs the following, with the whole body wrapped in try/catch that calls ErrorHandler::record_fatal():

  1. Bail if ErrorHandler::has_fatal_recorded().
  2. init_appsero() — telemetry client, try/catch, silent-failing.
  3. $this->container['bootstrap'] = new Bootstrap(); then ->init().
  4. add_action('plugins_loaded', <Elementor integration>, 20) — loads includes/Integrations/Elementor/Bootstrap.php and calls ::get_instance() only if \Elementor\Plugin or ELEMENTOR_VERSION is present.
  5. do_action('hvnly_loaded').
<?php
// hvnly_loaded is the earliest safe point for extension code:
// Bootstrap::init() has already populated the container.
add_action( 'hvnly_loaded', function () {
    $engine = HVNLY_NAB()->engine();
    // Register your own hooks, endpoints, or shortcodes here.
} );

Stage 3: Bootstrap::init() ordered steps

Bootstrap::init() is the service-startup pipeline. The order matters — later steps depend on services registered by earlier ones.

  1. load_core_dependencies() — reserved no-op. If is_admin(), initializes MigrationAdminNotices.
  2. init_engine() — requires includes/HvnlyEngine.php and calls \HvnlyEngine::get_instance() if the class exists; otherwise builds an anonymous fallback engine.
  3. init_shared_services() — AvatarService::register_hooks(); registers Helper (Helpers::get_instance()), DB (new Database()), and rest_api (new Controller()). All calls are class_exists-guarded and wrapped in try/catch.
  4. init_settings_system() — registers settings_manager (SettingsManager::get_instance()) and style_generator (DynamicStyleGenerator::get_instance()).
  5. check_version_update() — runs migrations if needed (see below).
  6. init_services() — registers cache_manager (new CacheManager()) and hook_manager (new HookManager()), then splits into admin/frontend services.
  7. register_hooks() — delegates to HookManager::register_hooks().
  8. schedule_events() — delegates to Scheduler::schedule_events().
  9. init_enterprise_features() — EnterpriseCache::get_instance().

The admin/frontend service split

Step 6 loads different services depending on the request context, which keeps each request lean.

  • Admin only (is_admin()) — init_admin_services() registers admin (new Admin()), cache_admin (new CacheAdmin()), and workspace (WorkspaceBootstrap::get_instance()).
  • Frontend or AJAX (!is_admin() || DOING_AJAX) — init_frontend_services() registers frontend (new Frontend()).

Tip: Because the frontend service also loads during AJAX (DOING_AJAX), front-end functionality remains available to admin-ajax.php requests. Design AJAX handlers accordingly.

Performance: Admin-only services (the settings admin, cache admin, and Workspace bootstrap) never load on front-end page views. Keep your own admin-heavy code behind an is_admin() check to match this pattern.

Version check and migrations

Step 5, check_version_update(), is where page-load migrations run. If a migration is needed or the stored DB version is behind HVNLYNAB_VERSION, it runs the migrator; on success it updates the hvnly_db_version option, calls Installer::update(), and schedules a rewrite flush. This runs on every eligible init, which is how an upgraded plugin folder self-heals its data without re-activation.

<?php
// Force migrations during local development or a controlled deploy.
// Runtime-checked constant; define before the plugin boots.
define( 'HVNLY_FORCE_MIGRATIONS', true );

Stage 4: Hooks registered by HookManager

Step 7 delegates to HookManager::register_hooks(), which registers the runtime WordPress hooks that keep the plugin working during a request. This is the authoritative list.

HookCallbackNotes
admin_menu (99)maybe_redirect_after_activationOnboarding redirect after activation.
activated_plugin (10, 2 args)on_plugin_activatedReacts to plugin activation.
plugin_action_links_{basename}plugin_action_linksAdds Docs / Get Started / Clear Cache links.
(rewrite)RewriteRulesManager::register()Wires permalink handling and flush hooks.
wp_ajax_hvnly_clear_cachehandle_cache_ajaxRequires cache enabled + manage_options.
wp_ajax_hvnly_get_cache_statshandle_cache_ajaxNonce hvnly_cache_nonce.
wp_ajax_hvnly_update_cache_settingshandle_cache_settings_ajaxRequires manage_options.
(conditional)ContactAgentAjaxRegistrar::register()If the class exists.
save_post_hvnly_propertyCacheInvalidator::invalidateClears search/query caches on property save.
delete_postCacheInvalidator::invalidateClears caches on delete.
hvnly_daily_cache_cleanupCacheManager::clear_expired_cacheDaily cron; scheduled if not already.
initlocalization_setupwp_set_script_translations('havenlytics-app', ...).
doing_it_wrong_trigger_error (10)textdomain_noticeSuppresses a known textdomain notice.
admin_enqueue_scriptsadmin_scriptsEmpty placeholder.

Security: The cache AJAX handlers require hvnly_is_cache_enabled(), the manage_options capability, and the hvnly_cache_nonce nonce. Mirror this capability-plus-nonce pattern in any AJAX endpoint you add. See Security and AJAX API.

Event scheduling

Step 8 calls Scheduler::schedule_events(), which schedules the daily cache cleanup and daily analytics cleanup events. Note a documented internal detail: Scheduler::init() is never called, so the custom weekly and monthly cron intervals are not registered — only the two daily events reliably schedule. Do not rely on weekly or monthly Havenlytics cron intervals in your own code.

Warning: Because the weekly/monthly custom intervals are unregistered, scheduling against them will silently fail to recur. If you need periodic work, register your own interval via cron_schedules.

Best practices

  • Hook extension logic on hvnly_loaded, after Bootstrap::init() has populated the container.
  • Respect the admin/frontend split: guard admin-only code with is_admin() and remember AJAX loads the frontend service.
  • Reuse the cache AJAX security pattern (capability + nonce) for your own endpoints.
  • Let migrations run naturally on admin page-load; only force them with HVNLY_FORCE_MIGRATIONS in controlled scenarios.

Common mistakes

  • Assuming all services exist everywhere. Admin services are absent on front-end requests, and vice versa.
  • Depending on weekly/monthly cron. Only the daily events register.
  • Hooking too early. Code on plugins_loaded before priority 5 runs before the container is built.
  • Skipping nonce/capability checks on AJAX handlers, unlike the built-in cache handlers.

Need more help?

Can't find what you're looking for? Our team and community are here to help you ship faster.