Yours&Mine PHP Framework – Developer Guide v0.3

This document explains how the Yours&Mine PHP Framework is structured and how a request like GET / becomes the dashboard you see in the browser – under both the Material and Cork admin templates.

This guide is written as if you are the framework author. You can customise, refactor and extend anything here – the goal is to keep the mental model clear and consistent.

1. Project structure

Top-level folders (inside your project root):

app/
  Controllers/
  Views/
    layouts/
      material.php
      cork.php
    home/
      index.php
      about.php
bootstrap/
  helpers.php
  app.php
config/
  config.php
  brand.php
  lang.php
core/
  Bootstrap.php
  Router.php
  Lang.php
  BaseController.php
public/
  index.php
  assets/
    material/ ...      (Material template files)
    cork/ ...          (Cork template files)
    css/
      ym-dashboard.css
  readme_guide.html
vendor/
  autoload.php
composer.json

Key concepts:

2. Request lifecycle overview

When you open the dashboard (e.g. http://localhost/ur-mine/):

  1. Front controller: public/index.php runs first.
  2. It bootstraps Composer autoload + your own helpers.
  3. It instantiates YoursMine\Core\Bootstrap and calls $app->run().
  4. Bootstrap:
    • Loads configuration.
    • Creates the Router.
    • Registers application routes (e.g. /, /about).
    • Calls $router->dispatch().
  5. Router matches the current HTTP method + path and calls the handler, e.g. [HomeController::class, 'index'].
  6. HomeController::index() prepares some data and calls $this->view('home/index', $data).
  7. BaseController::view():
    • Resolves the view file: app/Views/home/index.php.
    • Renders it into a $content variable.
    • Chooses the current layout (Material or Cork) based on config.
    • Includes the layout file; layout prints the full HTML shell and injects $content inside.

3. Configuration basics

3.1 Main config

config/config.php returns a PHP array:

<?php

return [
    'app_name' => 'Yours&Mine PHP Framework',

    'timezone' => 'Asia/Kathmandu',

    // Current UI layout: 'material' or 'cork'
    'ui' => [
        'layout' => 'material',
    ],

    // Default language for the UI (also used for translations)
    'lang' => [
        'default' => 'en',
        'supported' => ['en', 'np'],
    ],
];

3.2 Brand configuration

config/brand.php centralises anything that feels like “branding”:

<?php

return [
    'name'         => 'Yours&Mine PHP Framework',
    'logo'         => 'assets/material/images/logo.svg',  // or null to use text
    'favicon'      => 'assets/material/images/favicon.ico',

    'footer_text'  => '© ' . date('Y') . ' Yours&Mine PHP Framework',
    'footer_link'  => 'https://example.com',              // or null
];

Layouts read these values using a helper such as config('brand.name').

3.3 UI layout selection

In config/config.php:

'ui' => [
    'layout' => 'material',   // options: 'material', 'cork'
],

In BaseController::view() you resolve that to a layout file:

$ui     = config('ui.layout', 'material');
$layout = $ui === 'cork'
    ? 'layouts/cork'
    : 'layouts/material';

$this->renderLayout($layout, $viewPath, $data);

Switching from Material to Cork is now just editing one config value.

4. Routing & the Bootstrap class

4.1 Router

core/Router.php is a small HTTP router that supports:

$handler is a callable, typically: [HomeController::class, 'index'].

4.2 Bootstrap

core/Bootstrap.php wires everything together:

namespace YoursMine\Core;

use App\Controllers\HomeController;

class Bootstrap
{
    protected array $config;
    protected Router $router;

    public function __construct()
    {
        $this->config = require YM_CONFIG_PATH . '/config.php';

        date_default_timezone_set($this->config['timezone'] ?? 'UTC');

        $this->router = new Router();
        $this->registerRoutes();
    }

    protected function registerRoutes(): void
    {
        $this->router->get('/',      [HomeController::class, 'index']);
        $this->router->get('/about', [HomeController::class, 'about']);
    }

    public function run(): void
    {
        $this->router->dispatch();
    }
}

5. Controllers & views

5.1 HomeController

The default controller at app/Controllers/HomeController.php might look like:

namespace App\Controllers;

use YoursMine\Core\BaseController;

class HomeController extends BaseController
{
    public function index(): void
    {
        $data = [
            'title' => __('nav.dashboard'),
        ];

        $this->view('home/index', $data);
    }

    public function about(): void
    {
        $data = [
            'title' => __('nav.about'),
        ];

        $this->view('home/about', $data);
    }
}

5.2 BaseController and view()

core/BaseController.php is responsible for rendering:

protected function view(string $view, array $data = []): void
{
    $viewFile = YM_APP_PATH . '/Views/' . $view . '.php';

    if (!file_exists($viewFile)) {
        throw new \RuntimeException("View not found: {$viewFile}");
    }

    extract($data, EXTR_SKIP);

    ob_start();
    include $viewFile;
    $content = ob_get_clean();

    $ui     = config('ui.layout', 'material');
    $layout = $ui === 'cork'
        ? YM_APP_PATH . '/Views/layouts/cork.php'
        : YM_APP_PATH . '/Views/layouts/material.php';

    include $layout;
}

5.3 Dashboard view (home/index.php)

This view is template-agnostic. Both Material and Cork layouts render it by injecting $content into their main content area. The view uses the ym-* CSS classes that we control in public/assets/css/ym-dashboard.css.

Example structure:

<div class="row g-3 mb-4">
  <div class="col-md-3">
    <div class="ym-card">
      <div class="ym-card-title">Framework Version</div>
      <div class="ym-card-value">v0.3</div>
      <div class="ym-card-meta">Routing + MVC + multi-theme layout.</div>
    </div>
  </div>

  <div class="col-md-3">... controllers card ...</div>
  <div class="col-md-3">... views card ...</div>
  <div class="col-md-3">... projects / slots card ...</div>
</div>

<div class="ym-card">
  <h5>How this page is rendered</h5>
  <p class="ym-dashboard-intro">Route → Controller → View → Layout.</p>

  <div class="ym-card-code">
    <div class="ym-step">
      <span class="ym-chip">1</span>
      GET <code>/</code> is routed in <code>core/Bootstrap.php</code>
      to <code>HomeController::index()</code>.
    </div>
    <div class="ym-step">
      <span class="ym-chip">2</span>
      Controller calls <code>$this->view('home/index', [...])</code>.
    </div>
    <div class="ym-step">
      <span class="ym-chip">3</span>
      Base controller renders <code>app/Views/home/index.php</code> into
      <code>$content</code>.
    </div>
    <div class="ym-step">
      <span class="ym-chip">4</span>
      Layout (Material or Cork) injects <code>$content</code> and outputs
      the final HTML.
    </div>
  </div>
</div>

6. Layouts: Material vs Cork

6.1 Material layout

app/Views/layouts/material.php wraps the Material HTML template. It:

6.2 Cork layout

app/Views/layouts/cork.php integrates Cork’s index.html. It:

6.3 Dashboard polishing & dark mode (Cork)

The shared dashboard uses ym-* classes. The look-and-feel is defined in:

public/assets/css/ym-dashboard.css

For Cork dark mode:

You can change the exact colors in ym-dashboard.css without touching either Material or Cork template files.

7. Language system (English & Nepali)

7.1 Language files

Language files live in app/Lang/{locale}.php. For example:

app/
  Lang/
    en.php
    np.php

7.2 English file (en.php)

<?php

return [
    'app' => [
        'name' => 'Yours&Mine PHP Framework',
    ],

    'nav' => [
        'dashboard' => 'Dashboard',
        'about'     => 'About',
    ],

    'dashboard' => [
        'framework_version'   => 'Framework Version',
        'controllers'         => 'Controllers',
        'views'               => 'Views',
        'projects'            => 'Projects (idea slots)',
        'how_rendered_title'  => 'How this page is rendered',
        'how_rendered_intro'  => 'Route → Controller → View → Layout.',
    ],

    'about' => [
        'title' => 'About this framework',
        'body'  => 'A lightweight custom PHP framework powered by your own MVC & HTML templates.',
    ],
];

7.3 Nepali file (np.php)

<?php

return [
    'app' => [
        'name' => 'तपाईको र मेरो PHP फ्रेमवर्क',
    ],

    'nav' => [
        'dashboard' => 'ड्यासबोर्ड',
        'about'     => 'बारेमा',
    ],

    'dashboard' => [
        'framework_version'   => 'फ्रेमवर्क संस्करण',
        'controllers'         => 'कन्ट्रोलरहरू',
        'views'               => 'भ्यूहरू',
        'projects'            => 'प्रोजेक्ट (आइडिया स्लट)',
        'how_rendered_title'  => 'यो पेज कसरी रेंडर हुन्छ',
        'how_rendered_intro'  => 'रुट → कन्ट्रोलर → भ्यू → लेआउट।',
    ],

    'about' => [
        'title' => 'यो फ्रेमवर्कबारे',
        'body'  => 'तपाईंले आफैं बनाएको हल्का PHP फ्रेमवर्क, MVC र HTML टेम्पलेटहरूसँग।',
    ],
];

7.4 The Lang helper

core/Lang.php manages loading these arrays and provides a function __():

__('nav.dashboard');    // "Dashboard" or "ड्यासबोर्ड"
__('about.title');      // about page title in current language

Typical implementation:

function __(string $key, ?string $locale = null): string
{
    return YoursMine\Core\Lang::get($key, $locale);
}

And Lang::get() splits the dot notation and walks the loaded array.

7.5 Switching language from the UI

Both layouts have a language dropdown with attributes such as data-ym-lang="en" or data-ym-lang="np".

Shared JS handler (included in layouts):

document.addEventListener('DOMContentLoaded', function () {
  document.querySelectorAll('[data-ym-lang]').forEach(function (el) {
    el.addEventListener('click', function (e) {
      e.preventDefault();
      var lang = this.getAttribute('data-ym-lang');
      if (!lang) return;

      var url = new URL(window.location.href);
      url.searchParams.set('lang', lang);
      window.location.href = url.toString();
    });
  });
});

In PHP (e.g. in a small bootstrap or middleware layer), you read the query parameter and store the selected language in the session or cookie:

$locale = $_GET['lang'] ?? ($_SESSION['ym_locale'] ?? config('lang.default', 'en'));
$_SESSION['ym_locale'] = $locale;

YoursMine\Core\Lang::setLocale($locale);

The current locale can be queried via Lang::locale() – used by layouts to decide which flag to show as active.

7.6 Adding new text to language files

Whenever you need to translate new UI text:

  1. Pick a logical key path, e.g. settings.general.title.
  2. Add that key in all language files (en.php, np.php, etc.):
// en.php
'settings' => [
    'general' => [
        'title' => 'General Settings',
    ],
],

// np.php
'settings' => [
    'general' => [
        'title' => 'साधारण सेटिङ',
    ],
],
  1. Call __('settings.general.title') in your controller or view.
If a key is missing in the current language file, the helper can be configured to fall back to the key itself (e.g. "settings.general.title") or to the default language.

8. Extending the framework

8.1 Adding a new page

  1. Create a controller method, e.g. ReportsController::index().
  2. Register a route in Bootstrap::registerRoutes():
$this->router->get('/reports', [ReportsController::class, 'index']);
  1. Create a view file: app/Views/reports/index.php.
  2. Add a menu item in the layout (Material and/or Cork) that links to /reports.

8.2 Adding another admin template (future)

The pattern you used for Material and Cork can be repeated:

  1. Create a new layout file: app/Views/layouts/{name}.php.
  2. Copy HTML from the template’s main dashboard, then:
    • Replace hardcoded brand name & logo with config('brand.*').
    • Replace content area with .
    • Integrate language dropdown and your shared JS language handler.
  3. Set 'ui.layout' => '{name}' in config/config.php.

9. Summary