7 ms·
Oink: An API for PHP in a single file
- andridk 3y agoNeat! Any plans to support OpenAPI schema? It's cool that it's easy to create the API, but consuming and documenting it is also a factor.
- tommica 3y agoOh, now that would be a neat feature to have in the library
- manbitesdog 3y agoThanks! I considered it, but since parameters are defined just because users call a validation function, there's no straighforward way to get them. Maybe I could use PHP8 attributes for this, but maybe it complicates the syntax too much for a microframework.
- p4bl0 3y agoThe title is quite confusing on what the project is. It should be "Oink: a single-file PHP library to easily build API" or something like that. Appart from that, it's a nice project, but would benefit from better examples that actually needs an API to do things asynchronously, like a web app that needs to be able to synchronize with a server from time to time. Maybe a TODO list to keep the example short? Because the blog and gallery examples are not really convincing: why would we depend on JS to render the page and almost do the job twice while the rendering could have easily be done in PHP directly?
- tommica 3y agoYep, more example would be useful to have, don't have to be pretty, just functional.
- gexla 3y agoOr you could read the source code. It's only 178 lines of PHP.
- 0x073 3y agoComplete undocumented and compressed lines of 178 php code. I understand the one file thing, but why no docs or more important any tests or static testing.
- gexla 3y agoThere's some documentation here... https://github.com/jcarlosroldan/oink/blob/main/documentation.md https://github.com/jcarlosroldan/oink/blob/main/documentatio... But I get your point.
- episteme 3y agoI hope this is in jest.
- gexla 3y agoWhy not? There are other people in the comments who at least scanned the code. You probably wouldn't be looking at the code if we were talking about a heavier framework, such as Laravel. That 178 lines feels dense though.
- p4bl0 3y agoI was saying the project needs more convincing examples of usage, that is, examples that makes it shine, which the current examples do not. I wasn't at all saying that how it works or how to use it is not clear. Also why would you be so aggressive? In addition to missing the point, your answer is worse than "RTFM".
- progre 3y agoNice! Here is my own take on the same idea (single file PHP api) https://github.com/prog-re/ugly/blob/main/docs/manual.md https://github.com/prog-re/ugly/blob/main/docs/manual.md Edit: I added links to Oink and Zaap from my readme.md. We could make a Single file PHP api framework webring!
- benterix 3y agoA side note: I think you guys are too depreciating. "A pig in the mud", "ugly"... In the old days, we called it KISS. If you really do something improper, e.g. insecure or introducing a performance penalty, you should fix it, but otherwise I see no need of using depreciating names.
- aaviator42 3y agoHey, nice! Here's my take on the same concept: https://github.com/aaviator42/zaap https://github.com/aaviator42/zaap Allows me to have an API with multiple methods and endpoints up and running in minutes. :)
- purerandomness 3y agoYou don't need this PHP snippet: To get the same functionality without the extra step, simply use PostgREST [1] [1] https://postgrest.org/ https://postgrest.org/
- deleted 3y ago[deleted]
- pachico 3y agoI just honestly hope this doesn't turn into another rant against PHP, as it usually happens.
- 0x073 3y agoPersonally we have the dark times from php (v4-v7) and the good times (v7+). As a long time php developer the repository feels like the dark times. I could understand the rant.
- cynicalsecurity 3y agoPlease god no. People might think this is what PHP looks like in real life while it's far from the good practices that are normally used in PHP since ages.
- jw1224 3y agoAfter 15 years of PHP this is the first and only time I’ve ever seen someone actually use the “global” keyword. And to hijack $data, of all the variables you could choose… > This library prioritizes development speed and simplicity over everything else, including best practices and standards. Oh. At least we were warned!
- nolok 3y agoEspecially since it makes zero sense to do that. If you don't care about niceties and want to have a global variable without the absolute php-3 era failure prone jank just use a class static or hell even a function static or whatever. There is no reason whatsoever to use a global variable like that, hasn't been since ... decades now ? And then to name it data on top of it.
- gorm 3y agoYou are right, but for a project like this, with a name like this, it's rather artful and hacky. Nothing wrong in breaking the rules if you know what you are doing.
- dns_snek 3y agoWhat's the significance of $data in PHP?
- hennell 3y agoNothing special, just it's hardly a rare name for a variable. And this sets it globally.
- 3y ago
- deleted 3y ago[deleted]
- moray 3y agoThat's a nice little project that I would use to quickly build an API for testing or developing an app, you just need the php executable to start it up. But if I had to make one comment, after looking at the code, that serve function with that huge try/catch block is not the nicest thing to see, I would split it up and try to get some more meaningful exception and error handling.
- CR007 3y agoI love PHP but I hate the code in this repo.
- raziel2p 3y agoI dislike PHP but quite like the repo. It's very emblematic of what I think PHP is suitable for: Quick dirty prototyping.
- CR007 3y agoYou can do quick prototyping without dirt in any language, it just turns out that you like working in dirt.
- thomaslord 3y agoHonestly if I'm doing quick dirty prototyping I want to have as many tools provided for me as possible. I probably won't use most of them, but spinning up a quick Laravel project gives me built-in shortcuts for stuff that's just annoying to do in plain PHP. Sure there are conventions that say you should separate your code into different files, but if you want to you can just shove all of your code into the route files and ignore everything else.
- huqedato 3y agoThat's a gem! It reminds me why I used to love PHP: rapid prototyping, functional style. It's a shame that I don't use it for a long time.
- tored 3y agoPersonally I believe that you should always use require (require_once) instead of include (include_once) to be sure that a failure to load file creates an E_ERROR instead of an E_WARNING, this both for loading the framework but also when the framework when loading the api file. E_ERROR is a fatal error thus it will throw (I think since PHP 7) an Error exception that can be catched with a normal try/catch and thus possible to return a proper response.
- manbitesdog 3y agoThat's actually a great suggestion, I just made it into the library.
- superasn 3y agoI think this is the reason why PHP is still so popular. Even with the latest 8.3 version these things still work - even though everyone else has moved to frameworks like laravel or fully OO code. Sometimes you just want to get shit done for your personal weekend hobby projects and little things like this can be a tremendous help to make your MVP. Sometimes these MVPs can be super practical in real-life as well (1) Writing code this way can sometimes be liberating and more fun like freestyle swimming. I still miss the days of writing native SQL queries instead of using the ORM for everything nowadays. (1) https://twitter.com/levelsio/status/1381709793769979906?lang=en https://twitter.com/levelsio/status/1381709793769979906?lang...
- tored 3y agoNative SQL is the way to go, I always push for SQL over ORM in every project I'm in.
- SavageBeast 3y agoMe too! There are cases where an ORM is nice and especially if theres a cache layer in there for rarely-changing data requests (think big retail inventory system here). But most of the time we're not doing that and regular SQL is the way to go. This is whats really great about PHP, the time from "I have an idea" to a running implementation can be very quick and minimal. In a world of over-wrought solutions this still stands as a simple way to get shit running in a hurry.
- tored 3y agoI wouldn't pick an ORM even for caching, ORMs are typically a multi-layered blackbox magic based God class, thus very hard to debug when things hit the fan. Here is I how I do it with simple SQL and built in PHP constructs, easy to read, debug and implement. First part of the example is class based and the second one function based if you are more into that. <?php declare(strict_types=1); // SQL with classes namespace { $pdo = new PDO('sqlite::memory:'); $pdo->exec( "CREATE TABLE article ( article_id INTEGER PRIMARY KEY, name TEXT NOT NULL ) "); $pdo->exec("INSERT INTO article (name) VALUES('My Article')"); interface Cache { public function get(string $key): mixed; public function set(string $key, mixed $data): void; } final class RuntimeCache implements Cache { private array $cache = []; public function get(string $key): mixed { return $this->cache[$key] ?? null; } public function set(string $key, mixed $data): void { $this->cache[$key] = $data; } } } namespace Article { use Cache; use PDO; readonly class Article { public int $article_id; public string $name; } interface ArticleRepository { public function getArticleById(int $article_id): Article|null; } final class SqlArticleRepository implements ArticleRepository { public function __construct(private readonly PDO $pdo) { } public function getArticleById(int $article_id): Article|null { $stmt = $this->pdo->prepare( "SELECT article_id, name FROM article WHERE article_id = ?"); $stmt->execute([$article_id]); $article = $stmt->fetchObject(Article::class); return $article === false ? null : $article; } } final class CachedArticleRepository implements ArticleRepository { public function __construct(private readonly Cache $cache, private readonly ArticleRepository $articleRepository) { } public function getArticleById(int $article_id): Article|null { $key = "article-{$article_id}"; $cached = $this->cache->get($key); if (!empty($cached)) { return $cached; } $article = $this->articleRepository->getArticleById($article_id); if ($article !== null) { $this->cache->set($key, $article); } return $article; } } } namespace { $repo = new Article\CachedArticleRepository( new RuntimeCache(), new Article\SqlArticleRepository($pdo) ); var_dump($repo->getArticleById(1)); var_dump($repo->getArticleById(1)); } // SQL with functions namespace { function curry(callable $f, ...$args): callable { $rf = new ReflectionFunction($f); $count = $rf->getNumberOfParameters(); return function (...$arguments) use ($f, $rf, $count, $args) { if (count($args) + count($arguments) >= $count) { return $rf->invokeArgs(array_merge($args, $arguments)); } return curry($f, ...array_merge($args, $arguments)); }; } } namespace Article\SqlRepository { use Article\Article; function getArticleById(\PDO $pdo, int $article_id): Article|null { $stmt = $pdo->prepare( "SELECT article_id, name FROM article WHERE article_id = ?"); $stmt->execute([$article_id]); $article = $stmt->fetchObject(Article::class); return $article === false ? null : $article; } } namespace Article\CachedArticleRepository { use Article\Article; function getArticleById(callable $cacheGet, callable $cacheSet, callable $getArticleById, int $article_id): Article|null { $key = "article-{$article_id}"; $cached = $cacheGet($key); if (!empty($cached)) { return $cached; } $article = $getArticleById($article_id); if ($article !== null) { $cacheSet($key, $article); } return $article; } } namespace { // for this example, just reuse existing class based implementation $runtimeCache = new RuntimeCache(); $getArticleById = curry(Article\CachedArticleRepository\getArticleById(...), $runtimeCache->get(...), $runtimeCache->set(...), curry(Article\SqlRepository\getArticleById(...), $pdo)); var_dump($getArticleById(1)); var_dump($getArticleById(1)); }
- ragnaruss 3y agoReading the code, this feels like a python developer writing PHP and frankly doing a bad job. Even the formatting seems to give up readability for line count for some unknown reason. There is no typing, no following of PSR-2 in anyway, and things which make my static analysis tools very unhappy. A single file API is not a bad idea, but this is a bad implementation.
- onli 3y agoIt's a PHP project. It shouldn't have typing, that's for playing make-belief of writing in Java for the framework-oo-enterprise world. Psr-2 is often not followed, even in really good projects. And the static analysis tools are of limited use for a small code base (though to use them and make using them easier would be a plus).
- smarkov 3y ago> It's a PHP project. It shouldn't have typing, that's for playing make-belief of writing in Java for the framework-oo-enterprise world. At least types are enforced at runtime. Wait until you learn about TypeScript.
- askonomm 3y agoYes it definitely should have types, as is the standard in the community nowadays. Types are definitely not make-belief, as they are checked during runtime, making them actual, real types. I'd say any real project (not a prototype) that has more than 1 person working on it should definitely make use of types so that everyone would know what the shape of the expected data is.
- onli 3y agoTypes are not make-belief, I know that :) I said using them in a php project is playing make-belief, it's a joke about php developers coding enterprise Java style (for php salaries ofc). Had nothing to do with how the language handles types when they are used.
- slim 3y agoThis is cool but, I can make an API for PHP in zero file. It would be simpler and cleaner.
- hiccuphippo 3y agoI've never seen `use function Namespace\{functionName}` before, is that autoloadable with PSR-4?
- tored 3y agoNo, functions are never autoloadable in PHP (except with nasty hacks).
- geenat 3y agoAll it really takes in PHP: https://github.com/gnat/api.php/blob/main/api.php https://github.com/gnat/api.php/blob/main/api.php