4 ms·
I 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 framew
by superasn 3y ago
I 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)); }
- SavageBeast 3y ago"I 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." -- THATS THE TRUTH! Im still scarred from various production issues over the years where the cache keeps filling up all the memory and crashing for no discernable good reason. Yeah - theres a stack trace - it says that 3rd party rats nest library code I have no control over failed... Great, Thanks! ( Hey boss! I fixed that cache bug - I changed the 3rd party code in our dependency - now we're upgrade locked forever!!! Great right!! ) I do things like your example here when I know at what interval the underlying data is capable of changing. Batch process runs every night at midnight, no big deal, kill and rebuild the cache when it finishes. If you know when the data can change you can rebuild the cache on demand or let it gradually repopulate on its own nicely. The nasty part of caching is knowing when something changed in the underlying database such that the now invalidated cache entries can be evicted. Seems to me that when it absolutely needs to be up to the second kind of correct, we're best off skipping the "efficiency gain" a cache MAY offer in favor of direct SQL to an actual database connection. You can spend money on a BIG HOG of a database one time and know precisely how much you will spend to get the speed and reliability your use case demands. If you start trying to solve this with the caching/ORM route you're expense is NOT fixed. Dev Hours vs. Hardware Cost - Im buying hardware almost every time!
- tored 3y agoYes, the paradox with ORMs are that you actually need caching because they are typically very slow, with raw SQL and good indexes you can usually query database live for most of it. I find it usually better to have short timed cache in front of the renderer instead, like caching the HTML or JSON output of a view, then you don't end up with inconsistent data because one table was cached and another wasn't.
- ddtaylor 3y agoI have done both (basic) approaches and I find benefits in each. If I take the time to flesh out a project and either have a real DBA or at least put on my DBA hat for a day to come up with a proper set of SQL functions, views, etc. that expose everything nicely. That takes quite a while, but the results are good. ORMs like SQLAlchemy (for Python) or RedBeanPHP (for PHP) can save a lot of time when making MVPs or just gluing together a couple of open source widgets. For these "quick hack" style things I really enjoy RedBeanPHP's fluid schema: $bean = R::dispense('article'); $bean->title = "Foo"; $bean->body = "Bar! Bar!"; $bean->datePublished = R::isoDateTime(); R::store($bean); That little snippet of code will automatically create a table `article` and the coorisponding columns `title`, `body`, and `date_published` all with their correct types (and type promotion if needed). If I later decide to add something new to articles I just do this: $bean->whatDoesAiThink = $api->infer($bean->body); This would automatically add the column `what_does_ai_think` to the schema. All the automatic schema stuff can be disabled (ie. in production) via `R::freeze(true)`
- tored 3y agoYes, and I view this a strength of PHP, that you can write your own mini framework with a few lines. And as result we have many reliable frameworks to pick from for PHP. Other languages typically only have one dominant web framework, like Java Spring, which typically leads to a stagnating community.