I was using Spring Boot and it was my default selection until one client wanted to build a simple website. I had started coding with PHP but hadn't used it for a while. Then, I remembered the simplicity and minimalism of mixing PHP, HTML, CSS, and JS into one file (or separate files with .js etc), and how easy and cheap the hosting management was. But I didn't want to leave Spring's features behind, like JPA and dependency injection.
So, I built a microframework with zero dependency that even works on shared hosting.
- Website & Docs: pointartframework.com
- GitHub Repo: Cn8001/PointArt
The site itself is using PointArt and resides on shared hosting. For more documentation please visit the website.
PointArt is a PHP micro-framework that brings Spring Boot's attribute-based style to PHP 8.1 (PHP 8.1+ since it introduced attributes, and PDO driver is required):
#[Router(name: 'user', path: '/user')]
class UserController {
#[Wired]
private UserRepository $userRepository;
#[Route('/list', HttpMethod::GET)]
public function index(): string {
return Renderer::render('user.list', ['users' => $this->userRepository->findAll()]);
}
#[Route('/show/{id}', HttpMethod::GET)]
public function show(int $id): string {
$user = User::find($id);
return $user ? Renderer::render('user.show', ['user' => $user]) : httpError(404);
}
#[Route('/create', HttpMethod::POST)]
public function create(
#[RequestParam] string $name,
#[RequestParam] string $email
): string {
$user = new User();
$user->name = $name;
$user->email = $email;
$user->save();
return Renderer::render('user.show', ['user' => $user]);
}
}
The repository pattern generates query implementations from method names — just like Spring Data JPA. If you need custom SQL it also allows it:
abstract class UserRepository extends Repository {
protected string $entityClass = User::class;
abstract public function findByName(string $name): array;
abstract public function findOneByEmail(string $email): ?User;
abstract public function existsByEmail(string $email): bool;
abstract public function countByActiveTrue(): int;
// Custom SQL when you need it
#[Query("SELECT COUNT(*) FROM users")]
abstract public function countAll(): int;
}
Features:
-
#[Router]/#[Route]— attribute-based routing with path params and query string support -
#[Wired]— property injection, no constructor boilerplate -
#[Entity]/#[Column]/#[Id]— ORM for SQLite, MySQL, PostgreSQL - Spring Data-style dynamic finders +
#[Query]for raw SQL - Route + service registry is cached — no Reflection overhead on every request
- No Composer, no build step, no CLI — literally copy files to a server and go (that is the reason why I did not use a full framework.)
It's been a fun project to build from scratch (the dynamic repository finder parser was the trickiest part). Would love any feedback and open to contributions. You can see the future ideas on the website.
Top comments (9)
I don't understand why people don't want to use Composer?
Even if you don't want dependencies Composer helps to set boundaries like the php version, required php extensions (PDO), and versioning.
It also sets a barebones project structure, that most PHP developers are used to.
Another benefit of Composer is that you can use PHPUnit or any other test framework.
I saw your tests and it is just spaghetti code. Nobody is going to look at those tests.
And what about application tests?
If you are dead set on not using a test framework, at least use phpt tests.
While it is fun to create your own framework, there is a minimum of tools required to have a project that is build with code quality and maintainability in mind.
Just copying files to a server is even for PHP developers a no go nowadays.
Thanks for the comment! To be clear, I'm not against Composer at all. If you have the opportunity to use it or use any full-scale framework with it, you definitely should.
However, the issue is that many clients don't need a complex website. Even a stripped-down Symfony build can feel over-engineered for some, not to mention the higher VPS/server costs involved.
My starting point was finding a way to build things in a simple but structured way and still keep the maintainability also bringing in the Spring Boot features I love, while keeping it compatible with simple shared hosting.
The framework itself has test modules and can be expanded for the application, actually this is a future work I am thinking.
Also, I will consider your recommendation about phpt.
Thanks again for your feedback!
In many languages the package manager config file is the starting point of a project. Why would you want to go back to a less defined way of working? It is the backbone of maintainability.
The statement that Symfony needs more expensive hosting is just false. I ran Symfony applications on the cheapest (shared) hosting.
And If you want simple Symfony has a microkernel.
To be clear, the effort you did is commendable. It is the way you approached the features you wanted that strike me as not in tune with the way (PHP) development is done nowadays.
You can have simple and custom with less effort.
For example not long ago tempest/discovery was launched as a standalone component. You should check out the framework as well when you are there.
The library allows you to configure the paths where the relevant files of your application are, and takes care of the caching.
While zero dependencies might look nice, most of the times it means you are fixing problems other people already fixed for you.
I'll admit the deployment process is dated. PointArt started as an exploration, and it evolved into something for edge cases, but you're right that it's not the modern way.
That said, I did learn a lot from building it. The attribute-based routing and DI pattern is solid - I'll definitely check out Tempest.
👏👏👏
Marvelous engineering. Well done !
Fascinating. Wish we had this sooner
Looks cool 🎉
Thanks!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.