DEV Community

Cláudio Oliveira
Cláudio Oliveira

Posted on

Introdução ao My PHP MVC Framework: Construindo seu próprio Framework PHP do zero

No mundo do desenvolvimento web, frameworks desempenham um papel crucial na simplificação do processo de desenvolvimento e na organização do código. É difícil encontrar times de desenvolvimento que começam um novo projeto do zero sem pelo menos um "esqueleto", uma base - que justamente os frameworks provém (com algumas outras facilidades).

No mundo PHP (no qual eu tenho maior experiência) temos vários (e bons) exemplos de frameworks web como Laravel, Symfony, CodeIgniter, Zend Framework (Laminas Framework?), Yii, CakePHP, Slim e etc.

Atualmente eu trabalho com Laravel e seu ecossistema para desenvolver soluções web mas em essência eu sou desenvolvedor PHP. Decidi decidi desenvolver um pequeno projeto, um pequeno framework do zero para exercitar um pouco e se divertir.

Criei o "My PHP MVC Framework". A ideia é relativamente simples, um projeto com o padrão MVC (Model-View- Controller) com um código simples e com menos dependências externas possíveis. Por que? Quero escrever a maior parte que conseguir para ter mais resultados educacionais e até mesmo de portifólio.

Funcionalidades

  • Fácil de usar;
  • Simples e intuitivo;
  • Suporte para rotas com parâmetros;
  • Funções ou métodos associados com rotas
  • Gerenciador de banco de dados simples
  • Componentes de visualização

Você pode encontrar o repositório aqui ou pode criar um novo projeto via composer:

composer create-project claud/my-php-mvc
Enter fullscreen mode Exit fullscreen mode

Núcleo do framework:

Roteamento

Dentro do diretório _config _ existe um arquivo router.php e é nele que declaramos nossas rotas.

<?php
require_once __DIR__ . '/../vendor/autoload.php';

use Router\Router\Router;

$router = new Router();

/*
 * Add routes here
 */

$router->addRoute('/', 'App\Controller\WelcomeController@render');



return $router;

Enter fullscreen mode Exit fullscreen mode

Implementação de controladores e modelos básicos

Controler\WelcomeController.php

<?php

declare(strict_types=1);

namespace App\Controller;


class WelcomeController
{
    public function render()
    {
        return view("welcome", ['title' => "Bem vindo!"]);
    }

}

Enter fullscreen mode Exit fullscreen mode

Entity\User.php

<?php

namespace App\Entity;

class User
{
    public  int $id;
    public string $name;
    public string $username;

    public string $email;

    public string $password;

    public ?string $created_at;

    public ?string $updated_at;

    public function __construct(string $name, string $username, string $email, string $password, ?string $created_at = null, ?string $updated_at = null)
    {
        $this->name = $name;
        $this->username = $username;
        $this->email = $email;
        $this->password = $password;
        $this->created_at = $created_at;
        $this->updated_at = $updated_at;
    }

    public function setId(int $id): void
    {
        $this->id = $id;
    }

    public function getId(): ?int
    {
        return $this->id;
    }
}

Enter fullscreen mode Exit fullscreen mode

Repositpry\UserRepository.php

<?php

namespace App\Repository;

use App\Contracts\RepositoryInterface;
use App\Entity\User;
use PDO;

class UserRepository implements RepositoryInterface
{
    private PDO $pdo;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function create(object $user): object
    {
        $sql = 'INSERT INTO users (name, username, email, password) VALUES (?, ?, ?, ?);';
        $statement = $this->pdo->prepare($sql);
        $statement->bindValue(1, $user->name);
        $statement->bindValue(2, $user->username);
        $statement->bindValue(3, $user->email);
        $statement->bindValue(4, $user->password);

        $result = $statement->execute();
        if (!$result) {
            throw new \RuntimeException('Could not save user');
        }

        $id = $this->pdo->lastInsertId();

        $user->setId(intval($id));

        return $user;
    }

    public function update(int $id, object $user): bool
    {
        $sql = 'UPDATE users SET name = ?, username = ?, email = ?, password = ? WHERE id = ?;';
        $statement = $this->pdo->prepare($sql);
        $statement->bindValue(1, $user->name);
        $statement->bindValue(2, $user->username);
        $statement->bindValue(3, $user->email);
        $statement->bindValue(4, $user->password);
        $statement->bindValue(5, $user->id);

        return $statement->execute();
    }

    public function delete(int $id): bool
    {
        $sql = 'DELETE FROM users WHERE id = ?;';
        $statement = $this->pdo->prepare($sql);
        $statement->bindValue(1, $id);

        return $statement->execute();
    }

    public function all(): array
    {
        $sql = 'SELECT * FROM users;';

        $statement = $this->pdo->prepare($sql);

        $statement->execute();

        return $statement->fetchAll(\PDO::FETCH_ASSOC);
    }

    public function findByUsername(string $username)
    {
        $statement = $this->pdo->prepare('SELECT * FROM users WHERE username = ?;');
        $statement->bindValue(1, $username);
        $statement->execute();

        return $statement->fetch(\PDO::FETCH_ASSOC);
    }

    public function find(int $id): null|object
    {
        $statement = $this->pdo->prepare('SELECT * FROM users WHERE id = ?;');
        $statement->bindValue(1, $id);
        $statement->execute();

        $userArr = $statement->fetch(\PDO::FETCH_ASSOC);
        $user = new User($userArr['name'], $userArr['username'], $userArr['email'], $userArr['password'], $userArr['created_at'], $userArr['updated_at']);

        return $user;
    }

}
Enter fullscreen mode Exit fullscreen mode

Exemplo prático:

Criando um site

Criando o projeto

composer create-project claud/my-php-mvc site
Enter fullscreen mode Exit fullscreen mode

Dentro da pasta de Controller crie o arquivo SiteController.php

<?php

declare(strict_types=1);

namespace App\Controller;


class SiteController
{
    public function render(): void
    {
        view("site.index", ['title' => "Bem vindo!"]);
    }
}

Enter fullscreen mode Exit fullscreen mode

O retorno da view renderizar o arquivo index.php dentro da pasta site. Os nomes dos arquivos e pastas ficam a critério do DEV.

Estrutura de pastas

Criei uma pastas chamada __partials para colocar os dados da tag head

Estruturas de pastas

Como exemplo, eu peguei um projeto no Git Hub e usei para criar o site.

head.php

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= $title ?></title>
    <link rel="stylesheet" href="css/variables.css">
    <link rel="stylesheet" href="css/elements.css">
    <link rel="stylesheet" href="css/classes.css">
    <link rel="stylesheet" href="css/menu.css">
    <link rel="stylesheet" href="css/style.css">
</head>
Enter fullscreen mode Exit fullscreen mode

views\site\index.php

<!DOCTYPE html>
<html lang="pt_BR">
<?php include '__partials/head.php' ?>

<body>

    <input id="close-menu" class="close-menu" type="checkbox" aria-label="Close menu" role="button">
    <label class="close-menu-label" for="close-menu" title="close menu"></label>
    <aside class="menu white-bg">
        <div class="main-content menu-content">
            <h1 onclick="getElementById('close-menu').checked = false;">
                <a href="#home">LOGO</a>
            </h1>

            <nav>
                <ul onclick="getElementById('close-menu').checked = false;">
                    <li><a href="#intro">intro</a></li>
                    <li><a href="#grid-one">grid-one</a></li>
                    <li><a href="#gallery">gallery</a></li>
                    <li><a href="#grid-two">grid-two</a></li>
                    <li><a href="#pricing">pricing</a></li>
                    <li><a href="/contact">contact</a></li>
                </ul>
            </nav>
        </div>
    </aside>

    <div class="menu-spacing"></div>

    <section id="home" class="intro main-bg section">
        <div class="main-content intro-content">
            <div class="intro-text-content">
                <h2>January brings us Firefox 85</h2>
                <p>To wrap up January, we are proud to bring you the release of Firefox 85. In this version we are bringing you
                    support for the :focus-visible pseudo-class in CSS and associated devtools, and the complete removal of Flash
                    support from Firefox.</p>
            </div>
            <div class="intro-img">
                <img src="img/javascript.svg" alt="Logo de HTML, CSS e JS.">
            </div>
        </div>
    </section>

    <section id="intro" class="white-bg section">
        <div class="main-content top3-content">
            <h2>news coverage and some
                surprises</h2>
            <p>The release of Apple Silicon-based Macs at the end of last year generated a flurry of news coverage and some
                surprises at the machine’s performance. This post details some background information on the experience of
                porting Firefox to run natively on these CPUs.</p>
            <p>We’ll start with some background on the Mac transition and give an overview of Firefox internals that needed to
                know about the new architecture, before moving on to the concept of Universal Binaries.</p>
            <p>We’ll then explain how DRM/EME works on the new platform, talk about our experience with macOS Big Sur, and
                discuss various updater problems we had to deal with. We’ll conclude with the release and an overview of various
                other improvements that are in the pipeline.</p>
        </div>
    </section>


    <section id="grid-one" class="grid-one main-bg section">
        <div class="main-content grid-one-content">
            <h2 class="grid-main-heading">My Grid</h2>
            <p class="grid-description">Uma breve descrição.</p>

            <div class="grid">
                <article>
                    <h3>Teste 1</h3>
                    <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Debitis cum delectus molestias. Atque doloribus
                        nobis laudantium esse ut, non commodi maxime distinctio veritatis unde, reprehenderit minus ad dolores
                        provident maiores.</p>
                </article>
                <article>
                    <h3>Teste 2</h3>
                    <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Debitis cum delectus molestias. Atque doloribus
                        nobis laudantium esse ut, non commodi maxime distinctio veritatis unde, reprehenderit minus ad dolores
                        provident maiores.</p>
                </article>
                <article>
                    <h3>Teste 2</h3>
                    <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Debitis cum delectus molestias. Atque doloribus
                        nobis laudantium esse ut, non commodi maxime distinctio veritatis unde, reprehenderit minus ad dolores
                        provident maiores.</p>
                </article>
            </div>
        </div>
    </section>

    <section id="gallery" class="grid-one white-bg section">
        <div class="main-content grid-one-content">
            <h2 class="grid-main-heading">Gallery</h2>
            <p class="grid-description">Uma breve descrição.</p>

            <div class="grid">
                <div class="gallery-img">
                    <img src="http://source.unsplash.com/random/360x360?r=1" alt="random image from unsplash" />
                </div>
                <div class="gallery-img">
                    <img src="http://source.unsplash.com/random/360x360?r=2" alt="random image from unsplash" />
                </div>
                <div class="gallery-img">
                    <img src="http://source.unsplash.com/random/360x360?r=3" alt="random image from unsplash" />
                </div>
                <div class="gallery-img">
                    <img src="http://source.unsplash.com/random/360x360?r=4" alt="random image from unsplash" />
                </div>
                <div class="gallery-img">
                    <img src="http://source.unsplash.com/random/360x360?r=5" alt="random image from unsplash" />
                </div>
                <div class="gallery-img">
                    <img src="http://source.unsplash.com/random/360x360?r=6" alt="random image from unsplash" />
                </div>
            </div>
        </div>
    </section>

    <section id="grid-two" class="grid-one main-bg section">
        <div class="main-content grid-one-content">
            <h2 class="grid-main-heading">My Grid</h2>
            <p class="grid-description">Uma breve descrição.</p>

            <div class="grid">
                <article>
                    <h3>Teste 1</h3>
                    <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Debitis cum delectus molestias. Atque doloribus
                        nobis laudantium esse ut, non commodi maxime distinctio veritatis unde, reprehenderit minus ad dolores
                        provident maiores.</p>
                </article>
                <article>
                    <h3>Teste 2</h3>
                    <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Debitis cum delectus molestias. Atque doloribus
                        nobis laudantium esse ut, non commodi maxime distinctio veritatis unde, reprehenderit minus ad dolores
                        provident maiores.</p>
                </article>
                <article>
                    <h3>Teste 2</h3>
                    <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Debitis cum delectus molestias. Atque doloribus
                        nobis laudantium esse ut, non commodi maxime distinctio veritatis unde, reprehenderit minus ad dolores
                        provident maiores.</p>
                </article>
            </div>
        </div>
    </section>

    <section id="pricing" class="white-bg section">
        <div class="main-content top3-content">
            <h2>Pricing</h2>
            <p>The release of Apple Silicon-based Macs at the end of last year generated a flurry of news coverage and some
                surprises at the machine’s performance. This post details some background information on the experience of
                porting Firefox to run natively on these CPUs.</p>
            <p>We’ll start with some background on the Mac transition and give an overview of Firefox internals that needed to
                know about the new architecture, before moving on to the concept of Universal Binaries.</p>

            <div class="responsive-table">
                <table>
                    <caption>Pricing table</caption>

                    <thead>
                        <tr>
                            <th>Title 1</th>
                            <th>Title 2</th>
                            <th>Title 3</th>
                            <th>Title 4</th>
                            <th>Title 5</th>
                        </tr>
                    </thead>

                    <tbody>
                        <tr>
                            <td>Content 1</td>
                            <td>Content 2</td>
                            <td>Content 3</td>
                            <td>Content 3</td>
                            <td>Content 3</td>
                        </tr>
                        <tr>
                            <td>Content 1</td>
                            <td>Content 2</td>
                            <td>Content 3</td>
                            <td>Content 3</td>
                            <td>Content 3</td>
                        </tr>
                        <tr>
                            <td>Content 1</td>
                            <td>Content 2</td>
                            <td>Content 3</td>
                            <td>Content 3</td>
                            <td>Content 3</td>
                        </tr>
                    </tbody>

                    <tfoot>
                        <tr>
                            <td></td>
                            <td></td>
                            <td></td>
                            <td></td>
                            <td>Testando</td>
                        </tr>
                    </tfoot>
                </table>
            </div>
        </div>
    </section>

    <section id="contact" class="intro main-bg section">
        <div class="main-content intro-content">
            <div class="intro-text-content">
                <h2>January brings us Firefox 85</h2>
                <p>To wrap up January, we are proud to bring you the release of Firefox 85. In this version we are bringing you
                    support for the :focus-visible pseudo-class in CSS and associated devtools, and the complete removal of Flash
                    support from Firefox.</p>
            </div>
            <div class="intro-img">
                <img src="img/javascript.svg" alt="Logo de HTML, CSS e JS.">
            </div>
            <div class="contact-form">
                <fieldset class="form-grid">
                    <legend>Contact me</legend>

                    <div class="form-group">
                        <label for="first-name">First name</label>
                        <input type="text" name="first-name" id="first-name" placeholder="Your name">
                    </div>

                    <div class="form-group">
                        <label for="last-name">last name</label>
                        <input type="text" name="last-name" id="last-name" placeholder="Your last name">
                    </div>

                    <div class="form-group">
                        <label for="email">E-mail</label>
                        <input type="email" name="email" id="email" placeholder="Your e-mail">
                    </div>

                    <div class="form-group full-width">
                        <label for="message">Message</label>
                        <textarea name="message" id="message" cols="30" rows="10" placeholder="Your message"></textarea>
                    </div>

                    <div class="form-group full-width">
                        <button type="submit">Send message</button>
                    </div>
                </fieldset>
            </div>
        </div>
    </section>

    <footer id="footer" class="footer white-bg">
        <p><a rel="nofollow" target="_blank" href="https://beacons.page/otaviomiranda">Feito com <span class="heart">❤</span> por Otávio Miranda</a></p>
    </footer>

    <a class="back-to-top" href="#">➤</a>

    <script src="js/script.js"></script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Agora tem que criar a rota para exibir a página. No arquivo router.php dentro da pasta de config vamos escrever o seguinte código:

$router->addRoute('/', 'App\Controller\SiteController@render');
Enter fullscreen mode Exit fullscreen mode

Iniciando o servidor de desenvolvimento

php -S localhost:8000 -t public
Enter fullscreen mode Exit fullscreen mode

Agora vamos acessar a página

http://localhost:8000/
Enter fullscreen mode Exit fullscreen mode

O resultado deve ser esse algo como a imagem abaixo:

Exemplo do projeto

O projeto completo está neste repositório.

Próximos passos

Bom, eu estou pretendendo criar um projeto de gerenciamento de tickets para exemplo. A aplicação terá gerenciamento de usuários, chamados e autenticação de usuários.

Quer me ajudar? Assim que criar o repositório eu edito essa postagem e quem sabe escreva outro post.

Conclusão

O My PHP MVC é um pequeno projeto que visa simplificar e estudar PHP para aplicações web simples. Saliento que o principal objetivo dele é para estudo, ou seja, não é aconselhável usa-lo em ambiente de produção - quem sabe um dia.

Espero que este artigo tenha fornecido insights e inspirado você a quem sabe criar o seu próprio framework. Haaa, você pode me ajudar a melhorar o código também! Escreve nos comentários sugestões de _features _ ou melhor, manda um PR! ⭐⭐⭐

Obs: Fiz uso de IA para me ajudar a escrever esse post (correções ortográficas e sugestões de tópicos) 😉

Top comments (0)