PHP code example of pagemill / mvc

1. Go to this page and download the library: Download pagemill/mvc library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

pagemill / mvc example snippets



namespace MyApp;

use PageMill\MVC\ControllerAbstract;

class HomeController extends ControllerAbstract {
    
    protected function getRequestActions(): array {
        return []; // No actions needed for this simple example
    }
    
    protected function getDataActions(): array {
        return []; // No data actions needed
    }
    
    protected function buildModels(): array {
        // Return data to be passed to the view
        return [
            'title' => 'Welcome to PageMill MVC',
            'message' => 'Hello, World!'
        ];
    }
}


namespace MyApp;

use PageMill\MVC\Template\HTMLAbstract;

class HomeView extends HTMLAbstract {
    
    public string $title = '';
    public string $message = '';
    
    protected function prepareDocument(): void {
        $this->document->title = $this->title;
    }
    
    protected function generateHeader(): void {
        echo '<!DOCTYPE html><html><head>';
        echo $this->document->generateHead();
        echo '</head><body>';
    }
    
    protected function generateBody(): void {
        echo '<h1>' . htmlspecialchars($this->title) . '</h1>';
        echo '<p>' . htmlspecialchars($this->message) . '</p>';
    }
    
    protected function generateFooter(): void {
        echo '</body></html>';
    }
}


namespace MyApp;

use PageMill\MVC\ResponderAbstract;

class HomeResponder extends ResponderAbstract {
    
    protected function getView(string $content_type): string {
        return HomeView::class;
    }
}


PageMill\HTTP\Request;
use PageMill\HTTP\Response;
use MyApp\HomeController;
use MyApp\HomeResponder;

$request = new Request();
$response = new Response();

$controller = new HomeController('/', [], $request);
[$data, $inputs] = $controller->handleRequest();

$responder = new HomeResponder($response);
$responder->respond($data, $inputs);

class ProductController extends ControllerAbstract {
    
    // Filters clean/validate input
    protected function getFilters(): array {
        return [
            'id' => ['type' => 'int', 'min' => 1]
        ];
    }
    
    // Request actions run first (validation)
    protected function getRequestActions(): array {
        return [
            new ValidateProductExists()
        ];
    }
    
    // Data actions transform data
    protected function getDataActions(): array {
        return [
            new LoadProductReviews()
        ];
    }
    
    // Models provide data
    protected function buildModels(): array {
        return [
            new ProductModel($this->inputs['id']),
            new CategoryModel()
        ];
    }
}

class ValidateProductExists extends ActionAbstract {
    
    public int $id = 0;
    
    protected function doAction(array $data): mixed {
        $product = Product::findById($this->id);
        
        if (!$product) {
            $this->errors[] = 'Product not found';
            return null;
        }
        
        return ['product' => $product];
    }
}

class ProductModel extends ModelAbstract {
    
    public int $id = 0;
    
    protected function getData(): array {
        return [
            'product' => $this->loadProductFromDatabase($this->id),
            'related' => $this->loadRelatedProducts($this->id)
        ];
    }
}

class ProductResponder extends ResponderAbstract {
    
    protected function getAcceptedContentTypes(): array {
        return ['text/html', 'application/json'];
    }
    
    protected function getView(string $content_type): string {
        return match($content_type) {
            'application/json' => ProductJSONView::class,
            default => ProductHTMLView::class
        };
    }
}

class ProductHTMLView extends HTMLAbstract {
    
    public array $product = [];
    public array $related = [];
    
    protected function prepareDocument(): void {
        $this->document->title = $this->product['name'];
        $this->document->canonical = 'https://example.com/products/' . $this->product['id'];
        $this->assets->add('css', ['product']);
        $this->assets->add('js', ['product'], 'footer');
    }
    
    protected function generateHeader(): void {
        echo '<!DOCTYPE html><html><head>';
        echo $this->document->generateHead();
        $this->assets->link('css');
        echo '</head><body>';
    }
    
    protected function generateBody(): void {
        echo '<h1>' . htmlspecialchars($this->product['name']) . '</h1>';
        echo '<p>' . htmlspecialchars($this->product['description']) . '</p>';
        echo '<div class="related-products">';
        foreach ($this->related as $item) {
            echo '<div class="product">' . htmlspecialchars($item['name']) . '</div>';
        }
        echo '</div>';
    }
    
    protected function generateFooter(): void {
        $this->assets->link('js', 'footer');
        echo '</body></html>';
    }
}

class ProductJSONView extends JSONAbstract {
    
    public array $product = [];
    public array $related = [];
    
    protected function getData(): array {
        return [
            'status' => 'success',
            'data' => [
                'product' => $this->product,
                'related' => $this->related
            ]
        ];
    }
}

// In your view's prepareDocument() method:

// Add CSS files
$this->assets->add('css', ['normalize', 'main', 'components']);

// Add JS files to different groups
$this->assets->add('js', ['app'], 'header');
$this->assets->add('js', ['analytics'], 'footer');

// In generateHeader():
$this->assets->link('css'); // Outputs <link> tags

// In generateFooter():
$this->assets->link('js', 'footer'); // Outputs <script> tags

$assets = Assets::init();

$assets->addLocation('css', [
    'directory' => '/var/www/public/css',
    'url' => '/css'
]);

$assets->addLocation('js', [
    'directory' => '/var/www/public/js',
    'url' => '/js'
]);

// Inline critical CSS
$this->assets->inline('css', 'critical');

// Output: <style>/* contents of critical.css */</style>

use PageMill\MVC\HTML\Assets\Combine;

$combine = new Combine($assets, $request, $response);
$combine->combine('css'); // Combines all requested CSS files

class ButtonElement extends ElementAbstract {
    
    public static function getAssets(?string $class = null): array {
        return [
            'css' => ['button'],
            'js' => ['button-handler']
        ];
    }
}

// In your view:
$this->element_assets->add([ButtonElement::class]);
// Automatically loads button.css and button-handler.js

protected function prepareDocument(): void {
    // Page title
    $this->document->title = 'My Page Title';
    
    // Canonical URL
    $this->document->canonical = 'https://example.com/page';
    
    // Robots directives
    $this->document->robots_index = false;    // noindex
    $this->document->robots_follow = true;    // follow
    $this->document->robots_archive = false;  // noarchive
    
    // Meta tags
    $this->document->addMeta([
        'name' => 'description',
        'content' => 'Page description'
    ]);
    
    $this->document->addMeta([
        'property' => 'og:title',
        'content' => 'My Page Title'
    ]);
    
    // Custom variables for templates
    $this->document->custom_var = 'Some value';
}

// In generateHeader():
echo $this->document->generateHead();
// Outputs: <title>, <meta>, <link rel="canonical">, etc.

// HTTP headers are sent automatically:
$this->document->generateHeaders();
// Sends: X-Robots-Tag, Link rel=canonical, etc.

class AlertElement extends ElementAbstract {
    
    public string $message = '';
    public string $type = 'info';
    
    public static function getAssets(?string $class = null): array {
        return [
            'css' => ['alert'],
            'js' => ['alert-dismiss']
        ];
    }
    
    public function generateElement(): void {
        echo '<div class="alert alert-' . htmlspecialchars($this->type) . '">';
        echo htmlspecialchars($this->message);
        echo '<button class="close">×</button>';
        echo '</div>';
    }
}

// Usage:
$alert = new AlertElement(['message' => 'Success!', 'type' => 'success']);
$alert->generateElement();

// Auto-load element assets:
$this->element_assets->add([AlertElement::class]);

use PageMill\MVC\Environment;

// Enable debug mode
Environment::debug(true);

// Check debug state
if (Environment::debug()) {
    // Show detailed errors
}

// Disable debug mode
Environment::debug(false);

use PageMill\MVC\Traits\PropertyMap;

class MyClass {
    use PropertyMap;
    
    public string $name = '';
    public int $age = 0;
    
    // Optional: Define type constraints
    protected static array $constraints = [
        'email' => ['type' => 'string'],
        'created_at' => ['type' => \DateTime::class]
    ];
    
    public function __construct(array $data) {
        // Maps array keys to properties with type checking
        $this->mapProperties($data);
        
        // Ignore unknown properties
        $this->mapProperties($data, ignore: true);
    }
}

class ProductResponder extends ResponderAbstract {
    
    protected function getAcceptedContentTypes(): array {
        return ['text/html', 'application/json', 'application/xml'];
    }
    
    protected function getView(string $content_type): string {
        return match($content_type) {
            'application/xml' => ProductXMLView::class,
            'application/json' => ProductJSONView::class,
            default => ProductHTMLView::class
        };
    }
}

class ProcessOrderAction extends ActionAbstract {
    
    protected function doAction(array $data): mixed {
        // Validate first
        $validateAction = new ValidateOrderAction($this->inputs);
        $result = $validateAction->doAction($data);
        
        if (!empty($validateAction->getErrors())) {
            $this->errors = $validateAction->getErrors();
            return null;
        }
        
        // Then process
        return $this->processPayment($result);
    }
}

$assets->registerHandler(
    'css',
    'sri', // Custom handler name
    function(string $type, array $assetList) {
        foreach ($assetList as $asset) {
            $hash = hash_file('sha384', $asset['path']);
            echo '<link rel="stylesheet" href="' . $asset['url'] . '" ';
            echo 'integrity="sha384-' . base64_encode($hash) . '" ';
            echo 'crossorigin="anonymous">';
        }
    }
);

$assets->generate('sri', null, 'css');

class MyController extends ControllerAbstract {
    
    protected function getRequestActions(): array {
        return [new ValidateInputAction()];
    }
    
    protected function buildModels(): array {
        // Check if validation failed
        if (!empty($this->inputs['_errors'])) {
            return ['errors' => $this->inputs['_errors']];
        }
        
        return [new MyModel($this->inputs)];
    }
}

use PHPUnit\Framework\TestCase;
use PageMill\HTTP\Request;

class MyControllerTest extends TestCase {
    
    public function testHandleRequestReturnsData(): void {
        $request = $this->createMock(Request::class);
        $controller = new MyController('/path', ['id' => 1], $request);
        
        [$data, $inputs] = $controller->handleRequest();
        
        $this->assertArrayHasKey('product', $data);
        $this->assertEquals(1, $inputs['id']);
    }
}

$assets->addLocation('css', [
    'directory' => __DIR__ . '/public/css',  // Absolute path
    'url' => '/css'  // Web-accessible URL
]);

protected static array $constraints = [
    'age' => ['type' => 'integer']
];

$this->mapProperties($data, ignore: true);