PHPackages                             locky42/leopard-core - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. locky42/leopard-core

ActiveLibrary[Framework](/categories/framework)

locky42/leopard-core
====================

Lightweight PHP framework core for routing, controllers, and application base.

1.1.6(1mo ago)016MITPHPPHP ^8.3

Since Aug 22Pushed 1mo agoCompare

[ Source](https://github.com/locky42/leopard-core)[ Packagist](https://packagist.org/packages/locky42/leopard-core)[ RSS](/packages/locky42-leopard-core/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (12)Versions (10)Used By (0)

Leopard Core
============

[](#leopard-core)

`leopard-core` is the core library for the Leopard Framework, providing essential features such as routing, attributes, dependency injection container, and other foundational components.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Core Components](#core-components)
    - [Dependency Injection Container](#dependency-injection-container)
    - [Routing](#routing)
    - [Attributes](#attributes)
    - [ContractFactory](#contractfactory)
    - [View](#view)
    - [SEO](#seo)
- [Usage Examples](#usage-examples)
- [Testing](#testing)

---

Installation
------------

[](#installation)

Install `leopard-core` using Composer:

```
composer require locky42/leopard-core
```

---

Core Components
---------------

[](#core-components)

### Dependency Injection Container

[](#dependency-injection-container)

The `Container` is a simple dependency injection container that allows you to register services and retrieve their instances.

#### Example:

[](#example)

```
use Leopard\Core\Container;

$container = new Container();

// Register a service
$container->set('logger', function () {
    return new Logger();
});

// Retrieve the service
$logger = $container->get('logger');
```

---

### Routing

[](#routing)

The `Router` allows you to define routes using attributes, YAML configuration, and automatic route generation based on controller structure.

#### Routing Methods

[](#routing-methods)

1. **Attribute-based routing** - Define routes using PHP attributes
2. **YAML configuration** - Define routes and controller paths in YAML
3. **Auto-routing** - Automatic route generation for methods ending with `Action` suffix

#### Action Method Convention

[](#action-method-convention)

For auto-routing (YAML controllers), only methods ending with `Action` suffix are processed as routes:

- **HTTP method prefix**: `get`, `post`, `put`, `delete`, `patch`, `options`, `head`
- **Default method**: If no prefix is specified, `GET` is used by default
- **Action name**: Formed by removing the HTTP method prefix (if any) and the `Action` suffix

**Examples:**

```
class UserController
{
    // GET /user/about
    public function aboutAction(): string
    {
        return "About page";
    }

    // GET /user/profile
    public function getProfileAction(): string
    {
        return "User profile (GET)";
    }

    // POST /user/profile
    public function postProfileAction(): string
    {
        return "Update profile (POST)";
    }

    // DELETE /user/account
    public function deleteAccountAction(): string
    {
        return "Delete account";
    }

    // GET /user (index is special case)
    public function indexAction(): string
    {
        return "User index";
    }

    // This method will NOT be routed (no Action suffix)
    public function helperMethod(): string
    {
        return "Not a route";
    }
}
```

#### Attribute-based Routing Example:

[](#attribute-based-routing-example)

```
use Leopard\Core\Router;
use Leopard\Core\Attributes\Route;

class TestController
{
    #[Route('/test', method: 'GET')]
    public function test(): string
    {
        return "Hello, world!";
    }

    #[Route('/user/{id}', method: 'GET')]
    public function getUser(string $id): string
    {
        return "User ID: $id";
    }
}

$router = new Router($container);
$router->registerController(TestController::class);
$response = $router->dispatch('GET', '/test');
```

#### YAML Configuration Example:

[](#yaml-configuration-example)

```
routes:
  - controller: User/ProfileController
    action: show
    method: GET
    path: /profile/{id}

controllers:
  - controller: Site/PageController
    path: /pages

  - namespace: Api
    path: /api
```

#### YAML Controllers Behavior

[](#yaml-controllers-behavior)

- `controllers[].controller` registers one конкретний контролер (relative to `App\\Controllers\\...`, or absolute FQCN if it starts with `\\`).
- `controllers[].namespace` scans all `*Controller.php` in `src/Controllers/{Namespace}` and registers them with the same base path.
- Only methods ending with `Action` are auto-routed from YAML controller definitions.
- HTTP method prefix is detected from method name: `get|post|put|delete|patch|options|head`.
- Special path handling for `path`:
    - `path: /` → `/{controller}` and `/{controller}/{action}`
    - `path: ""` → `/` and `/{action}`
    - `path: /base` → `/base/{controller}` and `/base/{controller}/{action}`

#### Dynamic Parameters in Paths

[](#dynamic-parameters-in-paths)

The router supports these placeholders in route paths:

- `{id}` - one URI segment (no slash)
- `{id:\\d+}` - custom regex constraint
- `{path}` - greedy capture including `/`

If a parameter type in controller method is `int|float|bool` and conversion fails, router returns `404`.

`HEAD` requests are allowed to match `GET` routes.

#### Auto-routing with loadControllersFrom:

[](#auto-routing-with-loadcontrollersfrom)

```
$router = new Router($container);

// Load all controllers from directory
$router->loadControllersFrom(__DIR__ . '/src/Controllers');

// Methods with Action suffix will be auto-registered
// GET /test/about -> TestController::aboutAction()
// POST /test/submit -> TestController::postSubmitAction()
```

---

### Attributes

[](#attributes)

`leopard-core` supports PHP attributes for defining routes and other metadata.

#### Example:

[](#example-1)

```
use Leopard\Core\Attributes\Route;

#[Route('/user/{id}', method: 'GET')]
public function getUser(string $id): string
{
    return "User ID: $id";
}
```

---

### ContractFactory

[](#contractfactory)

The `ContractFactory` is a universal factory for creating instances through interface contracts. It enables flexible dependency management by allowing you to register and swap implementations without modifying existing code.

#### Key Benefits:

[](#key-benefits)

- **Flexibility** - Easily swap implementations
- **Testability** - Create mock objects for testing
- **Extensibility** - Add custom implementations
- **Dependency Inversion** - Depend on abstractions, not concrete classes

#### Basic Usage:

[](#basic-usage)

```
use Leopard\Core\Factory\ContractFactory;

// Define an interface
interface LoggerInterface {
    public function log(string $message): void;
}

// Create an implementation
class FileLogger implements LoggerInterface {
    public function log(string $message): void {
        file_put_contents('app.log', $message . PHP_EOL, FILE_APPEND);
    }
}

// Register the implementation
ContractFactory::register(LoggerInterface::class, FileLogger::class);

// Create instances through the factory
$logger = ContractFactory::create(LoggerInterface::class);
$logger->log('Application started');
```

#### Swapping Implementations:

[](#swapping-implementations)

```
// Production logger
class ProductionLogger implements LoggerInterface {
    public function log(string $message): void {
        // Send to external service
    }
}

// Test logger
class TestLogger implements LoggerInterface {
    private array $logs = [];

    public function log(string $message): void {
        $this->logs[] = $message;
    }

    public function getLogs(): array {
        return $this->logs;
    }
}

// In production
ContractFactory::register(LoggerInterface::class, ProductionLogger::class);

// In tests
ContractFactory::register(LoggerInterface::class, TestLogger::class);
```

#### Available Methods:

[](#available-methods)

- `register(string $interface, string $className, array $doctrineMapping = []): void` - Register an implementation (and sync Doctrine mapping when available)
- `create(string $interface): object` - Create an instance
- `getMapping(string $interface): ?string` - Get registered class name
- `hasMapping(string $interface): bool` - Check if interface is registered
- `getMappings(): array` - Get all registered mappings
- `unregister(string $interface): bool` - Unregister an interface
- `clear(): void` - Clear all mappings
- `reset(): void` - Reset to initial state

#### Integration Example:

[](#integration-example)

```
use Leopard\Core\Factory\ContractFactory;
use Leopard\User\Contracts\Models\UserInterface;
use App\Models\User;

// Register user models
ContractFactory::register(UserInterface::class, User::class);

// Use in your application
class UserService {
    public function createUser(array $data): UserInterface {
        $user = ContractFactory::create(UserInterface::class);
        $user->setPassword($data['password']);
        return $user;
    }
}
```

#### Doctrine Integration (auto ResolveTargetEntity)

[](#doctrine-integration-auto-resolvetargetentity)

If `locky42/leopard-doctrine` is installed, `ContractFactory::register(...)` automatically forwards the mapping to `ResolveTargetEntityRegistry::addResolveTargetEntity(...)`.

```
use Leopard\Core\Factory\ContractFactory;
use Leopard\User\Contracts\Models\UserInterface;
use App\Models\User;

// Registers ContractFactory mapping
// + auto-registers Doctrine resolve-target mapping
ContractFactory::register(UserInterface::class, User::class);
```

You can also pass Doctrine mapping options as the third argument:

```
ContractFactory::register(
    UserInterface::class,
    User::class,
    ['fetch' => 'EAGER']
);
```

In application projects, keep all contract mappings in one file (for example `config/contract-mappings.php`) and include it in bootstrap.

#### Best Practices:

[](#best-practices)

1. **Always use `::class` syntax:**

    ```
    // Good
    ContractFactory::register(UserInterface::class, User::class);

    // Bad
    ContractFactory::register('UserInterface', 'User');
    ```
2. **Register at application bootstrap:**

    ```
     // config/contract-mappings.php
    ContractFactory::register(UserInterface::class, User::class);
    ContractFactory::register(LoggerInterface::class, FileLogger::class);

     // bootstrap.php
     require_once __DIR__ . '/config/contract-mappings.php';
    ```
3. **Use type hints with interfaces:**

    ```
    // Good - flexible
    public function processUser(UserInterface $user) { }

    // Bad - tightly coupled
    public function processUser(User $user) { }
    ```
4. **Clear mappings in tests:**

    ```
    class MyTest extends TestCase {
        protected function setUp(): void {
            ContractFactory::clear();
            ContractFactory::register(UserInterface::class, MockUser::class);
        }

        protected function tearDown(): void {
            ContractFactory::reset();
        }
    }
    ```

#### Error Handling:

[](#error-handling)

The factory throws `InvalidArgumentException` in these cases:

- Class doesn't exist
- Interface doesn't exist
- Class doesn't implement the interface
- No mapping found when creating instance

```
try {
    $user = ContractFactory::create(UserInterface::class);
} catch (\InvalidArgumentException $e) {
    // Handle: interface not registered
    echo "Error: " . $e->getMessage();
}
```

---

### View

[](#view)

The `View` class is responsible for rendering templates and managing the presentation layer. It supports layouts, blocks, and integration with the SEO service.

#### Features:

[](#features)

- **Template rendering** with data passing
- **Layout system** for consistent page structure
- **Block rendering** for reusable components
- **CSS and JavaScript management**
- **SEO metadata** through integrated Seo service

#### Example:

[](#example-2)

```
use Leopard\Core\View;

$view = new View(__DIR__ . '/src/views');

// Set custom layout
$view->setLayout('layouts/admin');

// Add styles and scripts
$view->addStyle('/assets/css/main.css');
$view->addScript('/assets/js/app.js');

// Configure SEO
$view->getSeo()->setTitle('Welcome Page');
$view->getSeo()->setDescription('This is the homepage');
$view->getSeo()->setKeywords(['php', 'framework', 'leopard']);

// Render view
echo $view->render('site/home', [
    'username' => 'John',
    'data' => ['foo' => 'bar']
]);
```

#### Rendering Blocks:

[](#rendering-blocks)

```
// In your layout file (layouts/main.php)
DOCTYPE html>
