From ff28a7ac42849214dbd84fb9db3e81d8c30638ff Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Wed, 29 Jul 2026 13:40:48 +0300 Subject: [PATCH 01/10] Issue 23 dotkernel.com --- bin/generate-feed | 23 + composer.json | 4 +- config/autoload/app.global.php | 3 + public/feed.xml | 0 src/App/src/ConfigProvider.php | 6 + src/App/src/Factory/FeedGeneratorFactory.php | 31 ++ .../src/Factory/GetFeedViewHandlerFactory.php | 25 + src/App/src/Fixture/PostLoader.php | 6 + src/App/src/Fixture/articles_cleaned.json | 489 ++++++++++++------ src/App/src/Handler/GetFeedViewHandler.php | 38 ++ .../src/Migration/Version20260728073216.php | 31 ++ src/App/src/RoutesDelegator.php | 2 + src/App/src/Service/FeedGenerator.php | 81 +++ src/Blog/src/Entity/Post.php | 15 + 14 files changed, 590 insertions(+), 164 deletions(-) create mode 100755 bin/generate-feed create mode 100644 public/feed.xml create mode 100644 src/App/src/Factory/FeedGeneratorFactory.php create mode 100644 src/App/src/Factory/GetFeedViewHandlerFactory.php create mode 100644 src/App/src/Handler/GetFeedViewHandler.php create mode 100644 src/App/src/Migration/Version20260728073216.php create mode 100644 src/App/src/Service/FeedGenerator.php diff --git a/bin/generate-feed b/bin/generate-feed new file mode 100755 index 0000000..dcb6e86 --- /dev/null +++ b/bin/generate-feed @@ -0,0 +1,23 @@ +#!/usr/bin/env php +get(FeedGenerator::class); + +$count = $feedGenerator->write(); + +printf( + "Done. %d article%s written to %s%s", + $count, + $count === 1 ? '' : 's', + $feedGenerator->getFeedFile(), + PHP_EOL +); \ No newline at end of file diff --git a/composer.json b/composer.json index ac299b0..f0e2240 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ }, "require": { "php": "~8.5.0", + "ext-dom": "*", "doctrine/data-fixtures": "^2.2", "doctrine/doctrine-fixtures-bundle": "^4.3", "dotkernel/dot-errorhandler": "^5.0.0", @@ -69,7 +70,8 @@ "@development-enable" ], "post-update-cmd": [ - "php bin/composer-post-install-script.php" + "php bin/composer-post-install-script.php", + "php bin/generate-feed.php" ], "development-disable": "laminas-development-mode disable", "development-enable": "laminas-development-mode enable", diff --git a/config/autoload/app.global.php b/config/autoload/app.global.php index 7415db0..2a7a905 100644 --- a/config/autoload/app.global.php +++ b/config/autoload/app.global.php @@ -22,6 +22,9 @@ return [ 'app' => $app, + 'feed' => [ + 'file' => 'public/feed.xml', + ], 'twig' => [ 'globals' => [ 'app' => $app, diff --git a/public/feed.xml b/public/feed.xml new file mode 100644 index 0000000..e69de29 diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index 32014b5..1d43f04 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -12,9 +12,13 @@ use Dot\Cache\Adapter\FilesystemAdapter; use Light\App\DBAL\Types\UuidType; use Light\App\Factory\EntityListenerResolverFactory; +use Light\App\Factory\FeedGeneratorFactory; +use Light\App\Factory\GetFeedViewHandlerFactory; use Light\App\Factory\GetIndexViewHandlerFactory; +use Light\App\Handler\GetFeedViewHandler; use Light\App\Handler\GetIndexViewHandler; use Light\App\Resolver\EntityListenerResolver; +use Light\App\Service\FeedGenerator; use Mezzio\Application; use Roave\PsrContainerDoctrine\EntityManagerFactory; use Symfony\Component\Cache\Adapter\AdapterInterface; @@ -109,6 +113,8 @@ public function getDependencies(): array 'doctrine.entity_manager.orm_default' => EntityManagerFactory::class, EntityListenerResolver::class => EntityListenerResolverFactory::class, GetIndexViewHandler::class => GetIndexViewHandlerFactory::class, + GetFeedViewHandler::class => GetFeedViewHandlerFactory::class, + FeedGenerator::class => FeedGeneratorFactory::class, ], 'aliases' => [ EntityManager::class => 'doctrine.entity_manager.orm_default', diff --git a/src/App/src/Factory/FeedGeneratorFactory.php b/src/App/src/Factory/FeedGeneratorFactory.php new file mode 100644 index 0000000..7544583 --- /dev/null +++ b/src/App/src/Factory/FeedGeneratorFactory.php @@ -0,0 +1,31 @@ +get(PostRepository::class); + assert($postRepository instanceof PostRepository); + + $config = $container->get('config'); + + return new FeedGenerator( + $postRepository, + $config['feed']['file'], + rtrim($config['application']['url'] ?? '', '/') . '/', + $config['app']['meta']['title'] ?? '', + $config['app']['meta']['description'] ?? '', + ); + } +} diff --git a/src/App/src/Factory/GetFeedViewHandlerFactory.php b/src/App/src/Factory/GetFeedViewHandlerFactory.php new file mode 100644 index 0000000..60d68f5 --- /dev/null +++ b/src/App/src/Factory/GetFeedViewHandlerFactory.php @@ -0,0 +1,25 @@ +get(FeedGenerator::class); + assert($feedGenerator instanceof FeedGenerator); + + return new GetFeedViewHandler($feedGenerator); + } +} diff --git a/src/App/src/Fixture/PostLoader.php b/src/App/src/Fixture/PostLoader.php index 716e0a1..52e3667 100644 --- a/src/App/src/Fixture/PostLoader.php +++ b/src/App/src/Fixture/PostLoader.php @@ -75,6 +75,7 @@ public function load(ObjectManager $manager): void : new DateTimeImmutable(); $excerpt = $articleData['excerpt'] ?? ''; + $tlDr = $articleData['tl_dr'] ?? ''; $article = $repository->findOneBy(['slug' => $slug]); @@ -87,6 +88,7 @@ public function load(ObjectManager $manager): void $article->setCategory($category); $article->setAuthor($author); $article->setExcerpt($excerpt); + $article->setTldr($tlDr); $manager->persist($article); echo "CREATE: {$title}\n"; @@ -117,6 +119,10 @@ public function load(ObjectManager $manager): void $article->setExcerpt($excerpt); $changed = true; } + if ($article->getTldr() !== $tlDr) { + $article->setTldr($tlDr); + $changed = true; + } echo $changed ? "UPDATE: {$title}\n" : "UNCHANGED: {$title}\n"; } diff --git a/src/App/src/Fixture/articles_cleaned.json b/src/App/src/Fixture/articles_cleaned.json index c05815b..74b96e2 100644 --- a/src/App/src/Fixture/articles_cleaned.json +++ b/src/App/src/Fixture/articles_cleaned.json @@ -12,7 +12,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "DotKernel borrows the database naming conventions from FaZend: Rules of naming of database tables and columns. FaZend is an open-source PHP framework based on Zend Framework." + "excerpt": "DotKernel borrows the database naming conventions from FaZend: Rules of naming of database tables and columns. FaZend is an open-source PHP framework based on Zend Framework.", + "tl_dr": "DotKernel's database naming conventions are borrowed from FaZend's \"Rules of naming of database tables and columns.\"\nTables use singular, camelLetter names, every table has an auto-increment id, foreign keys are named after the referenced table and column, and SQL keywords are capitalized." }, { "post_title": "camelCase Table Names in MySQL on Windows", @@ -22,7 +23,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "If you are using a WAMP stack, like WAMP or XAMPP, and try to create a table in camelCase ( example: adminLogin) you will notice that camelCase is not working, table name will be lowercase: adminlogin. In order to fix this, you need to add to your my." + "excerpt": "If you are using a WAMP stack, like WAMP or XAMPP, and try to create a table in camelCase ( example: adminLogin) you will notice that camelCase is not working, table name will be lowercase: adminlogin. In order to fix this, you need to add to your my.", + "tl_dr": "" }, { "post_title": "Sending emails using Dot_Email component and Zend_Email", @@ -32,7 +34,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Dot_Email class extends Zend_Mail, so all the methods from Zend_Mail are available  in Dot_Email. Dot_Email is a simple class composed only from 2 methods, except constructor, all  other methods beeing inherited  from Zend_Mail." + "excerpt": "Dot_Email class extends Zend_Mail, so all the methods from Zend_Mail are available  in Dot_Email. Dot_Email is a simple class composed only from 2 methods, except constructor, all  other methods beeing inherited  from Zend_Mail.", + "tl_dr": "Dot_Email extends Zend_Mail, so all of Zend_Mail's methods are available in it.\nBeyond its constructor, Dot_Email itself adds only two methods: setContent() and send().\nTo send an email you must always call addTo(), setSubject(), one of setBodyText()\/setBodyHtml()\/setContent(), and finally send()." }, { "post_title": "DotKernel 1.2.0 release", @@ -42,7 +45,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "Finally we reached DotKernel 1.2." + "excerpt": "Finally we reached DotKernel 1.2.", + "tl_dr": "DotKernel 1.2.0 has been released, bringing changes since the previous 1.1.2 release.\nThe database tables were renamed and restructured to follow database naming conventions, and configuration for each \"dots\" (submodule) now lives in XML files instead of being hard-coded in PHP.\nThe release also adds new library classes (Dot_Geoip, Dot_Seo), updates existing ones (Dot_Curl, Dot_Session), and confirms that all SQL queries are written as prepared statements." }, { "post_title": "DotKernel 1.2.2 release", @@ -52,7 +56,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "Yesterday, we released DotKernel 1.2." + "excerpt": "Yesterday, we released DotKernel 1.2.", + "tl_dr": "DotKernel 1.2.2 is a bug-fix release that closes five tracked issues.\nBecause one of the fixes updated the copyright line, every PHP file in the codebase changed, so the full release or the incremental upgrade package is needed." }, { "post_title": "DotKernel 1.3.0 release", @@ -62,7 +67,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "DotKernel 1.3." + "excerpt": "DotKernel 1.3.", + "tl_dr": "DotKernel 1.3.0 brings a switchable admin skin, a way to protect member-only pages, a rename of Dot_Sessions, and a reorganization of resource.xml into route.xml and dots.xml.\nBecause of that XML reorganization, 1.3.0 is not backward compatible with earlier versions." }, { "post_title": "GeoIP: Ip Address Location In DotKernel", @@ -72,7 +78,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "GeoIP is the proprietary technology that drives MaxMind's IP geolocation data and services. It is a non-invasive way to determine geographical and other information about Internet visitors in real-time." + "excerpt": "GeoIP is the proprietary technology that drives MaxMind's IP geolocation data and services. It is a non-invasive way to determine geographical and other information about Internet visitors in real-time.", + "tl_dr": "GeoIP is MaxMind's proprietary technology for IP geolocation.\nDotKernel uses it to get user statistics by country, determining a visitor's country, region, city, postal code, or area code in real time.\nThe logic lives in library\/Dot\/Geoip.php, inside the getCountryByIp function, which branches over four cases depending on whether the mod_geoip PECL extension and its .dat files are available." }, { "post_title": "WURFL Zend Framework Integration into DotKernel", @@ -82,7 +89,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "WURFL is integrated into DotKernel, using the Zend_Http_UserAgent class from the latest release ZF 1.11." + "excerpt": "WURFL is integrated into DotKernel, using the Zend_Http_UserAgent class from the latest release ZF 1.11.", + "tl_dr": "WURFL is integrated into DotKernel using the Zend_Http_UserAgent class from ZF 1.11.0rc1 (the beta release at the time of the post).\nThis post walks through the required folders, config files, and code to wire it up." }, { "post_title": "DotKernel 1.3.2 release", @@ -92,7 +100,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "Before the winter holiday we came with a new release: DotKernel 1.3." + "excerpt": "Before the winter holiday we came with a new release: DotKernel 1.3.", + "tl_dr": "Released just before the winter holidays, DotKernel 1.3.2 is mainly a maintenance release: it contains many bug fixes, some refactoring, and a few minor features." }, { "post_title": "Zend_Auth and Zend_Acl integrated in DotKernel", @@ -102,7 +111,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "Zend_Auth and Zend_Acl have been integrated into the DotKernel, starting with version 1.5." + "excerpt": "Zend_Auth and Zend_Acl have been integrated into the DotKernel, starting with version 1.5.", + "tl_dr": "Zend_Auth and Zend_Acl have been integrated into DotKernel starting with version 1.5.0.\nThe User and Admin models were completely refactored using the new Dot_Auth and Dot_Acl classes for authentication and access control." }, { "post_title": "Disable Wurfl redirect for mobile browsers", @@ -112,7 +122,8 @@ "display_name": "Adrian", "github": "" }, - "excerpt": "DotKernel has an example mobile site at http:\/\/v1.dotkernel." + "excerpt": "DotKernel has an example mobile site at http:\/\/v1.dotkernel.", + "tl_dr": "DotKernel's example mobile site normally relies on Wurfl to detect mobile browsers and automatically redirect visitors there on their first homepage view, which isn't always desired.\nAs of revision 408, this behavior is controlled by a single resources.useragent.wurflapi.redirect setting in application.ini.\nThe article shows that setting along with the matching condition in IndexController.php that checks it before registering and redirecting a visit." }, { "post_title": "Protecting admin folder with .htaccess in Plesk", @@ -122,7 +133,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "In \/var\/www\/vhosts\/exampledomain.com\/conf\/vhost." + "excerpt": "In \/var\/www\/vhosts\/exampledomain.com\/conf\/vhost.", + "tl_dr": "" }, { "post_title": "Adding a second caching layer to WURFL in Dotkernel using APC", @@ -132,7 +144,8 @@ "display_name": "Adrian", "github": "" }, - "excerpt": "On one of our recent projects that used WURFL, response time was an important factor. Profiling revealed that the greatest chunk of response time (up to a few hundred milliseconds) was taken up by WURFL." + "excerpt": "On one of our recent projects that used WURFL, response time was an important factor. Profiling revealed that the greatest chunk of response time (up to a few hundred milliseconds) was taken up by WURFL.", + "tl_dr": "On a high-traffic project using WURFL, profiling showed WURFL's default filesystem cache was costing up to a few hundred milliseconds per request. Adding a small, custom second cache layer on top of WURFL, built on APC, cut response time by an order of magnitude, down to 20-30ms." }, { "post_title": "Zend Registry usage in DotKernel", @@ -142,7 +155,8 @@ "display_name": "Adrian", "github": "" }, - "excerpt": "In DotKernel, Zend_Registry will contain the following variables: startTime - the result of microtime() at the beginning of the request configuration - the configuration options loaded from configs\/application.ini router - routing settings loaded from configs\/router." + "excerpt": "In DotKernel, Zend_Registry will contain the following variables: startTime - the result of microtime() at the beginning of the request configuration - the configuration options loaded from configs\/application.ini router - routing settings loaded from configs\/router.", + "tl_dr": "In DotKernel, Zend_Registry holds a fixed set of request-scoped variables — from timing and configuration to the database adapter and session object — and can be read either as a full instance or one value at a time." }, { "post_title": "WURFL PHP API license incompatible with DotKernel", @@ -152,7 +166,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "We integrated long time ago the WURFL PHP API into DotKernel code base. At that time, the license of that WURFL library was GNU\/GPL, which make it perfect compatible with Zend Framework license( new BSD) and DotKernel ( OSL 3." + "excerpt": "We integrated long time ago the WURFL PHP API into DotKernel code base. At that time, the license of that WURFL library was GNU\/GPL, which make it perfect compatible with Zend Framework license( new BSD) and DotKernel ( OSL 3.", + "tl_dr": "The WURFL PHP API was integrated into DotKernel long ago under a GNU\/GPL license, compatible with Zend Framework's new BSD license and DotKernel's OSL 3.0 license.\nOn June 6th, 2011, WURFL PHP API version 1.3.0 changed its license to AGPL, turning it into a \"trial only\" library for product evaluation.\nDotKernel had updated to this version in the 1.5.0 release candidate without noticing the license change." }, { "post_title": "DotKernel 1.5.0 Released", @@ -162,7 +177,8 @@ "display_name": "Adrian", "github": "" }, - "excerpt": "After a longer wait than usual, DotKernel 1.5." + "excerpt": "After a longer wait than usual, DotKernel 1.5.", + "tl_dr": "After a longer wait than usual and around 250 commits, DotKernel 1.5.0 was released, skipping 1.4 entirely due to the scale of changes.\nHighlights include switching from Dojo to jQuery, a redesigned admin and frontend, model inheritance through a new Dot_Model class, support for dashed controller names, and a reorganized Zend Registry." }, { "post_title": "Detecting Mobile Devices in DotKernel 1.6.0", @@ -172,7 +188,8 @@ "display_name": "deddu", "github": "" }, - "excerpt": "The new DotKernel version 1.6." + "excerpt": "The new DotKernel version 1.6.", + "tl_dr": "DotKernel 1.6.0 no longer ships with a working built-in mobile detection method, because mobile detection now relies on the new Wurfl Cloud integration and must be configured via a Wurfl Cloud account and API key.\nThe old Dot_UserAgent_Wurfl class was removed and replaced by Dot_UserAgent_WurflCloud, which uses the Wurfl Cloud API adapter.\nThe article walks through the application.ini settings and shows sample code for reading device info and redirecting mobile visitors." }, { "post_title": "Zend_Session usage in DotKernel - Refactor of Dot_Session class", @@ -182,7 +199,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "We found a strange behaviour of sessions in one of our project, running DotKernel version 1.5." + "excerpt": "We found a strange behaviour of sessions in one of our project, running DotKernel version 1.5.", + "tl_dr": "A strange session bug was found on a project running DotKernel 1.5.0: in IE8 and IE9, the session cookie was sometimes not saved, forcing repeated logins.\nInvestigation traced it to the Dot_Session class calling both regenerateID() and rememberMe() unnecessarily, generating the session cookie 3 times.\nThe fix, shipped in DotKernel 1.5.1, removed the regenerateID() call and added two new application.ini settings." }, { "post_title": "Zend_Console implementation in DotKernel", @@ -192,7 +210,8 @@ "display_name": "Adrian", "github": "" }, - "excerpt": "Starting with 1.5, DotKernel has a Console bootstrap to easily run PHP scripts from the command line." + "excerpt": "Starting with 1.5, DotKernel has a Console bootstrap to easily run PHP scripts from the command line.", + "tl_dr": "Starting with version 1.5, DotKernel has a Console bootstrap to easily run PHP scripts from the command line.\nThe most common use for this is running cron jobs without using wget or going through Apache." }, { "post_title": "Manual upgrade of WURFL xml file in DotKernel", @@ -202,7 +221,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "DotKernel Application Framework can be downloaded with WURFL XML file bundled in it, but is quite an old file,  the latest GPL version, from June 2011. Because of license changed of that WURFL file,  this bundled file will not be upgraded anymore by us." + "excerpt": "DotKernel Application Framework can be downloaded with WURFL XML file bundled in it, but is quite an old file,  the latest GPL version, from June 2011. Because of license changed of that WURFL file,  this bundled file will not be upgraded anymore by us.", + "tl_dr": "DotKernel Application Framework bundles a WURFL XML file, but it's the last GPL version (from June 2011).\nBecause of a license change to that WURFL file, DotKernel will no longer upgrade the bundled file — it must be upgraded manually." }, { "post_title": "How to Set a Persistent Connection to Database with Zend Framework Zend_Db adapter", @@ -212,7 +232,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "From time to time, it may be a good idea to have a persistent connection to database. The place where it should be added that new configuration option is application." + "excerpt": "From time to time, it may be a good idea to have a persistent connection to database. The place where it should be added that new configuration option is application.", + "tl_dr": "" }, { "post_title": "Zend Studio PHP Formatter file for DotKernel coding standard.", @@ -222,7 +243,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Here is  uploaded the XML file , ready to be imported in your Zend Studio, version 9.x This file follow DotKernel’s Coding standard." + "excerpt": "Here is  uploaded the XML file , ready to be imported in your Zend Studio, version 9.x This file follow DotKernel’s Coding standard.", + "tl_dr": "" }, { "post_title": "Zend Framework dropped integration of WURFL adapter", @@ -232,7 +254,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "According to Matthew Weier O'Phinney, Zend Framework Project Leader, in the next release of ZF, 1.12." + "excerpt": "According to Matthew Weier O'Phinney, Zend Framework Project Leader, in the next release of ZF, 1.12.", + "tl_dr": "" }, { "post_title": "Using UTF8 charset in DotKernel", @@ -242,7 +265,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "In order to use UTF8 encoding in your DotKernel based system, is needed to make some changes in both database structure and in the application.ini file." + "excerpt": "In order to use UTF8 encoding in your DotKernel based system, is needed to make some changes in both database structure and in the application.ini file.", + "tl_dr": "To use UTF8 encoding in a DotKernel-based system, changes are needed in both the database structure and the application.ini file.\nThese changes were committed into the DotKernel 1.6.0 dev codebase." }, { "post_title": "Commitment to PHP - new Zend Certified Engineers - ZCE - in our team", @@ -252,7 +276,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Another 2 of our team members passed the ZCE exam. Now we are 5 :-) That mean we are really taking PHP  into serious , and at least we have good technical skills." + "excerpt": "Another 2 of our team members passed the ZCE exam. Now we are 5 :-) That mean we are really taking PHP  into serious , and at least we have good technical skills.", + "tl_dr": "" }, { "post_title": "Forcing UTF8 connections and character set in MySQL", @@ -262,7 +287,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "In some situations, it may be neccesar to force MySQL server collation and character set to UTF8. As you can't control all scripts that are connecting to your database( for instance: mysql command line, or mysqldump) For that , open the my." + "excerpt": "In some situations, it may be neccesar to force MySQL server collation and character set to UTF8. As you can't control all scripts that are connecting to your database( for instance: mysql command line, or mysqldump) For that , open the my.", + "tl_dr": "In some situations it may be necessary to force the MySQL server's collation and character set to UTF8, since you can't control all the scripts connecting to your database (for instance the mysql command line or mysqldump).\nThis is done by editing my.cnf." }, { "post_title": "Highcharts Integration in DotKernel 1.6.0", @@ -272,7 +298,8 @@ "display_name": "deddu", "github": "" }, - "excerpt": "Integrating a new charting library in the latest version of DotKernel (1.6." + "excerpt": "Integrating a new charting library in the latest version of DotKernel (1.6.", + "tl_dr": "DotKernel 1.6.0 integrates the Highcharts charting library, offering a new, intuitive and interactive charting experience.\nSample charts (pie, column and line) were added to the admin, and the library ships in the project's externals directory." }, { "post_title": "Wurfl Cloud Integration in DotKernel 1.6.0", @@ -282,7 +309,8 @@ "display_name": "deddu", "github": "" }, - "excerpt": "Another new feature in version 1.6." + "excerpt": "Another new feature in version 1.6.", + "tl_dr": "DotKernel 1.6.0 integrates Wurfl Cloud, WURFL's (Wireless Universal Resource FiLe) new cloud-based way of delivering device detection services, as its default method for detecting mobile devices." }, { "post_title": "Scientia Mobile licensed its Wurfl Cloud PHP library to DotKernel 1.6", @@ -292,7 +320,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "In the DotKernel version 1.6." + "excerpt": "In the DotKernel version 1.6.", + "tl_dr": "In DotKernel 1.6.0, released on May 16th, 2012, the GPL'ed WURFL PHP library was removed because its code was obsolete and the XML file structure had changed. It was replaced by Scientia Mobile's WURFL Cloud PHP library, made available to DotKernel under a special, restrictive license." }, { "post_title": "New Features in Zend Framework 1.12", @@ -302,7 +331,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "According to Matthew Weier O'Phinney announcement, Zend Framework team is pleased to announce the immediate availability of the first release candidate of the Zend Framework 1.12 series, 1." + "excerpt": "According to Matthew Weier O'Phinney announcement, Zend Framework team is pleased to announce the immediate availability of the first release candidate of the Zend Framework 1.12 series, 1.", + "tl_dr": "Per Matthew Weier O'Phinney's announcement, the Zend Framework team made available the first release candidate of the Zend Framework 1.12 series, 1.12.0RC1.\nIt back ports several ZF2 components to ZF1, removes the WurflApi adapter due to licensing changes, and fixes over 200 reported issues." }, { "post_title": "DotKernel on Nginx", @@ -312,7 +342,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Due to the fact that the current buzzword is Nginx instead of Apache, we decided to test if Dotkernel is running out of the box on it. And how to configure Nginx :-) Installed on a clean Centos 6." + "excerpt": "Due to the fact that the current buzzword is Nginx instead of Apache, we decided to test if Dotkernel is running out of the box on it. And how to configure Nginx :-) Installed on a clean Centos 6.", + "tl_dr": "Since Nginx was becoming the buzzword instead of Apache, this article tests DotKernel on Nginx and documents the configuration needed: server block settings, a try_files directive in place of .htaccess, PHP-FPM handling, and protecting the configs folder." }, { "post_title": "PHP Formatter and Templates for Zend Studio 10.1", @@ -322,7 +353,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Because Zend donated the Zend Studio's Formatter upstream to the PDT project, the Formatter plugin for Zend Studio 10.1 need to be changed: replace  \"com." + "excerpt": "Because Zend donated the Zend Studio's Formatter upstream to the PDT project, the Formatter plugin for Zend Studio 10.1 need to be changed: replace  \"com.", + "tl_dr": "" }, { "post_title": "Installing GeoIP extension in Zend Server 5.6 on Windows", @@ -332,7 +364,8 @@ "display_name": "deddu", "github": "" }, - "excerpt": "To test if you have php_geoip extension on your Zend Server, create an php file and copy the following code. This will output true if extension is available or false if not." + "excerpt": "To test if you have php_geoip extension on your Zend Server, create an php file and copy the following code. This will output true if extension is available or false if not.", + "tl_dr": "Test whether php_geoip is already available, and if not, download the correct php_geoip.dll for your PHP build from windows.php.net, copy it into Zend Server's phpext folder, enable it from the Zend Server GUI, and download the MaxMind GeoIP databases." }, { "post_title": "Installing GeoIP extension in Zend Server 6 on Windows", @@ -342,7 +375,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "As an update to the post Installing GeoIP extension in Zend Server 5.6 on Windows , for Zend Server 6." + "excerpt": "As an update to the post Installing GeoIP extension in Zend Server 5.6 on Windows , for Zend Server 6.", + "tl_dr": "As an update to Installing GeoIP extension in Zend Server 5.6 on Windows, here's how to enable php_geoip on Zend Server 6.1." }, { "post_title": "Avoid routing through bootstrap of non existent files", @@ -352,7 +386,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "In some cases you may encounter missing files: images, css or js files. All those missing files are processed by the current bootstrap: index." + "excerpt": "In some cases you may encounter missing files: images, css or js files. All those missing files are processed by the current bootstrap: index.", + "tl_dr": "" }, { "post_title": "Implementing the new Password Hashing API from PHP 5.5 in DotKernel", @@ -362,7 +397,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "In order to use the new Password Hashing functions , introduced in PHP 5.5 , and unify all password related functions , used for both admin and users, we did a major refactor of DotKernel codebase, in version 1." + "excerpt": "In order to use the new Password Hashing functions , introduced in PHP 5.5 , and unify all password related functions , used for both admin and users, we did a major refactor of DotKernel codebase, in version 1.", + "tl_dr": "To use the new Password Hashing functions introduced in PHP 5.5 and unify password-related functions for both admin and users, DotKernel's codebase was refactored in version 1.8.0 (starting from revision 799).\nBecause those functions require PHP 5.5+, the Password Compat library is used for compatibility, and the minimum PHP version to run DotKernel was raised to 5.3.8." }, { "post_title": "Configuring the Cache in DotKernel", @@ -372,7 +408,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This article contains the DotKernel cache layer configuration guide. The DotKernel Caching Layer is based on Zend Framework Cache, more configuration options can be found at the following links: Zend Framework Cache Frontends Zend Framework Cache Backends Main cache settings (Cache Frontend) The main cache settings within the application." + "excerpt": "This article contains the DotKernel cache layer configuration guide. The DotKernel Caching Layer is based on Zend Framework Cache, more configuration options can be found at the following links: Zend Framework Cache Frontends Zend Framework Cache Backends Main cache settings (Cache Frontend) The main cache settings within the application.", + "tl_dr": "DotKernel's caching layer is built on Zend Framework Cache and is configured through cache.* settings in application.ini.\nThe main frontend settings control whether caching is enabled, which cache service to use, the namespace prefix, and how long entries live.\nOptional backend-specific settings (like the file cache directory) are recommended so that separate projects don't accidentally share the same cache." }, { "post_title": "Caching in DotKernel using Zend Framework", @@ -382,7 +419,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "It's very expensive to load configurations and settings from XML files, on every requests. First because of latency of accessing files from hard drive, second because of the XML file parsing burden." + "excerpt": "It's very expensive to load configurations and settings from XML files, on every requests. First because of latency of accessing files from hard drive, second because of the XML file parsing burden.", + "tl_dr": "Loading configuration and settings from XML files on every request is expensive, both due to hard-drive latency and XML parsing overhead.\nDotKernel 1.8 implements a cache layer for router, acl_role, menu, options (including seo_xml), browser_xml, os_xml and test data, with a choice of APC\/APCU or file-based storage." }, { "post_title": "DotKernel Reserved Variable Names for Caching", @@ -392,7 +430,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This article is related to: Caching in DotKernel with Zend Framework Cache The variables that DotKernel cache are below: Router Router is the object that load routes (modules, controllers, actions) settings from router.xml file." + "excerpt": "This article is related to: Caching in DotKernel with Zend Framework Cache The variables that DotKernel cache are below: Router Router is the object that load routes (modules, controllers, actions) settings from router.xml file.", + "tl_dr": "This article is a follow-up to \"Caching in DotKernel Using Zend Framework Cache\" and lists the variables DotKernel caches, along with the exact cache key each one uses." }, { "post_title": "GeoIP City Removed From DotKernel", @@ -402,7 +441,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "In the newest version we have removed the GeoIP City integration. The City database on GeoIP 1." + "excerpt": "In the newest version we have removed the GeoIP City integration. The City database on GeoIP 1.", + "tl_dr": "The newest DotKernel version removed the GeoIP City integration because the City database on GeoIP extension version 1.1.0+ was causing a segmentation fault, crashing requests or outputting an error instead of executing the PHP code.\nUsers on an older DotKernel version combined with GeoIP >=1.1.0 may hit this.\nIf you don't need GeoIP City, the affected code can be removed." }, { "post_title": "How to use Alerts in DotKernel", @@ -412,7 +452,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "Alerts (or Dot_Alert's) are e-mails usually sent to the site developers, these messages are sent with mail() therefore you shouldn't use them to send regular mail. Alerts should only notify you as a developer: \"Hey, something's wrong here, you might want to know this!\" In this article you will find out how to use the Alerts system in DotKernel, we will also go through an existing example so this can be understood easier." + "excerpt": "Alerts (or Dot_Alert's) are e-mails usually sent to the site developers, these messages are sent with mail() therefore you shouldn't use them to send regular mail. Alerts should only notify you as a developer: \"Hey, something's wrong here, you might want to know this!\" In this article you will find out how to use the Alerts system in DotKernel, we will also go through an existing example so this can be understood easier.", + "tl_dr": "Alerts (Dot_Alert's) are e-mails usually sent to site developers using PHP's mail(), meant only to notify a developer that something is wrong — not for regular mail.\nDot_Alert resembles Dot_Email: it has a sender, subject, destination and message, and can be sent.\nThis guide walks through DotKernel's existing example, where an Alert notifies the developer when an e-mail fails to send." }, { "post_title": "DotKernel 1.8.0 LTS Released", @@ -422,7 +463,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "DotKernel 1.8." + "excerpt": "DotKernel 1.8.", + "tl_dr": "DotKernel 1.8.0 (LTS) was released with a new Plugin Architecture, a redesigned and mobile-friendly frontend, APC\/File caching for faster response times, a new Dot_Request class, and multiple security and alerting improvements.\nSome features (WURFL integration, multiple SMTP transporters) were removed from core and made available as plugins instead." }, { "post_title": "DotKernel 1.8.1 + Upgrade from 1.8.0 Released", @@ -432,7 +474,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "DotKernel 1.8." + "excerpt": "DotKernel 1.8.", + "tl_dr": "DotKernel 1.8.1 was released with Enhanced Cache Support, allowing cache tags to be used if the hosting environment supports them.\nA dedicated upgrade package is available for users coming from 1.8.0." }, { "post_title": "Adding Windows 10 OS and Browser detection in DotKernel projects", @@ -442,7 +485,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "Recently we have added the Windows 8, 8.1 and 10 OS icon and Microsoft's Edge browser icon." + "excerpt": "Recently we have added the Windows 8, 8.1 and 10 OS icon and Microsoft's Edge browser icon.", + "tl_dr": "DotKernel added Windows 8, 8.1 and 10 OS icons and a Microsoft Edge browser icon, shown in the User and Admin login icons.\nThis article is the upgrade guide for applying that icon patch." }, { "post_title": "Adding Composer support in your DotKernel project", @@ -452,7 +496,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "Composer is an application-level package manager. Composer auto-loads the dependencies on demand and can also auto-load custom classes ." + "excerpt": "Composer is an application-level package manager. Composer auto-loads the dependencies on demand and can also auto-load custom classes .", + "tl_dr": "Composer is an application-level package manager that auto-loads dependencies (and custom classes) on demand.\nThis article covers the steps needed to add a composer.json file to a DotKernel project, run composer update, and safely require the generated autoloader so the project works whether or not Composer is present." }, { "post_title": "Using DotKernel with Composer Dependencies", @@ -462,7 +507,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This article will cover the external dependency usage VIA composer within DotKernel applications. There is also an article explaining how composer can be added to DotKernel learn more." + "excerpt": "This article will cover the external dependency usage VIA composer within DotKernel applications. There is also an article explaining how composer can be added to DotKernel learn more.", + "tl_dr": "This article covers using external dependencies via Composer within DotKernel applications.\nComposer autoloads dependencies automatically, so there is no need to include\/require them.\nThe example renders a Barcode using Zend Framework 1 (non-namespaced) and Zend Framework 2 (namespaced), and applies to any DotKernel 1.x version running PHP greater than 5.4.0." }, { "post_title": "Migration of Zend Framework 1 PEAR channel", @@ -472,7 +518,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "The unofficial PEAR channel for Zend Framework 1 was hosted on Google Code at this location: ZF Pear, but since the closing of Google Code we were forced to move it. Zend Framework 1 is still used by a lot of  projects in Production, it's still a viable library collection  and it's also  running on  PHP7 ; even if is only in maintenance\/security-patch mode, so it's not an option to cancel it completely." + "excerpt": "The unofficial PEAR channel for Zend Framework 1 was hosted on Google Code at this location: ZF Pear, but since the closing of Google Code we were forced to move it. Zend Framework 1 is still used by a lot of  projects in Production, it's still a viable library collection  and it's also  running on  PHP7 ; even if is only in maintenance\/security-patch mode, so it's not an option to cancel it completely.", + "tl_dr": "The unofficial PEAR channel for Zend Framework 1 was hosted on Google Code, and once Google Code closed, it had to move.\nBecause the repository is over 1 GB, it could not be migrated to GitHub, so a dedicated server was built to host the PEAR channel long-term at pear.dotkernel.com." }, { "post_title": "Disambiguation: DotKernel 1 and DotKernel 3", @@ -482,7 +529,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "What is DotKernel? The name DotKernel symbiotically  combines the string  Dot, as a representation of the Internet, and Kernel, the quintessence of any IT application. In other words Dotkernel wishes to be, with modesty, the central part of the Internet development and hence ensuring increased development productivity and run-time performance." + "excerpt": "What is DotKernel? The name DotKernel symbiotically  combines the string  Dot, as a representation of the Internet, and Kernel, the quintessence of any IT application. In other words Dotkernel wishes to be, with modesty, the central part of the Internet development and hence ensuring increased development productivity and run-time performance.", + "tl_dr": "DotKernel 1 is the original PHP Application Framework built on Zend Framework 1 with an MVC architecture, released in 2010 and now in bugfix-only mode at version 1.8 LTS.\nDotKernel 3 is a newer collection of PSR-7 middleware applications built on the Zend Expressive microframework and Zend Framework 3 components, implementing PSR-1, PSR-2, PSR-4, PSR-7, and PSR-11.\nSince DotKernel 3's release, the unqualified name \"DotKernel\" refers to DotKernel 3, while DotKernel 1 is always referenced explicitly." }, { "post_title": "Templating in DotKernel3", @@ -492,7 +540,8 @@ "display_name": "Jesper", "github": "jesper@apidemia.dk" }, - "excerpt": "DotKernel3 aims to improve the DotKernel stack in every way possible, and one of the painpoints in the previous version of DotKernel was the templating engine. Albeit a solid and robust templating engine, it was also 10 years old, and used techniques that's slightly outdated by now." + "excerpt": "DotKernel3 aims to improve the DotKernel stack in every way possible, and one of the painpoints in the previous version of DotKernel was the templating engine. Albeit a solid and robust templating engine, it was also 10 years old, and used techniques that's slightly outdated by now.", + "tl_dr": "DotKernel3 moved from its previous, 10-year-old templating engine to the popular Twig Templating Engine, gaining layouts, loops, variables, and escaping, while giving developers the familiarity of HTML with the overview and convenience of PHP." }, { "post_title": "Logging with dot-log in Zend Expressive and DotKernel", @@ -502,7 +551,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This article will explain the usage of the dot-log component within DotKernel, Zend Expressive or in a project that uses Zend Service Manager. Since dot-log extends zendframework\/zend-log this tutorial mostly compatible with zend-log as well." + "excerpt": "This article will explain the usage of the dot-log component within DotKernel, Zend Expressive or in a project that uses Zend Service Manager. Since dot-log extends zendframework\/zend-log this tutorial mostly compatible with zend-log as well.", + "tl_dr": "This article explains how to use the dot-log component within DotKernel, Zend Expressive, or any project that uses Zend Service Manager.\nSince dot-log extends zendframework\/zend-log, the tutorial is mostly compatible with zend-log as well.\nSee the zend-log documentation for more detail." }, { "post_title": "Handling and Logging errors with dot-errorhandler and dot-log", @@ -512,7 +562,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This article is a follow-up for: Logging with dot-log in Zend Expressive and DotKernel, the mentioned article is a guide to using dot-log.   This article explains the usage of dotkernel\/dot-errorhandler with dotkernel\/dot-log or zendframework\/zend-log to log errors in Zend Expressive applications." + "excerpt": "This article is a follow-up for: Logging with dot-log in Zend Expressive and DotKernel, the mentioned article is a guide to using dot-log.   This article explains the usage of dotkernel\/dot-errorhandler with dotkernel\/dot-log or zendframework\/zend-log to log errors in Zend Expressive applications.", + "tl_dr": "This article is a follow-up to \"Logging with dot-log in Zend Expressive and DotKernel\" and explains how to use dotkernel\/dot-errorhandler together with dotkernel\/dot-log or zendframework\/zend-log to log errors in Zend Expressive applications.\nIt covers how dot-errorhandler was built, how to configure it, and how it was tested." }, { "post_title": "Adding a CORS implementation to Zend Expressive", @@ -522,7 +573,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This article is a guide on how to add a CORS implementation on an existing DotKernel3 project. The issue If you're facing this message: \"Access to XMLHttpRequest at ‘url’ has been blocked by cors policy." + "excerpt": "This article is a guide on how to add a CORS implementation on an existing DotKernel3 project. The issue If you're facing this message: \"Access to XMLHttpRequest at ‘url’ has been blocked by cors policy.", + "tl_dr": "When a client-side request is blocked with a \"No 'Access-Control-Allow-Origin' header\" error, it's because the server isn't sending the header that allows a browser to access its data (most common when fetching JSON to process with JavaScript).\nThis guide adds CORS support to a Zend Expressive \/ DotKernel3 project using Tuupola's Cors Middleware package." }, { "post_title": "DotKernel Coding Standard", @@ -532,7 +584,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "DotKernel will be a \"skeleton\"of Zend Framework. DotKernel borrowed the coding standard from Zend Framework: ZF Coding Standard with some exceptions." + "excerpt": "DotKernel will be a \"skeleton\"of Zend Framework. DotKernel borrowed the coding standard from Zend Framework: ZF Coding Standard with some exceptions.", + "tl_dr": "DotKernel is a \"skeleton\" of Zend Framework and borrows its coding standard from the ZF Coding Standard, with a small number of exceptions covering indentation, naming conventions, and brace placement." }, { "post_title": "DotBoost Technologies : Products and Services North American Relaunch", @@ -542,7 +595,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "A new style and advanced approach to accompany the Dotkernel source release Dotboost is pleased to announce our North American Relaunch. This new phase comes as a result of dedicated research and analysis on how to best serve clients in Canada and the US." + "excerpt": "A new style and advanced approach to accompany the Dotkernel source release Dotboost is pleased to announce our North American Relaunch. This new phase comes as a result of dedicated research and analysis on how to best serve clients in Canada and the US.", + "tl_dr": "Dotboost announces its North American relaunch, aimed at better serving clients in Canada and the US.\nThe relaunch centers on the source release of its in-house DotKernel framework, along with expanded business IT integration and clearer consulting services.\nFounded in 2005, Dotboost describes itself as treating clients as strategic partners rather than as a typical IT vendor." }, { "post_title": "DotKernel version 1.0 in action", @@ -552,7 +606,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "DotKernel is the DotBoost's in-house developed framework, based on Zend Framework. DotKernel is at version 1." + "excerpt": "DotKernel is the DotBoost's in-house developed framework, based on Zend Framework. DotKernel is at version 1.", + "tl_dr": "DotKernel is DotBoost's in-house developed framework, built on top of Zend Framework and released under the Open Software License (OSL 3.0).\nIt uses a simplified MVC architecture, easy to learn for beginner and intermediate programmers, by eliminating much of Zend Framework's complexity through a different approach to handling web requests.\nIt relies on only a handful of Zend Framework classes." }, { "post_title": "DotKernel Template Engine", @@ -562,7 +617,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "DotKernel Template Engine is an implementation of PHPLib Template engine for PHP5. It has an amazing ability to separate the application code from the presentation layer." + "excerpt": "DotKernel Template Engine is an implementation of PHPLib Template engine for PHP5. It has an amazing ability to separate the application code from the presentation layer.", + "tl_dr": "" }, { "post_title": "How to group log files by date using dot-log", @@ -572,7 +628,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "As described in this article, dot-log is a powerful tool for logging messages in your application. It's power stays in the fact that it can be implemented in a few easy steps and that it's highly customizable." + "excerpt": "As described in this article, dot-log is a powerful tool for logging messages in your application. It's power stays in the fact that it can be implemented in a few easy steps and that it's highly customizable.", + "tl_dr": "dot-log is a powerful, easily customizable logging tool.\nVersion 3.1.1 adds the ability to use datetime formatter strings right in the stream option of a log writer, and fixes an issue where caching dot-log configs caused logs to be written to a single file instead of being grouped by date." }, { "post_title": "Autologin using Cookie \/ Remember Me in Dotkernel", @@ -582,7 +639,8 @@ "display_name": "SergiuB", "github": "" }, - "excerpt": "Autologin using Cookie \/ Remember Me in Dotkernel This feature is used to automatically log the user who chooses this by checking the remember me box. Implemented in Dotkernel Frontend starting from Release 3." + "excerpt": "Autologin using Cookie \/ Remember Me in Dotkernel This feature is used to automatically log the user who chooses this by checking the remember me box. Implemented in Dotkernel Frontend starting from Release 3.", + "tl_dr": "This feature automatically logs in a user who checks the \"remember me\" box at login.\nIt has been implemented in Dotkernel Frontend starting from Release 3.3.0, and requires changes across the login form, a new entity\/migration, a new middleware, config, and the user service\/repository\/controller." }, { "post_title": "Doctrine cache using symfony\/cache", @@ -592,7 +650,8 @@ "display_name": "MarioRadu", "github": "marioradu05" }, - "excerpt": "When it comes to web development, performance is one of the critical elements that influence the success of an application. Developers focus on improving response times and overall speed to enhance the user experience." + "excerpt": "When it comes to web development, performance is one of the critical elements that influence the success of an application. Developers focus on improving response times and overall speed to enhance the user experience.", + "tl_dr": "Caching stores data the first time it's requested so that later requests can be served from the cache instead of the original, slower source, which improves response times.\nThis article, a follow-up to an earlier caching article, shows how to enable the dot-cache component, a wrapper around symfony\/cache, in DotKernel Admin.\nIt covers the array and filesystem storage adapters, configuring Doctrine's four cache types (result, metadata, query, hydration), and marking entities and queries as cacheable." }, { "post_title": "Dependency Injection made easy in Laminas\/Mezzio applications", @@ -602,7 +661,8 @@ "display_name": "Claudiu Pintiuta", "github": "claudiu@rospace.com" }, - "excerpt": "Note: The package requires Doctrine ORM. Still, it can be used in applications which do not integrate Doctrine." + "excerpt": "Note: The package requires Doctrine ORM. Still, it can be used in applications which do not integrate Doctrine.", + "tl_dr": "DotKernel's dot-dependency-injection package autowires constructor dependencies in Laminas\/Mezzio (and other PSR-11) applications, removing the need to write and maintain a custom factory class for every service.\nInstead of a bespoke factory, you add an attribute to the class constructor and register a single shared AttributedServiceFactory in your ConfigProvider.\nThe package requires Doctrine ORM but can still be used in applications that don't integrate Doctrine, and it also supports injecting Doctrine repositories directly instead of fetching them from the EntityManager." }, { "post_title": "Dotkernel Light - Starting with Mezzio microframework and Laminas components", @@ -612,7 +672,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Dotkernel Light is a version of Dotkernel Frontend that includes only the bare-bones essentials. Though simpler, it's perfect for: A presentation site, An introduction into the Mezzio microframework architecture, A starting point for a more complex project where you have full control over functionality." + "excerpt": "Dotkernel Light is a version of Dotkernel Frontend that includes only the bare-bones essentials. Though simpler, it's perfect for: A presentation site, An introduction into the Mezzio microframework architecture, A starting point for a more complex project where you have full control over functionality.", + "tl_dr": "Dotkernel Light is a version of Dotkernel Frontend that includes only the bare-bones essentials.\nIt's built on the Mezzio microframework using Laminas components, and is designed as a presentation site, a fast-start introduction to Mezzio, or a clean starting point for a project where you want full control over functionality." }, { "post_title": "Dotkernel Light: the best choice for your presentation site", @@ -622,7 +683,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Dotkernel Light is a good starting point for a project if you want to have full control over the functionality it contains. It easily grows into something more complex with the integration of packages based on your requirements." + "excerpt": "Dotkernel Light is a good starting point for a project if you want to have full control over the functionality it contains. It easily grows into something more complex with the integration of packages based on your requirements.", + "tl_dr": "Dotkernel Light is a lightweight starting point for a project when you want full control over its functionality, and it grows into something more complex as you add packages.\nIt comes with routing, templating, error handling, and tests\/code quality checks out of the box, but strips out everything a presentation site doesn't need — database, sessions\/cookies\/flash messages, auth, dependency injection, mail, navigation, CORS, forms, the user\/contact\/plugin modules." }, { "post_title": "Doctrine enum implementation in Dotkernel", @@ -632,7 +694,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "The update of doctrine\/orm to version 3.2." + "excerpt": "The update of doctrine\/orm to version 3.2.", + "tl_dr": "Doctrine ORM 3.2.0 added EnumType columns, building on the enum type introduced in PHP 8.1, and Dotkernel now implements this on both the PHP and database sides.\nThe article contrasts Dotkernel's old string-based flag columns (like User->Status) with a new setup that uses custom PHP enums paired with a DBAL type extending AbstractEnumType.\nThe new approach creates an explicit, enforced link between the PHP code and the database column values, at the cost of needing to update both sides whenever the value set changes." }, { "post_title": "Replacing laminas-mail with Symfony mailer in dot-mail", @@ -642,7 +705,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "What prompted the change According to the discussion from the LaminasTechnical steering Committee of 2023-12-04, it was decided that the laminas\/laminas-mail package would be abandoned. On the one hand, there is nobody to maintain the package and on the other, there are several alternatives available in the ecosystem: ddeboer\/imap for interacting with IMAP zbateson\/mail-mime-parser for parsing MIME messages symfony\/mailer for sending mail How Dotkernel handles the issue The Dotkernel team has also opted to replace the laminas\/laminas-mail package in the dotkernel\/dot-mail package." + "excerpt": "What prompted the change According to the discussion from the LaminasTechnical steering Committee of 2023-12-04, it was decided that the laminas\/laminas-mail package would be abandoned. On the one hand, there is nobody to maintain the package and on the other, there are several alternatives available in the ecosystem: ddeboer\/imap for interacting with IMAP zbateson\/mail-mime-parser for parsing MIME messages symfony\/mailer for sending mail How Dotkernel handles the issue The Dotkernel team has also opted to replace the laminas\/laminas-mail package in the dotkernel\/dot-mail package.", + "tl_dr": "The Laminas Technical Steering Committee decided on 2023-12-04 to abandon laminas\/laminas-mail.\nDotkernel responded by replacing it with symfony\/mailer inside the dotkernel\/dot-mail package (version 5), aiming for minimal impact on existing projects — calls to send mail stay the same, though mime and imap related functionality is removed." } ] }, @@ -659,7 +723,8 @@ "display_name": "Adrian", "github": "" }, - "excerpt": "Starting with the 1.5 release, DotKernel will make the switch from Dojo to jQuery." + "excerpt": "Starting with the 1.5 release, DotKernel will make the switch from Dojo to jQuery.", + "tl_dr": "Starting with DotKernel's 1.5 release, the framework switched from Dojo to jQuery, and this post is a quick primer on jQuery basics.\nIt covers the jQuery ($) object and CSS-style selectors, chaining methods to manipulate matched elements, binding events like click, and making Ajax calls with $.get() and $.getJSON()." }, { "post_title": "Codelobster PHP Edition - Free PHP, HTML, CSS, JavaScript editor (IDE)", @@ -669,7 +734,8 @@ "display_name": "Stas", "github": "stas@codelobster.com" }, - "excerpt": "Free PHP, HTML, CSS, JavaScript editor (IDE) - Codelobster PHP Edition For valuable work on creation of sites you need a good comfortable editor necessarily. There are many requiring paid products for this purpose, but we would like to select free of charge very functional and at the same time of simple in the use editor - Codelobster PHP Edition ." + "excerpt": "Free PHP, HTML, CSS, JavaScript editor (IDE) - Codelobster PHP Edition For valuable work on creation of sites you need a good comfortable editor necessarily. There are many requiring paid products for this purpose, but we would like to select free of charge very functional and at the same time of simple in the use editor - Codelobster PHP Edition .", + "tl_dr": "Codelobster PHP Edition is a free, lightweight IDE that highlights and autocompletes mixed PHP, HTML, CSS, and JavaScript code, including HTML5 and CSS3.\nIt also bundles an HTML\/CSS inspector, a PHP debugger, an SQL manager, FTP support, and a portable mode that needs no installation.\nOn top of that, it ships plugins for popular CMS platforms and PHP frameworks such as Drupal, Joomla, CakePHP, CodeIgniter, Symfony, Yii, WordPress, and Smarty." }, { "post_title": "Javascript: Email Validator", @@ -679,7 +745,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Problem: email should allow +\/- characters in user, - in domain. dash (-) should be allowed anywhere in an email address or domain." + "excerpt": "Problem: email should allow +\/- characters in user, - in domain. dash (-) should be allowed anywhere in an email address or domain.", + "tl_dr": "" } ] }, @@ -696,7 +763,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "GPL versions of WURFL PHP API libraries are ready to be downloaded from here . Version 1." + "excerpt": "GPL versions of WURFL PHP API libraries are ready to be downloaded from here . Version 1.", + "tl_dr": "GPL versions of the WURFL PHP API libraries were made available: version 1.1, the one integrated into Zend Framework's Zend_Http_UserAgent component, and version 1.2.1, the latest released under the GPL license.\nA later edit notes the download was removed because an AGPL version is available (which readers need to get themselves), and as a favor to Luca Passani." }, { "post_title": "Zend Framework 1.12.4 Released with Security Fixes", @@ -706,7 +774,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Matthew Weier O'Phinney just announced the release of ZF 1.12." + "excerpt": "Matthew Weier O'Phinney just announced the release of ZF 1.12.", + "tl_dr": "Matthew Weier O'Phinney announced the release of Zend Framework 1.12.4, along with 2.1.6 and 2.2.6, all containing security updates, and the ZF PEAR channel was updated to the latest 1.12.4 release.\nA March 7, 2014 edit notes that Zend Framework 1.12.5 was subsequently released to fix a backward compatibility issue introduced in the 1.12.4 release." }, { "post_title": "Zend_Mail and Zend_Http Security Fixes in Zend Framework 1.12.12", @@ -716,7 +785,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "The release of ZF 1.12." + "excerpt": "The release of ZF 1.12.", + "tl_dr": "Zend Framework 1.12.12 was released with security fixes for the Zend_Mail and Zend_Http components.\nConsumers of these components, including DotKernel which relies heavily on Zend_Mail, were strongly urged to upgrade immediately via PEAR or by applying the patch directly.\nA follow-up release, 1.12.13, was issued shortly after to fix a regression introduced in 1.12.12." }, { "post_title": "Zend Framework 1 End-of-Life", @@ -726,7 +796,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "EOL ( End-of-Life)  term was just announced. Only up until Sept." + "excerpt": "EOL ( End-of-Life)  term was just announced. Only up until Sept.", + "tl_dr": "Zend Framework 1 has officially entered End-of-Life (EOL) status now that Zend Framework 3 has been released.\nSecurity updates for Zend Framework 1 continued only until 28 September 2016, three months after the announcement." }, { "post_title": "Zend Framework as PEAR accessible repository on Plesk server", @@ -736,7 +807,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Why we want to install ZF as PEAR ? Because is too boring and time consuming to move all ZF files up and down for each script you want to install , there are a lot of files. Also that way we can forget about the need to update ZF at latest versions, and keep tracks of which version and on which server we have ZF." + "excerpt": "Why we want to install ZF as PEAR ? Because is too boring and time consuming to move all ZF files up and down for each script you want to install , there are a lot of files. Also that way we can forget about the need to update ZF at latest versions, and keep tracks of which version and on which server we have ZF.", + "tl_dr": "Rather than copying all of Zend Framework's many files into every project, this article shows how to install ZF as a PEAR-accessible repository on a Plesk server.\nThis makes it easier to track which ZF version is installed on which server and avoids manually updating each project, though it can introduce backward compatibility concerns in future ZF releases." }, { "post_title": "Zend Framework 1.7.0 Released", @@ -746,7 +818,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "At first glance , the biggest news is AMF support: Adobe's Action Message Format protocol to your PHP 5 application Download latest  ZF" + "excerpt": "At first glance , the biggest news is AMF support: Adobe's Action Message Format protocol to your PHP 5 application Download latest  ZF", + "tl_dr": "Zend Framework 1.7.0 has been released, and its headline feature is support for Adobe's Action Message Format (AMF) protocol in PHP 5 applications.\nThe release is available directly from the official Zend Framework download page." }, { "post_title": "Scienta ZF Debug Bar: A very helpfull ZF debug tool", @@ -756,7 +829,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Just found today a very interesting and helpful debug tool: Scienta We at DotKernel used some very basic debug bar:  queries, time spent , memory used.  But this Scienta is way more complex and nicer then our internal code,  so we switch to it and integrate it in DotKernel code base." + "excerpt": "Just found today a very interesting and helpful debug tool: Scienta We at DotKernel used some very basic debug bar:  queries, time spent , memory used.  But this Scienta is way more complex and nicer then our internal code,  so we switch to it and integrate it in DotKernel code base.", + "tl_dr": "The author came across the Scienta ZF Debug Bar, a debugging tool for Zend Framework applications.\nDotKernel had been relying on its own basic debug bar, which only showed queries, time spent, and memory used.\nFinding Scienta far more complex and polished than their internal tool, the DotKernel team decided to switch to it and integrate it into the DotKernel code base." }, { "post_title": "Sunsetting PEAR Channel for Zend Framework 1", @@ -766,7 +840,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Sunsetting PEAR Channel for Zend Framework 1 The unofficial PEAR channel for Zend Framework 1 was created in 2016 , at the time when PEAR was still used a lot. Due to the fact that is a pain to upgrade PEAR to work with PHP 8 , we must sunset the channel ." + "excerpt": "Sunsetting PEAR Channel for Zend Framework 1 The unofficial PEAR channel for Zend Framework 1 was created in 2016 , at the time when PEAR was still used a lot. Due to the fact that is a pain to upgrade PEAR to work with PHP 8 , we must sunset the channel .", + "tl_dr": "DotKernel is sunsetting its unofficial PEAR channel for Zend Framework 1, which was created in 2016 when PEAR was still widely used.\nThe main reasons are that upgrading PEAR to work with PHP 8 is too painful, and the channel currently runs on an LXC container with CentOS 7, which doesn't work on the latest Proxmox version, making the upgrade to AlmaLinux not worth the effort.\nThe post closes by thanking PEAR for its historical contribution to the PHP ecosystem." } ] }, @@ -783,7 +858,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Aptana 3.0 is in beta stage, can be downloaded from the official site ." + "excerpt": "Aptana 3.0 is in beta stage, can be downloaded from the official site .", + "tl_dr": "Aptana 3.0, then in beta, was set to bring PHP support back - and this time it would be built directly into the Studio 3 core rather than shipped as a separate plugin.\nA PHP debugger was also announced, to arrive as a separate set of plugins a few weeks later." }, { "post_title": "Protection against SQL Injection using PDO and Zend Framework", @@ -793,7 +869,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "SQL injection is a technique that exploits a security vulnerability occurring in the database layer of an application. Usually, user input is not filtered by the script and is passed into a SQL statement." + "excerpt": "SQL injection is a technique that exploits a security vulnerability occurring in the database layer of an application. Usually, user input is not filtered by the script and is passed into a SQL statement.", + "tl_dr": "SQL injection exploits unfiltered user input passed into SQL statements.\nPDO (PHP Data Objects) is a standardized database access layer that provides a data-access abstraction (not a database abstraction) and offers several benefits, including help protecting against SQL injection.\nIn Zend Framework, prepared statements are encouraged since they handle parameter escaping, but they are not a complete guarantee against SQL injection - especially with PDO_MySQL, and with WHERE IN \/ ORDER BY clauses." }, { "post_title": "Protection against SQL Injection using PDO and Zend Framework - part 2", @@ -803,7 +880,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "Following the preview article about SQL Injection, here is more - a strong argument why you should use Zend Framework for handling database access. Zend_Db is the primary class used for access the database, but there is more: Zend_Db_Statement, Zend_Db_Select and Zend_Db_Tables." + "excerpt": "Following the preview article about SQL Injection, here is more - a strong argument why you should use Zend Framework for handling database access. Zend_Db is the primary class used for access the database, but there is more: Zend_Db_Statement, Zend_Db_Select and Zend_Db_Tables.", + "tl_dr": "Following up on the earlier SQL Injection article, this part digs into the specific methods of Zend_Db (and related classes Zend_Db_Statement, Zend_Db_Select, Zend_Db_Tables) to show exactly when their use of prepared statements does, and does not, protect against SQL injection - and offers a quick type-casting tip for WHERE clauses." }, { "post_title": "End of Support for PHP 5.2.x Branch", @@ -813,7 +891,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "PHP 5.2." + "excerpt": "PHP 5.2.", + "tl_dr": "PHP 5.2.14 was just released, marking the end of active support for the PHP 5.2.x branch.\nPHP 5.3.3 was released at the same time, and projects and servers are encouraged to upgrade to the 5.3.x branch." }, { "post_title": "PHP Environment : Development Staging Production", @@ -823,7 +902,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "In hosted software development, the environment refers to a server tier designated to a specific stage in a release process. The purpose of these environments is to improve the development, testing and release processes in client-server applications." + "excerpt": "In hosted software development, the environment refers to a server tier designated to a specific stage in a release process. The purpose of these environments is to improve the development, testing and release processes in client-server applications.", + "tl_dr": "In hosted software development, an environment is a server tier designated to a specific stage of a release process.\nThe three most common environments are Development, Staging and Production, and applications are typically moved between them using Subversion source control." }, { "post_title": "Using Aptana to connect to DotKernel tracker (Mantis)", @@ -833,7 +913,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "In developing DotKernel application framework, we needed a tracking system. DotKernel Tracker is the place where the bugs are reported, new features are announced and other general tickets are added." + "excerpt": "In developing DotKernel application framework, we needed a tracking system. DotKernel Tracker is the place where the bugs are reported, new features are announced and other general tickets are added.", + "tl_dr": "This guide explains how to connect the Aptana IDE to DotKernel Tracker, the Mantis-based bug tracker used for the DotKernel application framework, via the Mylyn plugin's Mantis connector.\nIt walks through installing Aptana and Mylyn, adding DotKernel Tracker as a task repository, and validating the connection so tickets can be managed directly from the IDE." }, { "post_title": "How To Upgrade Wamp to PHP 5.3.4", @@ -843,7 +924,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "1.    Stop WAMP server." + "excerpt": "1.    Stop WAMP server.", + "tl_dr": "A step-by-step guide to manually upgrading the PHP version used by a WAMP server to PHP 5.3.4, by downloading the VC6 Thread Safe build, copying over configuration files, and switching the active PHP version in WAMP." }, { "post_title": "PHP 5.3.6 released. No upgrade possible for WampServer.", @@ -853,7 +935,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "PHP 5.3." + "excerpt": "PHP 5.3.", + "tl_dr": "" }, { "post_title": "Zend Server 5.5 Quick Setup on Windows", @@ -863,7 +946,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "In order to make  usable a fresh installation of Zend Server 5.5." + "excerpt": "In order to make  usable a fresh installation of Zend Server 5.5.", + "tl_dr": "A fresh Zend Server 5.5.0 install on Windows 7 needs a few quick tweaks before it's ready for development: enabling mod_rewrite in Apache, adjusting a handful of PHP directives, and fixing APC so it actually works even though it's shown as enabled." }, { "post_title": "Remote connections to MySQL server on Plesk based servers", @@ -873,7 +957,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "By default, MySql servers on Linux machines where Plesk is installed, have the old_passwords=1 or ON flag. That mean even if you have MySQL 5." + "excerpt": "By default, MySql servers on Linux machines where Plesk is installed, have the old_passwords=1 or ON flag. That mean even if you have MySQL 5.", + "tl_dr": "Plesk-based Linux servers default to old_passwords=1, which forces MySQL to use the old, pre-4.1 password storage style even on MySQL 5.5+, breaking remote PDO connections.\nThe fix is to create a new database user, grant it privileges, disable old_passwords, and reset its password so a newer, longer password hash is stored in mysql.user." }, { "post_title": "Version Control Ignore Patterns in Zend Studio", @@ -883,7 +968,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "In order to globally manage the \"Ignored Resources\"  patterns in Zend Studio, for all projects , instead of manually add to each project, you can do the following: 1. Go to Window-> Preferences 2." + "excerpt": "In order to globally manage the \"Ignored Resources\"  patterns in Zend Studio, for all projects , instead of manually add to each project, you can do the following: 1. Go to Window-> Preferences 2.", + "tl_dr": "Zend Studio lets you manage \"Ignored Resources\" patterns globally, under Window -> Preferences -> Team -> Ignored Resources, instead of configuring them separately for each project.\nThis is especially handy when a workspace mixes Git and SVN projects, though any given project can still opt to use its own specific patterns instead of the global ones." }, { "post_title": "Welcome to the 10th Zend Certified Engineer in Dotboost Team", @@ -893,7 +979,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Today is a major milestone for our Dotboost Technologies Inc. Company." + "excerpt": "Today is a major milestone for our Dotboost Technologies Inc. Company.", + "tl_dr": "Dotboost Technologies Inc. announces that the 10th member of its team has passed the Zend Certified Engineer exam, part of its commitment to top-level PHP development and quality assurance for clients.\nNext up: adopting Zend Framework 2 best practices, pursuing the Zend Framework 2 Certified Architect exam, and, starting in 2014, making Zend Certification mandatory for every developer on the team." }, { "post_title": "Better Unicode Support in MySQL 5.5 UTF8MB4", @@ -903,7 +990,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Beginning with version 5.5 of MySQL , utf8mb4 character set was introduced, in order to better support Unicode." + "excerpt": "Beginning with version 5.5 of MySQL , utf8mb4 character set was introduced, in order to better support Unicode.", + "tl_dr": "MySQL 5.5 introduced the utf8mb4 character set for fuller Unicode support, and DotKernel's sample dk.sql file was updated to use it.\nSwitching to utf8mb4 means VARCHAR(255) columns can hit MySQL's 767-byte max key length error, so VARCHAR(150) is used instead, and the connection charset must be updated in both the application config and my.cnf." }, { "post_title": "Using PHP 7 Express in Zend Studio 13", @@ -913,7 +1001,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This article will cover the steps needed in order to check the PHP7 compatibility, a small troubleshooter. This article will also contain a compatibility issue check on the latest Zend Framework 1 version." + "excerpt": "This article will cover the steps needed in order to check the PHP7 compatibility, a small troubleshooter. This article will also contain a compatibility issue check on the latest Zend Framework 1 version.", + "tl_dr": "Zend Studio 13 introduces PHP 7 Express, a feature that checks whether pre-PHP7 code will run cleanly on a PHP7 server.\nThis article walks through setting up a test project with the correct PHP version, verifying the PHP Interpreter setting, adding Zend Framework 1 to the project, and running PHP 7 Express to surface compatibility issues." }, { "post_title": "Floating-Point Arithmetic - Why is (int)((0.7+0.1)*10) = 7 ?", @@ -923,7 +1012,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This article applies to PHP 5.x but also to PHP 7 While using floating-point arithmetic you might have noticed that not all the calculus results are as expected, this can usually be observed when casting values." + "excerpt": "This article applies to PHP 5.x but also to PHP 7 While using floating-point arithmetic you might have noticed that not all the calculus results are as expected, this can usually be observed when casting values.", + "tl_dr": "This applies to PHP 5.x and PHP 7.\nFloating-point arithmetic doesn't always produce the results you'd expect, especially when casting values to int, because numbers like 0.7 and 0.1 cannot be represented exactly in binary.\nThe result is that (int)((0.7+0.1)*10) evaluates to 7 instead of the mathematically expected 8." }, { "post_title": "Aptana PHP installation in Aptana 2.x", @@ -933,7 +1023,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "As all aptana fans know, Aptana PHP plugin was discontinued in Aptana 2.x, in favor of PDT." + "excerpt": "As all aptana fans know, Aptana PHP plugin was discontinued in Aptana 2.x, in favor of PDT.", + "tl_dr": "Aptana discontinued its bundled Aptana PHP plugin in Aptana 2.x in favor of PDT, but PDT is missing major features needed for professional PHP development.\nThis article shows how to manually reinstall the Aptana PHP plugin through Aptana's update site, and how to add SVN support via Subclipse if it isn't already installed." }, { "post_title": "Database seeding: Doctrine data fixtures vs Phinx", @@ -943,7 +1034,8 @@ "display_name": "MarioRadu", "github": "marioradu05" }, - "excerpt": "Database seeding: Doctrine data fixtures vs Phinx Seeding the database means populating the database with initial values, it's commonly used for seeding the user roles and user accounts. Seeding the database the right way is no easy feat, and we will see why." + "excerpt": "Database seeding: Doctrine data fixtures vs Phinx Seeding the database means populating the database with initial values, it's commonly used for seeding the user roles and user accounts. Seeding the database the right way is no easy feat, and we will see why.", + "tl_dr": "DotKernel 3 previously used cakephp\/phinx for seeding the database, but the team wanted more flexibility and switched to doctrine\/data-fixtures since Doctrine is already the ORM in use.\nBecause doctrine\/data-fixtures has no CLI interface, DotKernel built the dotkernel\/dot-data-fixtures package to add one, and this article covers installing it, creating and executing fixtures, and ordering them by explicit order or by declared dependencies." }, { "post_title": "Mezzio app development in WSL2", @@ -953,7 +1045,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "Install a Mezzio app (DotKernel API) using WSL2 This article will run you through the steps of installing a Mezzio application (DotKernel API) in WSL2 and run it on Ubuntu 20.04 LTS." + "excerpt": "Install a Mezzio app (DotKernel API) using WSL2 This article will run you through the steps of installing a Mezzio application (DotKernel API) in WSL2 and run it on Ubuntu 20.04 LTS.", + "tl_dr": "This article runs through the steps of installing a Mezzio application (DotKernel API) in WSL2 and running it on Ubuntu 20.04 LTS, from installing WSL2 itself to configuring PHPStorm to work with the WSL2 file system." }, { "post_title": "AlmaLinux 9 in WSL2 : install PHP, Apache, MariaDB, Composer, PhpMyadmin", @@ -963,7 +1056,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "In this article we will demonstrate how we install AlmaLinux 9 using Windows Subsystem for Linux (WSL2). First, you need to check if your machine is ready for using WSL2." + "excerpt": "In this article we will demonstrate how we install AlmaLinux 9 using Windows Subsystem for Linux (WSL2). First, you need to check if your machine is ready for using WSL2.", + "tl_dr": "This guide shows how to install AlmaLinux 9 through Windows Subsystem for Linux (WSL2) and provision it with an Ansible-driven installer script that sets up PHP, Apache, MariaDB, Composer, and phpMyAdmin.\nIt covers verifying WSL2 readiness, installing the AlmaLinux 9 distribution from the Microsoft Store, running the two-step Ansible installer (with a required restart in between), and confirming the setup through Apache's homepage, a PHP info page, and phpMyAdmin." }, { "post_title": "Static Analysis - Replacing Psalm with PHPStan", @@ -973,7 +1067,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "What is Static Analysis Static analysis (static code analysis or source code analysis) applies a set of coding rules to debug source code before a program is run. Applied in the early phase of code development, the goals of static analysis are: Catch and fix errors like type-related errors which can occur especially in dynamically-typed programming languages like PHP." + "excerpt": "What is Static Analysis Static analysis (static code analysis or source code analysis) applies a set of coding rules to debug source code before a program is run. Applied in the early phase of code development, the goals of static analysis are: Catch and fix errors like type-related errors which can occur especially in dynamically-typed programming languages like PHP.", + "tl_dr": "Dotkernel is replacing Psalm with PHPStan for static analysis, following a broader PHP community shift (including projects like Doctrine and Composer) toward PHPStan's faster-growing ecosystem, full-time maintainer, PHPStorm-based stubs, and stronger detection.\nThis article explains what static analysis is, why the switch makes sense, and walks through updating composer.json, the CI workflow, and the phpstan.neon configuration to run PHPStan checks in place of Psalm." } ] }, @@ -990,7 +1085,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "What is PSR-7 and how to use itPSR-7 is a set of common interfaces defined by PHP Framework Interop Group. These interfaces are representing HTTP messages, and URIs for use when communicating trough HTTP." + "excerpt": "What is PSR-7 and how to use itPSR-7 is a set of common interfaces defined by PHP Framework Interop Group. These interfaces are representing HTTP messages, and URIs for use when communicating trough HTTP.", + "tl_dr": "PSR-7 defines a set of common interfaces from the PHP Framework Interop Group for representing HTTP messages and URIs, and any application built on those interfaces is a PSR-7 application.\nThis article lists the PSR-7 interfaces as a cheatsheet, then walks through practical examples using Zend Diactoros: adding, appending, reading, and removing HTTP headers, and reading, writing, appending, and prepending content to a PSR-7 message body via its stream interface." }, { "post_title": "Database migrations and how to use them", @@ -1000,7 +1096,8 @@ "display_name": "Jesper", "github": "jesper@apidemia.dk" }, - "excerpt": "Migrations, the superhero your database deserves Migrations ease the process of working together on projects, as well as deploying the database changes.   A newly released package for the DotKernel stack integrates migrations and seeders into the application; This is all done via the newly introduced \"php dot\" command that's available in the DotKernel stack." + "excerpt": "Migrations, the superhero your database deserves Migrations ease the process of working together on projects, as well as deploying the database changes.   A newly released package for the DotKernel stack integrates migrations and seeders into the application; This is all done via the newly introduced \"php dot\" command that's available in the DotKernel stack.", + "tl_dr": "Database migrations track schema changes so teams can collaborate without ad hoc, convoluted database change messages and can keep column types consistent across the team.\nA package for the DotKernel stack adds migrations and seeders to the application via a new php dot command.\nThe article walks through adopting migrations in an existing project, running and naming them, and explains how seeders differ from migrations by adding data rather than changing schema." }, { "post_title": "Using the URLGenerator work in FastRoute", @@ -1010,7 +1107,8 @@ "display_name": "Jesper", "github": "jesper@apidemia.dk" }, - "excerpt": "DotKernel 3 uses FastRoute under the hood, which is an excellent and fast routing package, but it does have some quirks. A wrong setup can lead to many headaches, as it's not prominent that the error you're experiencing is from FastRoute, and you may not know where exactly to look for the cause." + "excerpt": "DotKernel 3 uses FastRoute under the hood, which is an excellent and fast routing package, but it does have some quirks. A wrong setup can lead to many headaches, as it's not prominent that the error you're experiencing is from FastRoute, and you may not know where exactly to look for the cause.", + "tl_dr": "DotKernel 3 uses FastRoute under the hood, which is fast but has a quirk around slash-suffixes that a wrong route setup can trigger, leading to hard-to-diagnose errors.\nOptional slashes must be kept inside the optional block of a route definition, or the URLGenerator ends up producing the wrong route.\nEvery named route can then be referenced instead of hard-coded, using $this->url() in controllers or the path() function in Twig views." }, { "post_title": "Migrating DotKernel 3 from Zend Expressive 2 to Zend Expressive 3", @@ -1020,7 +1118,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This article covers the steps required to migrate a DotKernel 3 instance to the latest Zend Expressive Version. Migration from Zend Expressive 2 to 3." + "excerpt": "This article covers the steps required to migrate a DotKernel 3 instance to the latest Zend Expressive Version. Migration from Zend Expressive 2 to 3.", + "tl_dr": "This guide covers migrating a DotKernel 3 instance from Zend Expressive 2 to Zend Expressive 3, for projects that only contain controller-based middleware.\nOld middleware must first be refactored to the psr\/http-server-middleware interfaces, since Delegates become RequestHandlers.\nThe steps then cover updating composer.json dependencies, registering new ConfigProviders, wrapping routes.php and pipeline.php in callables, and replacing the old pipeRoutingMiddleware()\/pipeDispatchMiddleware() calls with their PSR-15 equivalents." }, { "post_title": "Doctrine Cache in Mezzio and Dotkernel", @@ -1030,7 +1129,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Doctrine caching in DotKernel Following version 2 of doctrine\/cache, in 2024 we published an update to this article here: https:\/\/www.dotkernel." + "excerpt": "Doctrine caching in DotKernel Following version 2 of doctrine\/cache, in 2024 we published an update to this article here: https:\/\/www.dotkernel.", + "tl_dr": "Running Doctrine ORM in production without any caching strategy wastes CPU cycles regenerating metadata and queries on every request.\nThis article configures Doctrine's metadata_cache, query_cache, and result_cache through psr\/container, using PhpFileCache and a default result cache lifetime of 3600 seconds.\nIt walks through enabling these caches both directly on a query and on a Doctrine Paginator-based collection, with real examples from Dotkernel Admin.\nNote: a 2024 follow-up article covers the same topic using Symfony Cache instead." }, { "post_title": "CORS policy setup in Dotkernel using mezzio-cors", @@ -1040,7 +1140,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "CORS policy setup in Dotkernel using mezzio-cors Error message Access to fetch at RESOURCE_URL from origin ORIGIN_URL has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Most developers have encountered this error when interacting with APIs." + "excerpt": "CORS policy setup in Dotkernel using mezzio-cors Error message Access to fetch at RESOURCE_URL from origin ORIGIN_URL has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Most developers have encountered this error when interacting with APIs.", + "tl_dr": "This article explains how to fix the common \"No 'Access-Control-Allow-Origin' header is present\" browser error by installing and configuring the mezzio-cors package.\nIt covers registering the package's ConfigProvider and middleware, then creating a CORS configuration file.\nThe configuration supports a permissive mode, where any origin is allowed, and a restrictive mode, where only specific listed origins are allowed.\nIt also shows how to verify each mode is working correctly." }, { "post_title": "Replacing dot-console with dot-cli based on laminas-cli", @@ -1050,7 +1151,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "Replacing dot-console with dot-cli based on laminas-cli Implementing dot-cli in your application DotKernel's dot-cli package comes as a replacement for dot-console, which was abandoned after Laminas abandoned their laminas-console package, that dot-console was based on. Setup Install package Run the following command in your application's root directory: composer require dotkernel\/dot-cli Register ConfigProvider Open your application's config\/config." + "excerpt": "Replacing dot-console with dot-cli based on laminas-cli Implementing dot-cli in your application DotKernel's dot-cli package comes as a replacement for dot-console, which was abandoned after Laminas abandoned their laminas-console package, that dot-console was based on. Setup Install package Run the following command in your application's root directory: composer require dotkernel\/dot-cli Register ConfigProvider Open your application's config\/config.", + "tl_dr": "DotKernel's dot-cli package replaces dot-console, which was abandoned after Laminas dropped the laminas-console package it was based on.\nSetting it up involves requiring the package via Composer, registering its ConfigProvider, and copying its bootstrap and config files into the application.\nIt also ships with FileLocker, a built-in, enabled-by-default locking system that prevents overlapping calls to the same command." }, { "post_title": "List available endpoints in DotKernel API using dot-cli", @@ -1060,7 +1162,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "Displaying DotKernel API endpoints using dot-cli Starting from version 3, DotKernel API uses dot-cli to display a list of available endpoints. Usage Run the following command in your application’s root directory: php ." + "excerpt": "Displaying DotKernel API endpoints using dot-cli Starting from version 3, DotKernel API uses dot-cli to display a list of available endpoints. Usage Run the following command in your application’s root directory: php .", + "tl_dr": "Starting from version 3, DotKernel API uses the dot-cli package to list all of its available endpoints via the route:list command.\nThe command's output can be filtered by route name, path, or HTTP method, and filters are case-insensitive and combinable." }, { "post_title": "Creating admin accounts in DotKernel API", @@ -1070,7 +1173,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "Creating admin accounts in DotKernel API Starting from v3, DotKernel API introduces support for admin accounts. In this article we will describe two different methods of creating an admin account." + "excerpt": "Creating admin accounts in DotKernel API Starting from v3, DotKernel API introduces support for admin accounts. In this article we will describe two different methods of creating an admin account.", + "tl_dr": "Starting with version 3, DotKernel API supports dedicated admin accounts.\nThey can be created either through a protected API endpoint, which lets you assign one or more admin roles and optional names, or through a terminal command, which is quicker but always assigns the default admin role.\nBoth methods leave you with a ready-to-use admin account." }, { "post_title": "Using Postman for documentation in DotKernel API 3", @@ -1080,7 +1184,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "Using Postman documentation in DotKernel API 3 Starting from version 3.0 DotKernel API provides it's documentation using Postman." + "excerpt": "Using Postman documentation in DotKernel API 3 Starting from version 3.0 DotKernel API provides it's documentation using Postman.", + "tl_dr": "Starting from version 3.0, DotKernel API documents its endpoints using Postman, via a provided collection and environment file that get imported into the tool.\nPostman organizes work into a Workspace, Collections, Environments, and Requests, and the DotKernel API collection ships with built-in security: global Bearer Token authorization inherited from the collection root, and automatic ACCESS_TOKEN\/REFRESH_TOKEN storage on the Admin\/Security and User\/Security folders.\nAfter making changes, the collection and environment files can be re-exported to overwrite the application's documentation files." }, { "post_title": "Generating a doctrine migration without dropping custom tables", @@ -1090,7 +1195,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "Generating a doctrine migration without dropping custom tables If your application needs to hold some custom (unmapped) tables in the database, then generating migrations with doctrine-migrations diff will try to drop the custom tables. This article provides a solution on how to avoid dropping those tables." + "excerpt": "Generating a doctrine migration without dropping custom tables If your application needs to hold some custom (unmapped) tables in the database, then generating migrations with doctrine-migrations diff will try to drop the custom tables. This article provides a solution on how to avoid dropping those tables.", + "tl_dr": "When an application has custom, unmapped database tables, running doctrine-migrations diff will try to drop them, since no Doctrine entity describes them.\nThis article shows how to prevent that using the --filter-expression option, including how to filter multiple table prefixes at once.\nIt also flags a Windows PowerShell quirk where the caret in the regex gets stripped, and how to work around it." }, { "post_title": "What is cross origin token redemption?", @@ -1100,7 +1206,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "What is cross origin token redemption? Cross-origin token redemption is a technique used to ensure the security and authenticity of a token that is issued by one website or domain, but intended for use on a different website or domain. This process is commonly used in situations where a user needs to access resources from multiple domains, such as when a user is logged in to one website and needs to access resources from another website." + "excerpt": "What is cross origin token redemption? Cross-origin token redemption is a technique used to ensure the security and authenticity of a token that is issued by one website or domain, but intended for use on a different website or domain. This process is commonly used in situations where a user needs to access resources from multiple domains, such as when a user is logged in to one website and needs to access resources from another website.", + "tl_dr": "Cross-origin token redemption verifies the security and authenticity of a token issued on one domain but used on another, which is common when a logged-in user needs to access resources on a different site.\nThe receiving domain checks the token's signature and decrypts it before trusting it.\nJWT and OAuth 2.0 are two standards that implement this pattern, each with a different verification flow." }, { "post_title": "Implementation of SEO friendly URL in an generic Laminas Mezzio app", @@ -1110,7 +1217,8 @@ "display_name": "MarioRadu", "github": "marioradu05" }, - "excerpt": "Prerequisites: Mezzio App Doctrine In the vast digital landscape of the internet, where websites compete for attention, having a well-crafted URL can make a significant difference. By incorporating human-readable slugs into website URLs, we can enhance user experience, improve search engine optimization (SEO), and foster better engagement." + "excerpt": "Prerequisites: Mezzio App Doctrine In the vast digital landscape of the internet, where websites compete for attention, having a well-crafted URL can make a significant difference. By incorporating human-readable slugs into website URLs, we can enhance user experience, improve search engine optimization (SEO), and foster better engagement.", + "tl_dr": "Human-readable URL slugs improve readability, SEO, and shareability compared to raw numeric IDs in URLs.\nThis article shows how to add slug support to a Mezzio application using the gedmo\/doctrine-extensions package.\nIt covers installing the package via Composer, registering its SluggableListener with Doctrine, and adding a slug column generated from an existing field (such as identity) via the @Gedmo\\Slug annotation." }, { "post_title": "Installing AlmaLinux 10 in WSL2: PHP, MariaDB, Composer, PhpMyadmin", @@ -1120,7 +1228,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "With the recent release of AlmaLinux OS 10, we have created a new recipe for our WSL development environment. Compared to its predecessor, AlmaLinux 10 provides performance enhancements, security updates and improved hardware support." + "excerpt": "With the recent release of AlmaLinux OS 10, we have created a new recipe for our WSL development environment. Compared to its predecessor, AlmaLinux 10 provides performance enhancements, security updates and improved hardware support.", + "tl_dr": "With the release of AlmaLinux OS 10, Dotkernel created a new WSL2 development environment recipe offering performance, security, and hardware improvements over AlmaLinux 9.\nThe recipe sets up WSL2, AlmaLinux 10, PHP, Apache, MariaDB, Git, Composer, Node.js, and PhpMyAdmin.\nIt also covers the OS\/hardware requirements, installing the distro, and running PHP projects directly or via virtual hosts." } ] }, @@ -1137,7 +1246,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "Problem PHP packages\/frameworks\/libraries\/scripts we work with might require different PHP extensions. In this case the Intl extension is needed to work with using Internationalization Functions." + "excerpt": "Problem PHP packages\/frameworks\/libraries\/scripts we work with might require different PHP extensions. In this case the Intl extension is needed to work with using Internationalization Functions.", + "tl_dr": "Errors like \"requires intl PHP extension\" or \"extension intl is missing\" happen because the PHP Intl extension isn't installed or enabled.\nThis article explains what Intl is used for, why it might be missing depending on whether you have a bundled or unbundled PHP install, and gives step-by-step fixes for both Linux and Windows servers." }, { "post_title": "[FIX] Installing PEAR packages with PHP 7.2", @@ -1147,7 +1257,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This article will cover the solution to the PEAR \"Cannot use result of built-in function in write context\" issue. The Issue If installing a pear package (for instance PHP Code Sniffer), when running: pear install PHP_CodeSniffer This error is shown PHP Fatal error: Cannot use result of built-in function in write context in ." + "excerpt": "This article will cover the solution to the PEAR \"Cannot use result of built-in function in write context\" issue. The Issue If installing a pear package (for instance PHP Code Sniffer), when running: pear install PHP_CodeSniffer This error is shown PHP Fatal error: Cannot use result of built-in function in write context in .", + "tl_dr": "On PHP 7.2, installing PEAR packages such as PHP Code Sniffer fails with a \"Cannot use result of built-in function in write context\" error in Archive_Tar's Tar.php, because a function is called by reference.\nThe fix is to edit the offending line in Tar.php to drop the by-reference call, then reinstall Archive_Tar and the target package." } ] }, @@ -1164,7 +1275,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "This report contains updates about the DotKernel3 documentation. We have added the release notes for DotKernel3 frontend and admin: you can now check the Release Notes page." + "excerpt": "This report contains updates about the DotKernel3 documentation. We have added the release notes for DotKernel3 frontend and admin: you can now check the Release Notes page.", + "tl_dr": "This report covers updates to the DotKernel3 documentation: new release notes for the frontend and admin, a Webpack tutorial added to the Prerequisites section, and revisions to the Api Endpoint Documentation Guidelines.\nContributor JapSeyz is thanked for this round of updates." }, { "post_title": "DotKernel3 - Stable Release version 1.0", @@ -1174,7 +1286,8 @@ "display_name": "Gabi DJ", "github": "" }, - "excerpt": "DotKernel was updated to support Zend Expressive 3 alongside with PSR-15 middleware. We have updated the core packages to support PSR-15 Middleware." + "excerpt": "DotKernel was updated to support Zend Expressive 3 alongside with PSR-15 middleware. We have updated the core packages to support PSR-15 Middleware.", + "tl_dr": "DotKernel3 1.0 updates the core packages to support Zend Expressive 3 and PSR-15 middleware, making both frontend (1.0.0) and admin (1.0.1) easier to migrate.\nNo functional changes were made to the core code, though projects using the old http-interop\/http-middleware package must migrate to the psr\/http-server-middleware interfaces.\nExisting DotKernel 3 (Expressive 2) projects can follow a separate guide to move to Zend Expressive 3." }, { "post_title": "Dotkernel Frontend version 3 launched", @@ -1184,7 +1297,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Dotkernel Frontend PHP Application version 3 was launched. Dotkernel is a Collection of PSR-7 Middleware applications built on top of Mezzio microframework and using Laminas components You can clone it from github Live demo: v3." + "excerpt": "Dotkernel Frontend PHP Application version 3 was launched. Dotkernel is a Collection of PSR-7 Middleware applications built on top of Mezzio microframework and using Laminas components You can clone it from github Live demo: v3.", + "tl_dr": "Dotkernel Frontend version 3 has launched as part of the Dotkernel collection of PSR-7 Middleware applications, built on the Mezzio microframework using Laminas components.\nThe source is available on GitHub, with a live demo running at v3.dotkernel.net.\nBranch 3.0 is now the default branch, requiring Mezzio ^3.2, PHP ^7.4, Doctrine 2.7.x, and Twig 3.x, with dot-* packages limited to version 3.x and above." }, { "post_title": "Dotkernel Admin version 3 launched", @@ -1194,7 +1308,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "Dotkernel Admin PHP Application version 3 was launched. Dotkernel is a Collection of PSR-7 Middleware applications built on top of Mezzio microframework and using Laminas components  Dotkernel Admin is a basic admin panel, based on Boostrap ^4." + "excerpt": "Dotkernel Admin PHP Application version 3 was launched. Dotkernel is a Collection of PSR-7 Middleware applications built on top of Mezzio microframework and using Laminas components  Dotkernel Admin is a basic admin panel, based on Boostrap ^4.", + "tl_dr": "Dotkernel Admin version 3 has launched as a basic admin panel built on Bootstrap ^4.5.0 and Doctrine, performing CRUD operations over a database on top of the Mezzio microframework and Laminas components.\nThe source is available on GitHub, with a live demo running at admin.dotkernel.net.\nBranch 3.0 is now the default branch, requiring Mezzio ^3.2, PHP ^7.4, Doctrine 2.7.x, Twig 3.x, and Bootstrap 4.5, with dot-* packages limited to version 3.x and above." }, { "post_title": "Dotkernel Admin V4", @@ -1204,7 +1319,8 @@ "display_name": "kakapiciu", "github": "sergiu@rospace.com" }, - "excerpt": "Getting Started with Dotkernel Admin V4 DotKernel's PSR-7 Admin is an application based on Mezzio, with the main purpose of managing and displaying tabular data from one or more databases components. On 19 July 2022 Dotkernel Admin V4 has been officially released." + "excerpt": "Getting Started with Dotkernel Admin V4 DotKernel's PSR-7 Admin is an application based on Mezzio, with the main purpose of managing and displaying tabular data from one or more databases components. On 19 July 2022 Dotkernel Admin V4 has been officially released.", + "tl_dr": "Dotkernel Admin V4, officially released on 19 July 2022, is DotKernel's PSR-7 Admin application built on Mezzio for managing and displaying tabular data from one or more databases.\nIt supports PHP 8.1 (minimum PHP 7.4), offers a config-driven module\/middleware\/route setup, RBAC-based authorization guards, a Symfony Console-based CLI with a file locker, per-module routing via RoutesDelegator, and a Bootstrap 4.5.0 \/ Fontawesome 5.0.6 frontend using Bootstrap Table for data listing." }, { "post_title": "Dotkernel API: architecture and components", @@ -1214,7 +1330,8 @@ "display_name": "kakapiciu", "github": "sergiu@rospace.com" }, - "excerpt": "This article refers to Dotkernel API v5. Checkout out the new additions for Dotkernel API v6 to stay up-to-date." + "excerpt": "This article refers to Dotkernel API v5. Checkout out the new additions for Dotkernel API v6 to stay up-to-date.", + "tl_dr": "Dotkernel API is built on the Mezzio microframework and Laminas components, based on Enrico Zimuel's Zend Expressive API skeleton and implementing PSR-3, PSR-4, PSR-7, PSR-11, and PSR-15.\nIts core components include Doctrine ORM for persistence, mezzio-hal for API payloads, mezzio-cors for CORS handling, and mezzio-authentication-oauth2 for OAuth 2.0 authentication, alongside Postman-based documentation, configurable routing and commands, a file locker system, and a factory-made test suite." }, { "post_title": "PHP 8.3 support in Dotkernel Admin", @@ -1224,7 +1341,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "With the release of PHP 8.3, the DotKernel team has been working on updating the dependencies in our packages." + "excerpt": "With the release of PHP 8.3, the DotKernel team has been working on updating the dependencies in our packages.", + "tl_dr": "Dotkernel Admin added PHP 8.3 support in release 4.3.1, dropping PHP 8.1 and now supporting only PHP 8.2 and PHP 8.3.\nThe update brought numerous dependency bumps across dotkernel\/, laminas\/, and mezzio\/* packages, removed PhpFileCache-related cache configuration because doctrine\/cache dropped its implementation classes, and removed doctrine\/doctrine-module due to a conflict, which may affect packages that depended on it.\nThe AdminService::logAdminVisit method was also updated to no longer return AddressNotFoundException." }, { "post_title": "PHP 8.3 support in Dotkernel Frontend", @@ -1234,7 +1352,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "To be able to take advantage of the support for PHP 8.3 in the newest packages, the DotKernel team has updated the Frontend Application to version 4." + "excerpt": "To be able to take advantage of the support for PHP 8.3 in the newest packages, the DotKernel team has updated the Frontend Application to version 4.", + "tl_dr": "To take advantage of PHP 8.3 support in the newest packages, the DotKernel team updated the Frontend application to version 4.2.0.\nAs with the earlier Admin update, this required dropping support for PHP 8.1 and for the no-longer-available PhpFileCache class, until a replacement is implemented." }, { "post_title": "PHP 8.3 support in Dotkernel API", @@ -1244,7 +1363,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "The last remaining application to be updated to support PHP 8.3 is the API, now at v4." + "excerpt": "The last remaining application to be updated to support PHP 8.3 is the API, now at v4.", + "tl_dr": "Dotkernel API, now at v4.2.1, is the last remaining Dotkernel application updated to support PHP 8.3, following the same approach used for the Frontend update.\nThe update drops PHP 8.1 support, updates a large set of dependencies, removes the PhpFileCache-based configuration in favor of the new dot-cache package, and requires a small query change (useQueryCache() to setCacheable())." } ] }, @@ -1261,7 +1381,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "The goal of this update is to replace the static way of creating routes with a more dynamic implementation. The result is a cleaner approach that is easier to set up and review at a glance." + "excerpt": "The goal of this update is to replace the static way of creating routes with a more dynamic implementation. The result is a cleaner approach that is easier to set up and review at a glance.", + "tl_dr": "This article, the first in a series about switching from controllers to PSR-15 compliant handlers, explains how Dotkernel replaced its static, hard-coded route declarations with a centralized, dynamic configuration in local.php.\nThe change is aimed at static pages only - any method other than GET (post, put, delete) returns a 405 status code." }, { "post_title": "Replacing controllers with PSR-15 compliant handlers in Dotkernel Light", @@ -1271,7 +1392,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "The goal of this update is to implement PSR-15 handlers into Dotkernel Light. There are several advantages to using handlers, which we will explore below." + "excerpt": "The goal of this update is to implement PSR-15 handlers into Dotkernel Light. There are several advantages to using handlers, which we will explore below.", + "tl_dr": "The goal of this update is to implement PSR-15 handlers into Dotkernel Light, keeping the application up-to-date with recommended design guidelines, secure, and aligned with standards widely adopted by the PHP community." }, { "post_title": "Dotkernel Light improvements: PSR-15 Handlers, Vite, PHPStan", @@ -1281,7 +1403,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Dotkernel Light is a PSR-15 compliant application that uses the Mezzio microframework and Laminas components. It's aimed at creating a simple website, like a presentation site, but can be expanded as needed." + "excerpt": "Dotkernel Light is a PSR-15 compliant application that uses the Mezzio microframework and Laminas components. It's aimed at creating a simple website, like a presentation site, but can be expanded as needed.", + "tl_dr": "Dotkernel Light is a PSR-15 compliant application built on Mezzio and Laminas, aimed at simple websites like presentation sites.\nSince its last update, it has moved from controllers to PSR-15 handlers, adopted Vite as its bundler, replaced Psalm with PHPStan, and picked up several smaller improvements." } ] }, @@ -1298,7 +1421,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "Zend_Db and its related classes provide a simple SQL database interface for Zend Framework. To connect to MySql database, we are using Pdo_Mysql adapter : $db = Zend_Db::factory('Pdo_Mysql', $dbConnect); SELECT query - WHERE clause The below 2 classical SQL queries are equivalent." + "excerpt": "Zend_Db and its related classes provide a simple SQL database interface for Zend Framework. To connect to MySql database, we are using Pdo_Mysql adapter : $db = Zend_Db::factory('Pdo_Mysql', $dbConnect); SELECT query - WHERE clause The below 2 classical SQL queries are equivalent.", + "tl_dr": "Zend_Db and its related classes provide a simple SQL database interface for Zend Framework.\nThis article shows how classical SELECT queries with JOINs and WHERE IN clauses are translated into Zend_Db's select() style, and how to debug the generated query." }, { "post_title": "What are returning the FETCH functions from Zend_Db", @@ -1308,7 +1432,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "Continuing the Zend_DB article series, we are stopping now at FETCH methods that are in Zend_Db_Adapter_Abstract: array fetchAll (string|Zend_Db_Select $sql, , ) array fetchAssoc (string|Zend_Db_Select $sql, ) array fetchCol (string|Zend_Db_Select $sql, ) string fetchOne (string|Zend_Db_Select $sql, ) array fetchPairs (string|Zend_Db_Select $sql, ) array fetchRow (string|Zend_Db_Select $sql, , ) To be more easily to follow, in green box is the classical SQL statement, and in blue box is the query written in Zend_Db style. Lets start." + "excerpt": "Continuing the Zend_DB article series, we are stopping now at FETCH methods that are in Zend_Db_Adapter_Abstract: array fetchAll (string|Zend_Db_Select $sql, , ) array fetchAssoc (string|Zend_Db_Select $sql, ) array fetchCol (string|Zend_Db_Select $sql, ) string fetchOne (string|Zend_Db_Select $sql, ) array fetchPairs (string|Zend_Db_Select $sql, ) array fetchRow (string|Zend_Db_Select $sql, , ) To be more easily to follow, in green box is the classical SQL statement, and in blue box is the query written in Zend_Db style. Lets start.", + "tl_dr": "Continuing the Zend_Db article series, this article walks through the FETCH methods available on Zend_Db_Adapter_Abstract: fetchAll, fetchAssoc, fetchCol, fetchOne, fetchPairs, and fetchRow.\nEach method is shown next to the equivalent old-style code built on query(), next_record(), and f(), so the two approaches can be compared side by side." }, { "post_title": "Subqueries with Zend_Db", @@ -1318,7 +1443,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "Continuing the Zend_DB article series, we are stopping now at subqueries. As you note, the below is a complicate query, with COUNT(), LEFT JOIN(), GROUP BY - select from 3 tables, and make a count from 2 different tables: SELECT a." + "excerpt": "Continuing the Zend_DB article series, we are stopping now at subqueries. As you note, the below is a complicate query, with COUNT(), LEFT JOIN(), GROUP BY - select from 3 tables, and make a count from 2 different tables: SELECT a.", + "tl_dr": "Continuing the Zend_Db series, this article shows a more complex query — combining COUNT(), LEFT JOIN, and GROUP BY across 3 tables, with a count taken from 2 different tables — and how to build it, including a nested subquery, using Zend_Db." }, { "post_title": "INSERT, UPDATE, DELETE statements with Zend_Db", @@ -1328,7 +1454,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "Continuing the Zend_DB article series, we are stopping now at DML statements. DML (Data Manipulation Language) statements are statements that change data values in database tables." + "excerpt": "Continuing the Zend_DB article series, we are stopping now at DML statements. DML (Data Manipulation Language) statements are statements that change data values in database tables.", + "tl_dr": "DML (Data Manipulation Language) statements change data values in database tables.\nThis article, continuing the Zend_Db series, shows how the three primary DML statements — INSERT, UPDATE, and DELETE — are written in raw SQL and translated into Zend_Db method calls." }, { "post_title": "Why use CURRENT_TIMESTAMP on a field that record date\/time?", @@ -1338,7 +1465,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "On a TIMESTAMP field that records date and time when inserting a new record, it is encouraged to use as a DEFAULT value, the CURRENT_TIMESTAMP constant. Why? Because when inserting a new row in the table for the date and time field there is no need to specifically add its value, either by creating it from PHP code with the Date\/ Time functions or with MySQL function NOW() ALTER TABLE `user` CHANGE `dateCreated` `dateCreated` TIMESTAMP NOT DEFAULT CURRENT_TIMESTAMP; CURRENT_TIMESTAMP is also a solution for  updating date and time fields." + "excerpt": "On a TIMESTAMP field that records date and time when inserting a new record, it is encouraged to use as a DEFAULT value, the CURRENT_TIMESTAMP constant. Why? Because when inserting a new row in the table for the date and time field there is no need to specifically add its value, either by creating it from PHP code with the Date\/ Time functions or with MySQL function NOW() ALTER TABLE `user` CHANGE `dateCreated` `dateCreated` TIMESTAMP NOT DEFAULT CURRENT_TIMESTAMP; CURRENT_TIMESTAMP is also a solution for  updating date and time fields.", + "tl_dr": "On a TIMESTAMP field that records date and time when inserting a new record, it's encouraged to use the CURRENT_TIMESTAMP constant as its DEFAULT value.\nThis removes the need to set the value manually from PHP or with MySQL's NOW() function, and the ON UPDATE CURRENT_TIMESTAMP clause can additionally keep the field updated automatically on every row update.\nOnly one TIMESTAMP field per table can be DEFAULT CURRENT_TIMESTAMP." }, { "post_title": "Using LIKE wildcards with Zend_Db", @@ -1348,7 +1476,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "Continuing the Zend_Db article series, let's discuss the LIKE condition. The LIKE condition allows you to use wildcards in the WHERE clause of an SQL statement." + "excerpt": "Continuing the Zend_Db article series, let's discuss the LIKE condition. The LIKE condition allows you to use wildcards in the WHERE clause of an SQL statement.", + "tl_dr": "The LIKE condition allows pattern matching in the WHERE clause of SELECT, INSERT, UPDATE, or DELETE statements.\nThe _ wildcard matches a single character, and % matches any string of any length (including zero).\nThis article shows how to use LIKE and NOT LIKE with both wildcards in Zend_Db." }, { "post_title": "htaccess 301 redirect non-www to www", @@ -1358,7 +1487,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "To always redirect users to the www site (for example: http:\/\/dotboost.com to http:\/\/www." + "excerpt": "To always redirect users to the www site (for example: http:\/\/dotboost.com to http:\/\/www.", + "tl_dr": "" }, { "post_title": "SVN Export in a virtual host", @@ -1368,7 +1498,8 @@ "display_name": "Adrian", "github": "" }, - "excerpt": "The following commands should be run in the terminal (for example, using Putty in Windows) on the host where you want to export the repository). It's recommended that you run them using the domain's user, not root." + "excerpt": "The following commands should be run in the terminal (for example, using Putty in Windows) on the host where you want to export the repository). It's recommended that you run them using the domain's user, not root.", + "tl_dr": "svn export lets you export the contents of a repository into a virtual host directory.\nThe commands should be run in a terminal (e.g. via Putty on Windows) on the target host, ideally using the domain's own user rather than root." }, { "post_title": "Aptana - set SVN keywords", @@ -1378,7 +1509,8 @@ "display_name": "Teo", "github": "" }, - "excerpt": "In Aptana it's very simple to set the svn:keywords property for a file. For example if you want to set the svn keyword property Id: In the file where you want to add the svn keyword property write $Id$ Right click on the file, then follow Team -> Set Property." + "excerpt": "In Aptana it's very simple to set the svn:keywords property for a file. For example if you want to set the svn keyword property Id: In the file where you want to add the svn keyword property write $Id$ Right click on the file, then follow Team -> Set Property.", + "tl_dr": "" }, { "post_title": "Golden Rules of Professional PHP Coding", @@ -1388,7 +1520,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "1.  Always use in development and in staging highest error reporting level, and display_errors ON: error_reporting(-1); ini_set('display_errors', 1); 2." + "excerpt": "1.  Always use in development and in staging highest error reporting level, and display_errors ON: error_reporting(-1); ini_set('display_errors', 1); 2.", + "tl_dr": "" }, { "post_title": "SVN keywords setup in PHP IDE ( Zend Studio)", @@ -1398,7 +1531,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "For a better integration of SVN, your PHP IDE( Zend Studio), and a bug tracker of choice, the below proprieties must be set, for each project you have. Right click on project Go to Team->Set Propriety SVN Ignore files, below you have an example." + "excerpt": "For a better integration of SVN, your PHP IDE( Zend Studio), and a bug tracker of choice, the below proprieties must be set, for each project you have. Right click on project Go to Team->Set Propriety SVN Ignore files, below you have an example.", + "tl_dr": "For better integration between SVN, the Zend Studio PHP IDE, and a bug tracker, a set of SVN properties must be set for each project.\nThis article lists which properties to set and how." }, { "post_title": "ZF Is Retired. Laminas MVC Is Retiring. Consider It Solved", @@ -1408,7 +1542,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "It all started with the announcement: Laminas MVC Is Retiring. Some people wrongfully thought everything with a Laminas logo is going away - NOT SO! Read on for a bit of history about Zend and Laminas, what it means to migrate your platform and why it's a decision that should not be taken lightly." + "excerpt": "It all started with the announcement: Laminas MVC Is Retiring. Some people wrongfully thought everything with a Laminas logo is going away - NOT SO! Read on for a bit of history about Zend and Laminas, what it means to migrate your platform and why it's a decision that should not be taken lightly.", + "tl_dr": "Laminas MVC is retiring, following Zend Framework and Apigility before it, but this doesn't mean everything with a Laminas logo is going away — Mezzio, built on Laminas components, is the fully-functional successor.\nMaintaining legacy MVC platforms is costly and risky long-term, since the architecture of today and tomorrow is middleware-based, and Apidemia offers a proven, phased migration process to move legacy platforms to Mezzio." }, { "post_title": "Basic Security in Dotkernel Headless Platform", @@ -1418,7 +1553,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Software security should always be in the back of your mind as a developer. It may seem fine at first to deliver a feature sooner, only to find later on that you left a backdoor into your crisp new update." + "excerpt": "Software security should always be in the back of your mind as a developer. It may seem fine at first to deliver a feature sooner, only to find later on that you left a backdoor into your crisp new update.", + "tl_dr": "Software security should always be top of mind for a developer, since ignoring it can lead to major costs, data loss, GDPR fines, or the loss of client trust. The article surveys many facets of software security and walks through the practical measures Dotkernel Headless Platform takes for each: input validation, content negotiation, CORS, RBAC, demo credentials, error reporting, OpenAPI docs, PHP and JavaScript dependencies, OAuth2, session\/cookie settings, and CI checks." } ] }, @@ -1435,7 +1571,8 @@ "display_name": "n3vrax", "github": "" }, - "excerpt": "Did you come to a point where using multiple broadcast receivers to listen for the same intent, separatly, in the same android app, leads to unexpected results? If that\"s the case, one broadcast receiver might consume the broadcasted intent, online casino leaving the others with nothing to receive. This can be the case where you use 3rd party libraries with broadcast receivers defined." + "excerpt": "Did you come to a point where using multiple broadcast receivers to listen for the same intent, separatly, in the same android app, leads to unexpected results? If that\"s the case, one broadcast receiver might consume the broadcasted intent, online casino leaving the others with nothing to receive. This can be the case where you use 3rd party libraries with broadcast receivers defined.", + "tl_dr": "" }, { "post_title": "Listen for Android install referrer", @@ -1445,7 +1582,8 @@ "display_name": "n3vrax", "github": "" }, - "excerpt": "Have you ever wondered if Android market sends you information at the moment of app install? Wouldn\"t be nice to create custom links to your android application, including bits of information about the referrer, and send it directly to the app for online casino processing at install? This could be a simple and accurate solution for mobile app install tracking but I\"m sure you can find this useful in many ways. With Android, you actually get this information as a broadcasted intent by android market at install time - even before opening your app." + "excerpt": "Have you ever wondered if Android market sends you information at the moment of app install? Wouldn\"t be nice to create custom links to your android application, including bits of information about the referrer, and send it directly to the app for online casino processing at install? This could be a simple and accurate solution for mobile app install tracking but I\"m sure you can find this useful in many ways. With Android, you actually get this information as a broadcasted intent by android market at install time - even before opening your app.", + "tl_dr": "" } ] }, @@ -1462,7 +1600,8 @@ "display_name": "admin", "github": "arhimede" }, - "excerpt": "This article covers the basic authorization of a Client application which use a backend built using DotKernel API Authorization Request Client application users send a POST request to the backend containing the following JSON object: { \"grant_type\": \"password\", \"client_id\": \"{API_CLIENT}\", \"client_secret\": \"{API_CLIENT_SECRET}\", \"scope\": \"{SCOPE}\", \"username\": \"{USERNAME\/EMAIL}\", \"password\": \"{PASSWORD}\" } Authorization Response If the credentials are correct, the API will return a JSON object containing the authentication data: { \"token_type\": \"Bearer\", \"expires_in\": 86400, \"access_token\": \".." + "excerpt": "This article covers the basic authorization of a Client application which use a backend built using DotKernel API Authorization Request Client application users send a POST request to the backend containing the following JSON object: { \"grant_type\": \"password\", \"client_id\": \"{API_CLIENT}\", \"client_secret\": \"{API_CLIENT_SECRET}\", \"scope\": \"{SCOPE}\", \"username\": \"{USERNAME\/EMAIL}\", \"password\": \"{PASSWORD}\" } Authorization Response If the credentials are correct, the API will return a JSON object containing the authentication data: { \"token_type\": \"Bearer\", \"expires_in\": 86400, \"access_token\": \"..", + "tl_dr": "" }, { "post_title": "DotKernel API Server Side Authorization", @@ -1472,7 +1611,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "This article covers the basic authorization of a Server Side application  built using DotKernel API Protecting an endpoint no-auth: the resource can be accessed without the need of authentication\/authorization authentication: the resource can be accessed only by authenticated users authorization: the resource can be accessed only by authenticated AND authorized users Configuring access to the endpoints is done by editing the following config file: config\/autoload\/authorization.local." + "excerpt": "This article covers the basic authorization of a Server Side application  built using DotKernel API Protecting an endpoint no-auth: the resource can be accessed without the need of authentication\/authorization authentication: the resource can be accessed only by authenticated users authorization: the resource can be accessed only by authenticated AND authorized users Configuring access to the endpoints is done by editing the following config file: config\/autoload\/authorization.local.", + "tl_dr": "DotKernel API endpoints can be protected at three levels: no-auth, authentication, and authorization.\nAccess is configured in config\/autoload\/authorization.local.php under the zend-expressive-authorization-rbac key, using a roles section for role inheritance and a permissions section for route access.\nAuthentication endpoints require a valid Bearer token and return 401 Unauthorized if it's missing, while authorization endpoints additionally check role permissions and return 403 Forbidden." }, { "post_title": "How to implement MailChimp in DotKernel API", @@ -1482,7 +1622,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "This article will walk you through the process of implementing MailChimp into your instance of DotKernel API using drewm\/mailchimp-api   Step 1: Add the library to your application using the following command: composer require drewm\/mailchimp-api   Step 2: Create configuration file config\/autoload\/mailchimp.global." + "excerpt": "This article will walk you through the process of implementing MailChimp into your instance of DotKernel API using drewm\/mailchimp-api   Step 1: Add the library to your application using the following command: composer require drewm\/mailchimp-api   Step 2: Create configuration file config\/autoload\/mailchimp.global.", + "tl_dr": "This is a step-by-step guide to adding MailChimp support to a DotKernel API instance using the drewm\/mailchimp-api library.\nIt covers installing the library, creating a MailChimp config file, building a factory that returns a DrewM\\MailChimp\\MailChimp instance, and registering that factory in ConfigProvider.php so it can be injected wherever needed." }, { "post_title": "DotKernel API 1.0.0 Released", @@ -1492,7 +1633,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "Dotkernel API has come a long way since this post was created. Check out the newest version of Dotkernel API to stay up to date with the latest functional and security features." + "excerpt": "Dotkernel API has come a long way since this post was created. Check out the newest version of Dotkernel API to stay up to date with the latest functional and security features.", + "tl_dr": "" }, { "post_title": "API Endpoint to Collect Client Errors", @@ -1502,7 +1644,8 @@ "display_name": "kakapiciu", "github": "sergiu@rospace.com" }, - "excerpt": "API Endpoint to Collect Client Errors Let's say you have a (Client) Frontend (e.g." + "excerpt": "API Endpoint to Collect Client Errors Let's say you have a (Client) Frontend (e.g.", + "tl_dr": "" }, { "post_title": "DotKernel API versus Laminas API Tools", @@ -1512,7 +1655,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Below we have created an analysis of the basic features available in Laminas Api Tools and DotKernel API. It's intended to highlight the differences between the two and also to showcase why DotKernel API is a good alternative for Laminas API Tools, especially considering the latter's archived status." + "excerpt": "Below we have created an analysis of the basic features available in Laminas Api Tools and DotKernel API. It's intended to highlight the differences between the two and also to showcase why DotKernel API is a good alternative for Laminas API Tools, especially considering the latter's archived status.", + "tl_dr": "This article compares the basic features of Laminas API Tools and Dotkernel API side by side, covering architecture, versioning, documentation, authentication, and more.\nIt highlights that Dotkernel API is a solid alternative now that Laminas API Tools has been archived, since Dotkernel API uses a modern middleware architecture, MIT license, and evolution-based deprecations instead of traditional versioning." }, { "post_title": "OpenAPI implementation in Dotkernel API", @@ -1522,7 +1666,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "What is OpenAPI? The OpenAPI Specification provides a consistent way to develop and interact with an API. It defines API structure and syntax in a universal way, regardless of the programming language used in the API's development." + "excerpt": "What is OpenAPI? The OpenAPI Specification provides a consistent way to develop and interact with an API. It defines API structure and syntax in a universal way, regardless of the programming language used in the API's development.", + "tl_dr": "OpenAPI is a specification for describing an API's structure in a language-agnostic, machine-readable way, offering benefits like standardization, automatic documentation, upfront design, and better collaboration compared to a tool like Postman.\nDotkernel API has full OpenAPI support: each module (Admin, App, User) documents its endpoints in an OpenAPI.php file, which zircote\/swagger-php turns into documentation rendered via Swagger UI or Redoc.\nTesting protected endpoints in Swagger UI requires generating an authentication token that matches the endpoint's required privileges." }, { "post_title": "Error reporting endpoint in Dotkernel API", @@ -1532,7 +1677,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Dotkernel API has received a lot of love from our developers, with regular updates to the platform for years. We use Dotkernel API in our projects, so any bugs and issues are addressed as soon as they are found." + "excerpt": "Dotkernel API has received a lot of love from our developers, with regular updates to the platform for years. We use Dotkernel API in our projects, so any bugs and issues are addressed as soon as they are found.", + "tl_dr": "Dotkernel API includes an error reporting endpoint that lets frontend developers securely report bugs and incorrect data processing back to the API, even when no fatal error shows up in the logs.\nIt works by sending a POST request to \/error-report with a token in the header; the API validates the request against configured tokens, domains, and IPs before logging the message.\nSetup involves generating a token, adding it to config\/autoload\/error-handling.global.php, and having the frontend send the Error-Reporting-Token and Origin headers." }, { "post_title": "Content Negotiation in Dotkernel REST API", @@ -1542,7 +1688,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Content negotiation is an important aspect of RESTful APIs to make it possible for diverse systems to work seamlessly together. It's based on enabling clients and servers to agree on the format and language of data they exchange." + "excerpt": "Content negotiation is an important aspect of RESTful APIs to make it possible for diverse systems to work seamlessly together. It's based on enabling clients and servers to agree on the format and language of data they exchange.", + "tl_dr": "Content negotiation lets clients and servers agree on the format and language of exchanged data.\nIt can be handled server-side or client-side (the latter being more versatile), communicated through HTTP headers or URL patterns, and Dotkernel API implements it out of the box using the Content-Type and Accept headers." }, { "post_title": "API Client Migration: From Postman to Bruno", @@ -1552,7 +1699,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Why We Switched to the Offline-Focused Bruno Every API developer knows that to build an API properly you need a reliable client for testing and interacting with the API. Ideally this tool should be free, it should store endpoint collections and share them easily with your team, and it should be fast and secure." + "excerpt": "Why We Switched to the Offline-Focused Bruno Every API developer knows that to build an API properly you need a reliable client for testing and interacting with the API. Ideally this tool should be free, it should store endpoint collections and share them easily with your team, and it should be fast and secure.", + "tl_dr": "The team has used Postman for years but is considering switching to Bruno, a lightweight, offline-first alternative, reflecting a broader PHP community trend toward local-first, Git-native developer tools.\nBruno wins on offline access, version control via Git, performance, and (arguably) security, while Postman still offers a broader feature set for larger, budget-having teams." } ] }, @@ -1569,7 +1717,8 @@ "display_name": "Alex Karajos", "github": "alexmerlin" }, - "excerpt": "PHP_CodeSniffer or phpcs is a tool that helps developers maintain a specific standard in the way they write code. In order to be able to provide relevant information, phpcs needs to be configured correctly in PHPStorm (see image)." + "excerpt": "PHP_CodeSniffer or phpcs is a tool that helps developers maintain a specific standard in the way they write code. In order to be able to provide relevant information, phpcs needs to be configured correctly in PHPStorm (see image).", + "tl_dr": "PHP_CodeSniffer (phpcs) needs to be configured correctly in PHPStorm under PHP > Quality Tools > PHP_CodeSniffer, with the Custom coding standard pointed at your project's phpcs.xml file.\nThis article gives separate setup steps for a freshly cloned project versus an existing one that isn't reporting issues yet, and explains how to read the resulting inline error and warning indicators in the editor." } ] }, @@ -1586,7 +1735,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "After a recent analysis, we discovered that one of the upstream packages we use is licensed under LGPL v3. Even though we at DotKernel use the MIT license for our open source projects, the more restrictive license must be applied to the whole application." + "excerpt": "After a recent analysis, we discovered that one of the upstream packages we use is licensed under LGPL v3. Even though we at DotKernel use the MIT license for our open source projects, the more restrictive license must be applied to the whole application.", + "tl_dr": "DotKernel discovered that an upstream dependency, matomo\/device-detector, was licensed under LGPL v3 - a more restrictive license than the MIT license DotKernel uses for its own projects.\nBecause the more restrictive license would have to apply to the whole application, DotKernel implemented a workaround: it stopped bundling that dependency by default and documented the licensing implications." } ] }, @@ -1603,7 +1753,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Middleware is code that exists between the request and response, and which can take the incoming request, perform actions based on it, and either complete the response or pass delegation on to the next middleware in the queue. The purpose of middleware Middleware makes it easier for software developers to implement communication and input\/output, so they can focus on the specific purpose of their application." + "excerpt": "Middleware is code that exists between the request and response, and which can take the incoming request, perform actions based on it, and either complete the response or pass delegation on to the next middleware in the queue. The purpose of middleware Middleware makes it easier for software developers to implement communication and input\/output, so they can focus on the specific purpose of their application.", + "tl_dr": "Middleware is code that exists between the request and response: it can take an incoming request, act on it, and either complete the response itself or delegate to the next middleware in the queue.\nIt's used for concerns like authentication, CORS, caching, rate limiting, and more, and in PHP a PSR-15 compliant middleware implements Psr\\Http\\Server\\MiddlewareInterface with a single process() method." }, { "post_title": "ConfigProvider - Bootstrap Modern PHP Applications", @@ -1613,7 +1764,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "In PHP, the ConfigProvider is a class that is part of an application's bootstrap process. It's a class or callable that returns configuration data telling the platform which middleware should run, in what order, and sometimes under what conditions." + "excerpt": "In PHP, the ConfigProvider is a class that is part of an application's bootstrap process. It's a class or callable that returns configuration data telling the platform which middleware should run, in what order, and sometimes under what conditions.", + "tl_dr": "In PHP, a ConfigProvider is a class or callable that is part of an application's bootstrap process, returning configuration data that tells the platform which middleware should run, in what order, and under what conditions.\nFrameworks like Mezzio, Laminas, Slim, and the Dotkernel Headless Platform use ConfigProviders to declare middleware pipeline configuration, dependency injection mappings, and request handlers, which get merged together automatically during bootstrap (except in Dotkernel, where new ConfigProviders must be registered manually)." }, { "post_title": "Request Lifecycle for a Mezzio-Based Application", @@ -1623,7 +1775,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Seamlessly Interconnected Middleware for Enterprise-Level Solutions The request lifecycle is the sequence of steps that happen from the moment a user makes an HTTP request until the server sends back a response. The graph below shows how the request is handled by Dotkernel Light (GitHub, documentation), one of the applications in the Dotkernel Headless Platform suite." + "excerpt": "Seamlessly Interconnected Middleware for Enterprise-Level Solutions The request lifecycle is the sequence of steps that happen from the moment a user makes an HTTP request until the server sends back a response. The graph below shows how the request is handled by Dotkernel Light (GitHub, documentation), one of the applications in the Dotkernel Headless Platform suite.", + "tl_dr": "The request lifecycle is the sequence of steps that happen from the moment a user makes an HTTP request until the server sends back a response.\nThis is illustrated using Dotkernel Light, one of the applications in the Dotkernel Headless Platform suite, walking through entry point setup, routing, handler execution, template rendering, response creation, and the response emitter." } ] }, @@ -1640,7 +1793,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "This naming pattern is used in Dotkernel Admin v6 and will also be implemented in the next releases for Frontend and Light. The bigger a project is, the more time it will take to develop and the more people will be assigned to it." + "excerpt": "This naming pattern is used in Dotkernel Admin v6 and will also be implemented in the next releases for Frontend and Light. The bigger a project is, the more time it will take to develop and the more people will be assigned to it.", + "tl_dr": "" } ] }, @@ -1657,7 +1811,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "The principle of a Headless Platform is to decouple the User Interface (frontend) from the backend services. The responses from the platform are then used by another system, such as a website or mobile app." + "excerpt": "The principle of a Headless Platform is to decouple the User Interface (frontend) from the backend services. The responses from the platform are then used by another system, such as a website or mobile app.", + "tl_dr": "A Headless Platform decouples the frontend (UI) from the backend services, with responses consumed by another system such as a website or mobile app. The Dotkernel Headless Platform is made up of Dotkernel API (a REST API based on the Mezzio skeleton) and Dotkernel Admin (a backend management interface), which can be installed separately or together.\nUsing both together, sharing a common Core module, gives consistent entities and queries, an easy-to-maintain shared file structure, and an architecture that scales from small microservices to enterprise-grade APIs." }, { "post_title": "Shared Core Submodule in Dotkernel Headless Platform", @@ -1667,7 +1822,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Dotkernel has implemented a Headless solution made up of these applications: Dotkernel API - a REST API, the root of the platform. Dotkernel Admin - (optional) complementary backend management." + "excerpt": "Dotkernel has implemented a Headless solution made up of these applications: Dotkernel API - a REST API, the root of the platform. Dotkernel Admin - (optional) complementary backend management.", + "tl_dr": "Dotkernel's Headless Platform is composed of Dotkernel API, Admin, and Queue, and can share a common Core submodule that holds the database entities and services used consistently across all of them.\nThe article walks through creating the Core submodule with git submodule add, committing changes from within the Core folder, and initializing\/updating it with git submodule init and git submodule update.\nSharing a Core module brings design flexibility, scalability, and easier bugfixes and onboarding as the platform grows." }, { "post_title": "Dotkernel API v6: The root of Dotkernel Headless Platform", @@ -1677,7 +1833,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Dotkernel API has come a long way since we published a list of its architecture and components a while ago. We implemented new features, while some components were replaced, and others were enhanced." + "excerpt": "Dotkernel API has come a long way since we published a list of its architecture and components a while ago. We implemented new features, while some components were replaced, and others were enhanced.", + "tl_dr": "Dotkernel API has evolved significantly since its original architecture and components article, adding Content Negotiation, standardized error responses via mezzio-problem-details, a shareable Core module, a custom templating solution replacing Twig, and a leaner handler dependency setup.\nPackages were updated across the board, the test suite switched from Psalm to PHPStan at a stricter rule level, and the roadmap for v6.1 targets Service Manager 4 and PHP 8.4\/8.5 support." }, { "post_title": "Complementary Admin in Dotkernel Headless Platform", @@ -1687,7 +1844,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "The Dotkernel Headless Platform is built with an architecture designed to be easy to maintain and expand indefinitely. Its core components are Dotkernel API and Dotkernel Queue, but the Dotkernel application suite also offers a fully separate, complementary Admin application designed to pair seamlessly with Dotkernel API." + "excerpt": "The Dotkernel Headless Platform is built with an architecture designed to be easy to maintain and expand indefinitely. Its core components are Dotkernel API and Dotkernel Queue, but the Dotkernel application suite also offers a fully separate, complementary Admin application designed to pair seamlessly with Dotkernel API.", + "tl_dr": "The Dotkernel Headless Platform's core components are Dotkernel API and Dotkernel Queue, but the suite also offers a fully separate, complementary Admin application designed to pair seamlessly with Dotkernel API.\nAdmin is an independent app built on the same Mezzio + Laminas foundation, sharing a unified tech stack with the API so the two form a cohesive, consistent system." }, { "post_title": "Dotkernel Queue - Asynchronous Execution in Dotkernel Headless Platform", @@ -1697,7 +1855,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "Dotkernel Queue is a component based on Symfony Messenger that is used to queue asynchronous tasks. netglue\/laminas-messenger is an adapter that integrates Symfony Messenger with the Laminas Service Manager container for Mezzio\/Laminas applications." + "excerpt": "Dotkernel Queue is a component based on Symfony Messenger that is used to queue asynchronous tasks. netglue\/laminas-messenger is an adapter that integrates Symfony Messenger with the Laminas Service Manager container for Mezzio\/Laminas applications.", + "tl_dr": "Dotkernel Queue is a component built on Symfony Messenger (via the netglue\/laminas-messenger adapter) that lets time-consuming or resource-intensive operations run asynchronously on background workers instead of inside the normal PHP request-response cycle.\nAn active daemon listens for TCP connections, stores incoming messages in Redis, and processes them in FIFO order, with logging, IP-whitelisting security, a configurable retry mechanism, reporting metrics, and a Dead Letter Queue for messages that fail.\nPriorities and parallel execution are planned future features." }, { "post_title": "DotMaker - Generate common code in Dotkernel", @@ -1707,7 +1866,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "The dotkernel\/dot-maker library, also named DotMaker, is designed to programmatically generate project files and directories that match the Dotkernel file structure inspired by Mezzio. Handling the file creation and configuration task manually invites mistakes that nobody has time for." + "excerpt": "The dotkernel\/dot-maker library, also named DotMaker, is designed to programmatically generate project files and directories that match the Dotkernel file structure inspired by Mezzio. Handling the file creation and configuration task manually invites mistakes that nobody has time for.", + "tl_dr": "DotMaker (dotkernel\/dot-maker) programmatically generates project files and directories matching the Dotkernel file structure inspired by Mezzio.\nIt boosts productivity and enforces consistency and standardization compared to creating modules and files by hand, and it can tell the difference between Dotkernel applications (Api, Admin, Frontend) to create the files each one requires." }, { "post_title": "Evolution Pattern versus API Versioning", @@ -1717,7 +1877,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "In programming and software architecture, an Evolution Pattern is a reusable, high-level strategy for modifying or evolving existing software systems over time. An evolution pattern tries to keep software relevant for old and new users by whatever means are available, as new needs arise." + "excerpt": "In programming and software architecture, an Evolution Pattern is a reusable, high-level strategy for modifying or evolving existing software systems over time. An evolution pattern tries to keep software relevant for old and new users by whatever means are available, as new needs arise.", + "tl_dr": "An Evolution Pattern keeps the same codebase and evolves it gradually (for example via sunsetting), while API versioning maintains multiple parallel versions of an API so existing clients aren't broken.\nThe two are not mutually exclusive.\nDotkernel API favors an evolution pattern with a sunsetting mechanism, reserving full versioning for major, format-level changes." }, { "post_title": "Version 7 adds PostgreSQL, Native UUID and PHP 8.5", @@ -1727,7 +1888,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "The Dotkernel Headless Platform has seen new releases for both API and Admin. The Admin codebase has received an overall facelift, as well as updates to retain compatibility with API v7." + "excerpt": "The Dotkernel Headless Platform has seen new releases for both API and Admin. The Admin codebase has received an overall facelift, as well as updates to retain compatibility with API v7.", + "tl_dr": "Dotkernel API v7 adds support for native UUID v7, PostgreSQL, PHP 8.5 (8.4 for Admin), database table prefixes, and improved database configuration, while replacing the binary data type for id columns with uuid.\nIt drops the Evolution pattern's Method Deprecation support and MySQL, since MySQL doesn't support the UUID data type.\nUUIDs are generated with the ramsey\/uuid package, previously uuid-named table columns are now called id, and PostgreSQL or MariaDB v10.7+ is required for UUID support." }, { "post_title": "Implementing Time-based One-Time Password (TOTP) in Dotkernel", @@ -1737,7 +1899,8 @@ "display_name": "Florin Bidirean", "github": "bidi47" }, - "excerpt": "What TOTP Does A Time-based One-Time Password (TOTP) is a security algorithm used as part of two-factor authentication (2FA) to protect against account attacks. The mechanism is integrated into dot-totp to enhance security by requiring both a password and an additional one-time code." + "excerpt": "What TOTP Does A Time-based One-Time Password (TOTP) is a security algorithm used as part of two-factor authentication (2FA) to protect against account attacks. The mechanism is integrated into dot-totp to enhance security by requiring both a password and an additional one-time code.", + "tl_dr": "dot-totp adds two-factor authentication (2FA) to Dotkernel Admin using time-based one-time passwords.\nUsers authenticate with their password plus a 6-digit code from an Authenticator app that refreshes every 30 seconds.\nInstallation is one Composer command plus a set of forms, handlers, middleware, and templates from the official code examples, applying a TotpTrait to the relevant entity, migrating three new database columns, and registering routes\/pipeline\/ConfigProvider updates." } ] } diff --git a/src/App/src/Handler/GetFeedViewHandler.php b/src/App/src/Handler/GetFeedViewHandler.php new file mode 100644 index 0000000..680aaf5 --- /dev/null +++ b/src/App/src/Handler/GetFeedViewHandler.php @@ -0,0 +1,38 @@ +feedGenerator->getFeedFile(); + + if (! is_file($feedFile) || filesize($feedFile) === 0) { + $this->feedGenerator->write(); + } + + return new XmlResponse( + (string) file_get_contents($feedFile), + 200, + ['content-type' => FeedGenerator::CONTENT_TYPE] + ); + } +} diff --git a/src/App/src/Migration/Version20260728073216.php b/src/App/src/Migration/Version20260728073216.php new file mode 100644 index 0000000..3cf8e14 --- /dev/null +++ b/src/App/src/Migration/Version20260728073216.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE post ADD tl_dr LONGTEXT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE post DROP tl_dr'); + } +} diff --git a/src/App/src/RoutesDelegator.php b/src/App/src/RoutesDelegator.php index b7e03a9..5af2628 100644 --- a/src/App/src/RoutesDelegator.php +++ b/src/App/src/RoutesDelegator.php @@ -5,6 +5,7 @@ namespace Light\App; use Laminas\Diactoros\Response\RedirectResponse; +use Light\App\Handler\GetFeedViewHandler; use Light\App\Handler\GetIndexViewHandler; use Mezzio\Application; use Psr\Container\ContainerInterface; @@ -18,6 +19,7 @@ public function __invoke(ContainerInterface $container, string $serviceName, cal $app = $callback(); assert($app instanceof Application); $app->get('/', [GetIndexViewHandler::class], 'app::index'); + $app->get('/feed/', [GetFeedViewHandler::class], 'app::feed'); $app->get('/{first}', function ($request) { $uri = $request->getUri(); diff --git a/src/App/src/Service/FeedGenerator.php b/src/App/src/Service/FeedGenerator.php new file mode 100644 index 0000000..6ce965c --- /dev/null +++ b/src/App/src/Service/FeedGenerator.php @@ -0,0 +1,81 @@ +feedFile; + } + + public function write(): int + { + $posts = $this->postRepository->findBy( + ['status' => PostStatusEnum::Published], + ['postDate' => 'DESC'] + ); + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + + $rss = $dom->createElement('rss'); + $rss->setAttribute('version', '2.0'); + $dom->appendChild($rss); + + $channel = $dom->createElement('channel'); + $rss->appendChild($channel); + + $this->appendText($dom, $channel, 'title', $this->title); + $this->appendText($dom, $channel, 'link', $this->baseUrl); + $this->appendText($dom, $channel, 'description', $this->description); + + foreach ($posts as $post) { + $link = $this->baseUrl . $post->getCategory()->getSlug() . '/' . $post->getSlug() . '/'; + + $item = $dom->createElement('item'); + $channel->appendChild($item); + + $this->appendText($dom, $item, 'title', $post->getTitle()); + $this->appendText($dom, $item, 'link', $link); + $this->appendText($dom, $item, 'description', $post->getTldr() ?? $post->getExcerpt()); + $this->appendText($dom, $item, 'pubDate', $post->getPostDate()->format(DateTimeInterface::RSS)); + $this->appendText($dom, $item, 'guid', $link); + } + + if ($dom->save($this->feedFile) === false) { + throw new RuntimeException('Unable to write RSS feed.'); + } + + return count($posts); + } + + private function appendText(DOMDocument $dom, DOMElement $parent, string $name, string $text): void + { + $el = $dom->createElement($name); + $el->appendChild($dom->createTextNode($text)); + $parent->appendChild($el); + } +} diff --git a/src/Blog/src/Entity/Post.php b/src/Blog/src/Entity/Post.php index 439ab45..76ec049 100644 --- a/src/Blog/src/Entity/Post.php +++ b/src/Blog/src/Entity/Post.php @@ -27,6 +27,9 @@ class Post extends AbstractEntity #[ORM\Column(name: 'excerpt', type: 'text')] private string $excerpt; + #[ORM\Column(name: 'tl_dr', type: 'text', nullable: true)] + private ?string $tlDr = null; + #[ORM\Column( name: 'status', type: 'post_status_enum', @@ -113,6 +116,16 @@ public function setExcerpt(string $excerpt): void $this->excerpt = $excerpt; } + public function getTldr(): ?string + { + return $this->tlDr; + } + + public function setTldr(?string $tlDr): void + { + $this->tlDr = $tlDr; + } + /** * @return array{ * id: non-empty-string, @@ -120,6 +133,7 @@ public function setExcerpt(string $excerpt): void * slug: string, * status: string, * excerpt: string, + * tlDr: string|null, * postDate: string, * category: array{id: non-empty-string, name: string, slug: string}, * author: array{id: non-empty-string, name: string, slug: string, github: string|null} @@ -133,6 +147,7 @@ public function getArrayCopy(): array 'slug' => $this->slug, 'status' => $this->status->value, 'excerpt' => $this->excerpt, + 'tlDr' => $this->tlDr, 'postDate' => $this->postDate->format('Y-m-d H:i:s'), 'category' => $this->category->getArrayCopy(), 'author' => $this->author->getArrayCopy(), From 69a3db22efcd7f044804318897dd913e035a9db4 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Wed, 29 Jul 2026 13:42:10 +0300 Subject: [PATCH 02/10] Issue 23 dotkernel.com - Added RSS --- src/App/templates/layout/default.html.twig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/App/templates/layout/default.html.twig b/src/App/templates/layout/default.html.twig index de25379..325a9c4 100644 --- a/src/App/templates/layout/default.html.twig +++ b/src/App/templates/layout/default.html.twig @@ -14,6 +14,7 @@ + @@ -86,7 +87,7 @@ Slack - + RSS Feed From 1423678f6d8a52c58ec34276722f94b9d4918382 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Wed, 29 Jul 2026 14:27:41 +0300 Subject: [PATCH 03/10] Issue 23 dotkernel.com - Add updated date and image to RSS feed --- src/App/src/Factory/FeedGeneratorFactory.php | 1 + src/App/src/Service/FeedGenerator.php | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/App/src/Factory/FeedGeneratorFactory.php b/src/App/src/Factory/FeedGeneratorFactory.php index 7544583..7b1b89f 100644 --- a/src/App/src/Factory/FeedGeneratorFactory.php +++ b/src/App/src/Factory/FeedGeneratorFactory.php @@ -26,6 +26,7 @@ public function __invoke(ContainerInterface $container): FeedGenerator rtrim($config['application']['url'] ?? '', '/') . '/', $config['app']['meta']['title'] ?? '', $config['app']['meta']['description'] ?? '', + $config['app']['meta']['image'] ?? '', ); } } diff --git a/src/App/src/Service/FeedGenerator.php b/src/App/src/Service/FeedGenerator.php index 6ce965c..9d06b76 100644 --- a/src/App/src/Service/FeedGenerator.php +++ b/src/App/src/Service/FeedGenerator.php @@ -4,6 +4,7 @@ namespace Light\App\Service; +use DateTimeImmutable; use DateTimeInterface; use DOMDocument; use DOMElement; @@ -17,12 +18,15 @@ class FeedGenerator { public const CONTENT_TYPE = 'application/rss+xml; charset=UTF-8'; + private const MEDIA_NAMESPACE = 'http://search.yahoo.com/mrss/'; + public function __construct( private readonly PostRepository $postRepository, private readonly string $feedFile, private readonly string $baseUrl, private readonly string $title, private readonly string $description, + private readonly string $image, ) { } @@ -43,6 +47,7 @@ public function write(): int $rss = $dom->createElement('rss'); $rss->setAttribute('version', '2.0'); + $rss->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:media', self::MEDIA_NAMESPACE); $dom->appendChild($rss); $channel = $dom->createElement('channel'); @@ -51,6 +56,7 @@ public function write(): int $this->appendText($dom, $channel, 'title', $this->title); $this->appendText($dom, $channel, 'link', $this->baseUrl); $this->appendText($dom, $channel, 'description', $this->description); + $this->appendText($dom, $channel, 'lastBuildDate', (new DateTimeImmutable())->format(DateTimeInterface::RSS)); foreach ($posts as $post) { $link = $this->baseUrl . $post->getCategory()->getSlug() . '/' . $post->getSlug() . '/'; @@ -62,7 +68,15 @@ public function write(): int $this->appendText($dom, $item, 'link', $link); $this->appendText($dom, $item, 'description', $post->getTldr() ?? $post->getExcerpt()); $this->appendText($dom, $item, 'pubDate', $post->getPostDate()->format(DateTimeInterface::RSS)); + $this->appendText($dom, $item, 'updatedAt', $post->getUpdatedFormatted(DateTimeInterface::RSS) ?? $post->getPostDate()->format(DateTimeInterface::RSS)); $this->appendText($dom, $item, 'guid', $link); + + if ($this->image !== '') { + $media = $dom->createElementNS(self::MEDIA_NAMESPACE, 'media:content'); + $media->setAttribute('url', $this->image); + $media->setAttribute('medium', 'image'); + $item->appendChild($media); + } } if ($dom->save($this->feedFile) === false) { From 6da8b52dd98ab384bca041172d2725624058788b Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Wed, 29 Jul 2026 14:31:30 +0300 Subject: [PATCH 04/10] Fixed error in generate-feed command execution --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f0e2240..2c3a7c1 100644 --- a/composer.json +++ b/composer.json @@ -71,7 +71,7 @@ ], "post-update-cmd": [ "php bin/composer-post-install-script.php", - "php bin/generate-feed.php" + "php bin/generate-feed" ], "development-disable": "laminas-development-mode disable", "development-enable": "laminas-development-mode enable", From e92ccfb52d4387353e3d54b7cddf0b3c992fe7b4 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Wed, 29 Jul 2026 14:36:33 +0300 Subject: [PATCH 05/10] Fixed error in generate-feed command execution --- bin/{generate-feed => generate-feed.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bin/{generate-feed => generate-feed.php} (100%) diff --git a/bin/generate-feed b/bin/generate-feed.php similarity index 100% rename from bin/generate-feed rename to bin/generate-feed.php From 1a3dceb0f7581da0040737c794e4db77dadd9179 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Wed, 29 Jul 2026 14:49:53 +0300 Subject: [PATCH 06/10] Fixed error in generate-feed command execution --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2c3a7c1..f0e2240 100644 --- a/composer.json +++ b/composer.json @@ -71,7 +71,7 @@ ], "post-update-cmd": [ "php bin/composer-post-install-script.php", - "php bin/generate-feed" + "php bin/generate-feed.php" ], "development-disable": "laminas-development-mode disable", "development-enable": "laminas-development-mode enable", From bdb8eca66911f3b55db1e6333a2f3ddad6a77cbd Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Wed, 29 Jul 2026 15:03:51 +0300 Subject: [PATCH 07/10] Fixed error in generate-feed command execution --- bin/{generate-feed.php => generate-feed} | 0 composer.json | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) rename bin/{generate-feed.php => generate-feed} (100%) diff --git a/bin/generate-feed.php b/bin/generate-feed similarity index 100% rename from bin/generate-feed.php rename to bin/generate-feed diff --git a/composer.json b/composer.json index f0e2240..f0b2ea8 100644 --- a/composer.json +++ b/composer.json @@ -70,8 +70,7 @@ "@development-enable" ], "post-update-cmd": [ - "php bin/composer-post-install-script.php", - "php bin/generate-feed.php" + "php bin/composer-post-install-script.php" ], "development-disable": "laminas-development-mode disable", "development-enable": "laminas-development-mode enable", From f2deb248ba179ba1493c2ed82a555d4a7f641262 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Wed, 29 Jul 2026 15:10:24 +0300 Subject: [PATCH 08/10] Fix for test errors --- src/App/src/Service/FeedGenerator.php | 6 +----- src/Blog/src/Repository/PostRepository.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/App/src/Service/FeedGenerator.php b/src/App/src/Service/FeedGenerator.php index 9d06b76..46fd4f1 100644 --- a/src/App/src/Service/FeedGenerator.php +++ b/src/App/src/Service/FeedGenerator.php @@ -8,7 +8,6 @@ use DateTimeInterface; use DOMDocument; use DOMElement; -use Light\Blog\Enum\PostStatusEnum; use Light\Blog\Repository\PostRepository; use RuntimeException; @@ -37,10 +36,7 @@ public function getFeedFile(): string public function write(): int { - $posts = $this->postRepository->findBy( - ['status' => PostStatusEnum::Published], - ['postDate' => 'DESC'] - ); + $posts = $this->postRepository->getPublishedPosts(); $dom = new DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; diff --git a/src/Blog/src/Repository/PostRepository.php b/src/Blog/src/Repository/PostRepository.php index 63abf63..db43225 100644 --- a/src/Blog/src/Repository/PostRepository.php +++ b/src/Blog/src/Repository/PostRepository.php @@ -100,6 +100,21 @@ public function getAdjacentPosts(Post $post): array return ['previous' => $previous, 'next' => $next]; } + /** + * @return array + */ + public function getPublishedPosts(): array + { + return $this->getQueryBuilder() + ->select('articles') + ->from(Post::class, 'articles') + ->where('articles.status = :published') + ->setParameter('published', PostStatusEnum::Published) + ->orderBy('articles.postDate', 'DESC') + ->getQuery() + ->getResult(); + } + /** * @return array */ From 7e4e6988d94072e060dc9d5d9cfb63667ca5528b Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Wed, 29 Jul 2026 15:17:51 +0300 Subject: [PATCH 09/10] Cs-fix --- src/App/src/Service/FeedGenerator.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/App/src/Service/FeedGenerator.php b/src/App/src/Service/FeedGenerator.php index 46fd4f1..62a978e 100644 --- a/src/App/src/Service/FeedGenerator.php +++ b/src/App/src/Service/FeedGenerator.php @@ -64,7 +64,13 @@ public function write(): int $this->appendText($dom, $item, 'link', $link); $this->appendText($dom, $item, 'description', $post->getTldr() ?? $post->getExcerpt()); $this->appendText($dom, $item, 'pubDate', $post->getPostDate()->format(DateTimeInterface::RSS)); - $this->appendText($dom, $item, 'updatedAt', $post->getUpdatedFormatted(DateTimeInterface::RSS) ?? $post->getPostDate()->format(DateTimeInterface::RSS)); + $this->appendText( + $dom, + $item, + 'updatedAt', + $post->getUpdatedFormatted(DateTimeInterface::RSS) ?? + $post->getPostDate()->format(DateTimeInterface::RSS) + ); $this->appendText($dom, $item, 'guid', $link); if ($this->image !== '') { From ab08d61cd3c3a25f27c69261ff1ca44bec3fcaa6 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Wed, 29 Jul 2026 17:42:34 +0300 Subject: [PATCH 10/10] Issue 23 code improvement + add 404 if something happens and cannot generate feed --- bin/generate-feed | 2 +- config/autoload/app.global.php | 2 +- src/App/src/Factory/FeedGeneratorFactory.php | 2 +- .../src/Factory/GetFeedViewHandlerFactory.php | 10 +++++- src/App/src/Fixture/PostLoader.php | 2 +- src/App/src/Handler/GetFeedViewHandler.php | 34 +++++++++++++++++-- src/App/src/Service/FeedGenerator.php | 2 +- src/Blog/src/Entity/Post.php | 4 +-- 8 files changed, 47 insertions(+), 11 deletions(-) diff --git a/bin/generate-feed b/bin/generate-feed index dcb6e86..4f468a6 100755 --- a/bin/generate-feed +++ b/bin/generate-feed @@ -20,4 +20,4 @@ printf( $count === 1 ? '' : 's', $feedGenerator->getFeedFile(), PHP_EOL -); \ No newline at end of file +); diff --git a/config/autoload/app.global.php b/config/autoload/app.global.php index 2a7a905..441f2b4 100644 --- a/config/autoload/app.global.php +++ b/config/autoload/app.global.php @@ -23,7 +23,7 @@ return [ 'app' => $app, 'feed' => [ - 'file' => 'public/feed.xml', + 'path' => realpath(__DIR__ . '/../../public/feed.xml'), ], 'twig' => [ 'globals' => [ diff --git a/src/App/src/Factory/FeedGeneratorFactory.php b/src/App/src/Factory/FeedGeneratorFactory.php index 7b1b89f..7b5be61 100644 --- a/src/App/src/Factory/FeedGeneratorFactory.php +++ b/src/App/src/Factory/FeedGeneratorFactory.php @@ -22,7 +22,7 @@ public function __invoke(ContainerInterface $container): FeedGenerator return new FeedGenerator( $postRepository, - $config['feed']['file'], + $config['feed']['path'], rtrim($config['application']['url'] ?? '', '/') . '/', $config['app']['meta']['title'] ?? '', $config['app']['meta']['description'] ?? '', diff --git a/src/App/src/Factory/GetFeedViewHandlerFactory.php b/src/App/src/Factory/GetFeedViewHandlerFactory.php index 60d68f5..491f0fe 100644 --- a/src/App/src/Factory/GetFeedViewHandlerFactory.php +++ b/src/App/src/Factory/GetFeedViewHandlerFactory.php @@ -6,6 +6,8 @@ use Light\App\Handler\GetFeedViewHandler; use Light\App\Service\FeedGenerator; +use Light\Blog\Repository\CategoryRepository; +use Mezzio\Template\TemplateRendererInterface; use Psr\Container\ContainerInterface; use function assert; @@ -17,9 +19,15 @@ class GetFeedViewHandlerFactory */ public function __invoke(ContainerInterface $container, string $requestedName): GetFeedViewHandler { + $template = $container->get(TemplateRendererInterface::class); + assert($template instanceof TemplateRendererInterface); + + $categoryRepository = $container->get(CategoryRepository::class); + assert($categoryRepository instanceof CategoryRepository); + $feedGenerator = $container->get(FeedGenerator::class); assert($feedGenerator instanceof FeedGenerator); - return new GetFeedViewHandler($feedGenerator); + return new GetFeedViewHandler($template, $categoryRepository, $feedGenerator); } } diff --git a/src/App/src/Fixture/PostLoader.php b/src/App/src/Fixture/PostLoader.php index 52e3667..8d63b72 100644 --- a/src/App/src/Fixture/PostLoader.php +++ b/src/App/src/Fixture/PostLoader.php @@ -119,7 +119,7 @@ public function load(ObjectManager $manager): void $article->setExcerpt($excerpt); $changed = true; } - if ($article->getTldr() !== $tlDr) { + if ($article->getTlDr() !== $tlDr) { $article->setTldr($tlDr); $changed = true; } diff --git a/src/App/src/Handler/GetFeedViewHandler.php b/src/App/src/Handler/GetFeedViewHandler.php index 680aaf5..419bff2 100644 --- a/src/App/src/Handler/GetFeedViewHandler.php +++ b/src/App/src/Handler/GetFeedViewHandler.php @@ -4,11 +4,17 @@ namespace Light\App\Handler; +use Fig\Http\Message\StatusCodeInterface; +use Laminas\Diactoros\Response\HtmlResponse; use Laminas\Diactoros\Response\XmlResponse; use Light\App\Service\FeedGenerator; +use Light\Blog\Entity\Category; +use Light\Blog\Repository\CategoryRepository; +use Mezzio\Template\TemplateRendererInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; +use Throwable; use function file_get_contents; use function filesize; @@ -17,6 +23,8 @@ class GetFeedViewHandler implements RequestHandlerInterface { public function __construct( + private readonly TemplateRendererInterface $template, + private readonly CategoryRepository $categoryRepository, private readonly FeedGenerator $feedGenerator, ) { } @@ -26,13 +34,33 @@ public function handle(ServerRequestInterface $request): ResponseInterface $feedFile = $this->feedGenerator->getFeedFile(); if (! is_file($feedFile) || filesize($feedFile) === 0) { - $this->feedGenerator->write(); + try { + $this->feedGenerator->write(); + } catch (Throwable) { + } + } + + if (! is_file($feedFile) || filesize($feedFile) === 0) { + return $this->notFound($this->categoryRepository->getCategories()); } return new XmlResponse( (string) file_get_contents($feedFile), - 200, - ['content-type' => FeedGenerator::CONTENT_TYPE] + StatusCodeInterface::STATUS_OK, + ['Content-Type' => FeedGenerator::CONTENT_TYPE] + ); + } + + /** + * @param Category[] $categories + */ + private function notFound(array $categories): HtmlResponse + { + return new HtmlResponse( + $this->template->render('error::404', [ + 'categories' => $categories, + ]), + StatusCodeInterface::STATUS_NOT_FOUND ); } } diff --git a/src/App/src/Service/FeedGenerator.php b/src/App/src/Service/FeedGenerator.php index 62a978e..fc186b8 100644 --- a/src/App/src/Service/FeedGenerator.php +++ b/src/App/src/Service/FeedGenerator.php @@ -62,7 +62,7 @@ public function write(): int $this->appendText($dom, $item, 'title', $post->getTitle()); $this->appendText($dom, $item, 'link', $link); - $this->appendText($dom, $item, 'description', $post->getTldr() ?? $post->getExcerpt()); + $this->appendText($dom, $item, 'description', $post->getTlDr() ?? $post->getExcerpt()); $this->appendText($dom, $item, 'pubDate', $post->getPostDate()->format(DateTimeInterface::RSS)); $this->appendText( $dom, diff --git a/src/Blog/src/Entity/Post.php b/src/Blog/src/Entity/Post.php index 76ec049..9dd5235 100644 --- a/src/Blog/src/Entity/Post.php +++ b/src/Blog/src/Entity/Post.php @@ -116,12 +116,12 @@ public function setExcerpt(string $excerpt): void $this->excerpt = $excerpt; } - public function getTldr(): ?string + public function getTlDr(): ?string { return $this->tlDr; } - public function setTldr(?string $tlDr): void + public function setTlDr(?string $tlDr): void { $this->tlDr = $tlDr; }