diff --git a/composer.json b/composer.json index accf000f..2216e27e 100644 --- a/composer.json +++ b/composer.json @@ -36,12 +36,15 @@ "dotkernel/dot-response-header": "^3.6", "laminas/laminas-component-installer": "^3.5.0", "laminas/laminas-config-aggregator": "^1.17.0", + "league/commonmark": "^2.10", "mezzio/mezzio": "^3.24.0", "mezzio/mezzio-fastroute": "^3.13.0", "mezzio/mezzio-twigrenderer": "^2.17.0", "ramsey/uuid": "^4.5.0", "ramsey/uuid-doctrine": "^2.1.0", - "roave/psr-container-doctrine": "^5.2.2 || ^6.0.0" + "roave/psr-container-doctrine": "^5.2.2 || ^6.0.0", + "symfony/yaml": "^8.1", + "twig/markdown-extra": "^3.28" }, "require-dev": { "filp/whoops": "^2.17.0", diff --git a/config/autoload/templates.global.php b/config/autoload/templates.global.php index 54c9f4e9..a8ec466c 100644 --- a/config/autoload/templates.global.php +++ b/config/autoload/templates.global.php @@ -2,6 +2,9 @@ declare(strict_types=1); +use Twig\Extra\Markdown\MarkdownExtension; +use Twig\RuntimeLoader\RuntimeLoaderInterface; + return [ 'templates' => [ 'extension' => 'html.twig', @@ -12,10 +15,14 @@ 'autoescape' => 'html', 'auto_reload' => true, 'cache_dir' => 'data/cache/twig', - 'extensions' => [], + 'extensions' => [ + MarkdownExtension::class, + ], 'globals' => [], 'optimizations' => -1, - 'runtime_loaders' => [], + 'runtime_loaders' => [ + RuntimeLoaderInterface::class, + ], 'timezone' => 'UTC', ], ]; diff --git a/public/md-articles/architecture/configprovider-bootstrap-modern-php-applications.md b/public/md-articles/architecture/configprovider-bootstrap-modern-php-applications.md index 2dc47fdf..992aca41 100644 --- a/public/md-articles/architecture/configprovider-bootstrap-modern-php-applications.md +++ b/public/md-articles/architecture/configprovider-bootstrap-modern-php-applications.md @@ -34,7 +34,10 @@ class ConfigProvider { public function __invoke(): array { - return [ /* ... */ ]; + return [ + 'dependencies' => $this->getDependencies(), + 'templates' => $this->getTemplates(), + ]; } public function getDependencies(): array @@ -59,7 +62,7 @@ What each item means: | Item | Meaning | |---|---| -| `dependencies` | Used by the dependency injector (e.g. laminas-servicemanager) to construct every requested service. | +| `dependencies` | Used by the dependency injector (e.g. [laminas-servicemanager](https://docs.mezzio.dev/mezzio/v3/features/container/laminas-servicemanager/)) to construct every requested service. | | `factories` | The factory builds the service. | | `invokables` | The service is built with `new` directly. | | `aliases` | Redirects to another service name. | @@ -74,7 +77,11 @@ The ConfigProvider is automatically picked up by the framework during applicatio 2. **Read the configuration array** - A call similar to `$config = $container->get('config') ?? [];` reads an array of entries. 3. **Resolve item** - `$app->pipe()` is called to resolve one of the following: resolve the service name from the container, wrap the middleware if an array is provided, or call the closure or invokable object. 4. **Handle errors** - The error-handling middleware is the last one in the pipeline, to make sure it can handle any exceptions. -5. **Execute at runtime** - Laminas Stratigility iterates over the pipeline in the order it was registered. Each middleware can handle the request and return a response, or delegate execution to the next middleware in the pipeline, until a `ResponseInterface` is returned to the client. +5. **Execute at runtime** - [Laminas Stratigility](https://docs.laminas.dev/laminas-stratigility/) iterates over the pipeline in the order it was registered. Each middleware can handle the request and return a response, or delegate execution to the next middleware in the pipeline, until a `ResponseInterface` is returned to the client. + +Below you can see how Mezzio and Dotkernel merge and use ConfigProviders to build the middleware pipeline and dependencies. + +![](/uploads/article/019f8a80-cc92-7277-92c8-c0e68d81615f/ConfigProvider2.png) ## Benefits @@ -82,7 +89,7 @@ The ConfigProvider is automatically picked up by the framework during applicatio - **Modular** - Each package can ship with its own config without interfering with others. - **Container-friendly** - Works well with frameworks using DI containers like Laminas ServiceManager, PHP-DI, or Pimple. - **Standardized service definitions** - Consistent rules for object creation, separate from business logic. -- **Auto-Discovery** - In Laminas/Mezzio, the ConfigAggregator automatically loads and merges all ConfigProviders. +- **Auto-Discovery** - In Laminas/Mezzio, the [ConfigAggregator](https://docs.laminas.dev/laminas-config-aggregator/) automatically loads and merges all ConfigProviders. Dotkernel is an exception: new ConfigProviders have to be added manually in `config/config.php`, because all the initial ConfigProviders required to install the applications are already injected. - **Environment-agnostic** - Returns an array that defines dev, test, or prod environments. - **Testability** - The consistent, central configuration promotes isolated (e.g. per-module) testing, easier swapping of dependencies, and assertion of pipeline setup (e.g. checking if a config key is present). diff --git a/public/md-articles/best-practice/aptana-set-svn-keywords.md b/public/md-articles/best-practice/aptana-set-svn-keywords.md index 0514236d..f9e55a12 100644 --- a/public/md-articles/best-practice/aptana-set-svn-keywords.md +++ b/public/md-articles/best-practice/aptana-set-svn-keywords.md @@ -10,31 +10,37 @@ language: "en" # Aptana - set SVN keywords -## Overview +In Aptana it's very simple to set the [svn:keywords](http://svnbook.red-bean.com/en/1.4/svn.advanced.props.special.keywords.html) property for a file. -In Aptana it's very simple to set the svn:keywords property for a file. -For example, to set the svn keyword property `Id`: +For example if you want to set the svn keyword property ***Id***: -## Steps +1. In the file where you want to add the svn keyword property write **$Id$** -1. In the file where the svn keyword property should be added, write `$Id$`. -2. Right click on the file, then follow Team -> Set Property... -(Note: "Set Property..." will not be active if the file hasn't first been added to SVN via Team -> Add to Version Controller). -3. Select `svn:keywords`, and write `Id` in the text field. +![](/uploads/article/019f8a80-cc86-73d9-a427-0621b2a55777/id-file-300x235.gif) -When the SVN commit of the file is made, the `$Id$` keyword will be replaced with text containing the file's SVN metadata, in a specific format. +2. Right click on the file, then follow Team -> Set Property...**Note**: *Set Property...* will not be active if you haven't first added the file to SVN: *Team*->*Add to Version Controller* + +![](/uploads/article/019f8a80-cc86-73d9-a427-0621b2a55777/set-property-300x152.gif) + +3. Select **svn:keywords**, and write **Id** in the text field + +![](/uploads/article/019f8a80-cc86-73d9-a427-0621b2a55777/svn-keywords-300x298.gif) + +When you make the SVN commit of the file, the *$Id$* keyword will be replaced with text in the format shown below: + +![](/uploads/article/019f8a80-cc86-73d9-a427-0621b2a55777/id-file-svn-300x141.gif) ## FAQ **Q: How do you set the svn:keywords property for a file in Aptana?** -A: Write the keyword marker (for example `$Id$`) in the file, then right click the file and follow Team -> Set Property..., select `svn:keywords`, and write `Id` in the text field. +A: Write the keyword marker (for example $Id$) in the file, then right click the file and follow Team -> Set Property..., select svn:keywords, and write Id in the text field. **Q: Why is "Set Property..." not active when I right click the file?** A: Set Property... will not be active if the file hasn't first been added to SVN. Use Team -> Add to Version Controller before trying to set the property. **Q: What happens to the $Id$ keyword after an SVN commit?** -A: After the SVN commit of the file, the `$Id$` keyword is replaced with text containing the file's SVN metadata, in a specific format. +A: After the SVN commit of the file, the $Id$ keyword is replaced with text containing the file's SVN metadata, in a specific format. ## Resources diff --git a/public/md-articles/best-practice/basic-security-in-dotkernel-headless-platform.md b/public/md-articles/best-practice/basic-security-in-dotkernel-headless-platform.md index 5ee7435c..a3ac45df 100644 --- a/public/md-articles/best-practice/basic-security-in-dotkernel-headless-platform.md +++ b/public/md-articles/best-practice/basic-security-in-dotkernel-headless-platform.md @@ -14,9 +14,9 @@ language: "en" 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. -## Facets of Software Security +**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. You ignore security at your own risk, with potentially major costs to your personal or your organization's image, and to your client's trust in your abilities. The costs to recover the damages caused by a lacking security are sometimes astronomical, enough to put a company out of business. -There are many potential ways a hacker can access code or data fraudulently: +There are many facets of software security, meaning there are a lot of potential ways a hacker can access your code or your data fraudulently: - Authentication and access control. - Data protection. @@ -29,97 +29,132 @@ There are many potential ways a hacker can access code or data fraudulently: - Secure software development lifecycle. - Human and organizational factors. +To keep a platform safe, you must actively mitigate these risks with recommended coding practices that include tight security. You wouldn't build a nice house, only to leave the door unlocked, right? + ## The Tenets of Software Security in Dotkernel Headless Platform -Dotkernel aims to: +We at Dotkernel aim to: - Create code that follows software security guidelines. - Implement community recommendations related to software security. - Use 3rd-party code and libraries from trusted sources. - Constantly monitor software news related to security vulnerabilities and mitigate them as soon as possible. +We do all this to attempt to stay ahead of the vulnerabilities that can lead from otherwise useful, productive code, to data loss and a GDPR fine, or a loss of funds for our clients. + +Let's take a practical view on software security in Dotkernel. + ## Form Input Validation -Never trust that user input is correct by passing it directly into business logic. -By defining the configuration for an input filter, a field's presence and type are both ensured. Dotkernel API uses laminas/laminas-inputfilter for this purpose. -Dotkernel Admin additionally uses laminas/laminas-form, which contains a thin layer of objects representing form elements, an InputFilter for each input (or custom validators), and methods for binding data to and from the form. laminas-form integrates with the Laminas Security Ecosystem: laminas-escaper, laminas-validator, laminas-session, and laminas-filter. +You should never trust that the user inputs correct data by passing it directly into your business logic. By defining the configuration for an input filter, you ensure that a field is both present, and of the correct type. + +[Dotkernel API](https://www.dotkernel.org) makes use of [laminas/laminas-inputfilter](https://github.com/laminas/laminas-inputfilter) for this purpose. + +In addition to the above filtering, Dotkernel Admin also makes use of [laminas/laminas-form](https://github.com/laminas/laminas-form). laminas-form contains: + +- A thin layer of objects representing form elements. +- An InputFilter for each input, like mentioned previously, or custom validators. +- Methods for binding data to and from the form. + +laminas-form ensures that data validation, filtering, and rendering enforce strong security practices by design. It also has integration with the Laminas Security Ecosystem that contains laminas-escaper, laminas-validator, laminas-session, and laminas-filter. ## Content Negotiation -Content negotiation is used in RESTful APIs so client and server agree on the format and language of exchanged data. -Dotkernel API handles this via a middleware configured in `config/autoload/content-negotiation.global.php`, using the `Content-Type` and `Accept` HTTP request headers, and returning `application/json` or `application/hal+json` data formats. +Content negotiation is used in RESTful APIs to ensure that systems work seamlessly together by having the client and server agree on the format and language of data they exchange. + +Dotkernel API handles content negotiation via a middleware configured in the `config/autoload/content-negotiation.global.php` file. It handles client-side content negotiation via the use of two HTTP request headers: `Content-Type` and `Accept`, and returns `application/json`, `application/hal+json` data formats. ## Cross-Origin Resource Sharing -CORS is a browser security mechanism controlling how web pages can request resources from a different domain. -In Dotkernel API, CORS is handled by mezzio/mezzio-cors and configured in `config/autoload/cors.local.php`. -It starts detecting the proper `cors` configuration whenever it detects a `cors preflight`, validating the call using configuration items: origins, headers, max age, and credentials. +Cross-Origin Resource Sharing (or CORS) is a security mechanism implemented into web browsers to control how web pages can request resources from a different domain than the one where the request originated from. + +In Dotkernel API, CORS is handled by [mezzio/mezzio-cors](https://github.com/mezzio/mezzio-cors) and configured in the `config/autoload/cors.local.php` file. mezzio-cors starts to detect the proper `cors` configuration whenever it detects a `cors preflight`. Cors validates the call using several configuration items: origins, headers, max age, credentials. > When configuring your pipeline, make sure to add the CorsMiddleware BEFORE the RouteMiddleware. ## Role-Based Access Control -RBAC manages access to resources by assigning roles to user types, which are in turn assigned to users requiring a certain level of access. -Dotkernel API uses mezzio/mezzio-authorization-rbac for this purpose, with several predefined roles configurable in `config/autoload/authorization.global.php`. +Role-Based Access Control (or RBAC) is a security model used in software systems to manage access to resources. It does this by assigning roles to user types which are in turn assigned to users who require a certain level of access. + +Dotkernel API uses [mezzio/mezzio-authorization-rbac](https://github.com/mezzio/mezzio-authorization-rbac) for this purpose. There are several roles predefined, which you can configure to suit your project by editing the `config/autoload/authorization.global.php` file. ## Demo Credentials -Demo credentials are provided in Dotkernel API for convenience, to allow easy testing of the installation. +Demo credentials are provided in Dotkernel API for your convenience, to allow you to test the installation easily. -> It is important to update or remove these accounts in your production environment. +> It is important to **update or remove** these accounts in your production environment. ## Error Reporting Endpoint and ErrorReportingTokens -The error reporting endpoint provides a reliable channel through which 3rd-party developers can report issues directly. -Dotkernel API has a dedicated `/error-report` endpoint for this, using an `ErrorReportingToken` set up in `config/autoload/error-handling.global.php`. +The purpose for the error reporting endpoint is to have a reliable channel through which 3rd-party developers can report issues to you directly. + +Dotkernel API has a dedicated endpoint `/error-report` for this purpose. It uses an `ErrorReportingToken` set up in the configuration file `config/autoload/error-handling.global.php`. ## OpenAPI Documentation -OpenAPI documentation (formerly Swagger) provides a standardized, machine-readable way to describe API requests and responses. -It's critical for developer efficiency (streamlines front/back-end communication, allows mock servers before the backend is implemented), reliability (auto-generated docs, easier testing), and integration (tools like Postman and Codegen libraries). -Dotkernel API implements zircote/swagger-php to provide interactive documentation. +OpenAPI documentation (formerly known as Swagger) provides a standardized, machine-readable way to describe APIs, meaning their `requests` and `responses`. It's critical for: -> Do not include sensitive information for your endpoints. -> Do not enable documentation in a production environment. +- Developer efficiency - it streamlines communication between front and back end developers, and it allows developers to use mock servers before the backend is fully implemented. +- Reliability - documentation can be auto-generated, testing is easier. +- Integration - several tools support OpenAPI, like Postman and Codegen libraries for multiple libraries. + +Dotkernel API implements [zircote/swagger-php](https://github.com/zircote/swagger-php) to provide an interactive documentation. + +> Do **not** include sensitive information for your endpoints. Do **not** enable documentation in a production environment. ## PHP Dependencies -Modern PHP projects rely heavily on external packages via Composer, and there is a tangible risk of exposing an application through insecure dependencies. -Dotkernel API has regular checks for vulnerable and outdated packages, including transient dependencies. +Modern PHP projects rely heavily on external packages via package managers like Composer. There is a tangible risk of exposing your application by using insecure dependencies. + +Dotkernel API has regular checks for vulnerable and outdated packages. Often the dependencies used in projects have transient dependencies which must also be checked. > Always use dependencies from reliable sources and keep them updated to their latest version. ## OAuth2 Security -OAuth 2.0 is a secure authorization framework letting one application access resources on behalf of a user without requiring the user's password, an industry standard for web, mobile, and API-based systems. Dotkernel API uses mezzio/mezzio-authentication-oauth2 for OAuth2 authentication. -The package itself is secure, but it must be used properly: +OAuth 2.0 is a secure authorization framework that allows one application to access resources or data on behalf of a user, without requiring the user's password. It is considered an industry standard for secure authorization across web, mobile, and API-based systems. -- Replace or update the default `admin` and `frontend` clients on production. -- Update the `access` and `refresh` tokens to match your application's requirements (defaults are one day for access, one month for refresh). -- Never commit any local keys generated by `./vendor/bin/generate-oauth2-keys`, since they verify the transmitted JWTs. +Dotkernel API uses the [mezzio/mezzio-authentication-oauth2](https://github.com/mezzio/mezzio-authentication-oauth2) for the OAuth2 authentication service. The package itself is secure, but you still need to make sure you use it properly: + +- Replace or update the default `admin` and `frontend` clients on your production environment. +- Update the `access` and `refresh` tokens to match your application's requirements. The defaults are one day for `access` and one month for `refresh`. +- Make sure to **not** commit any local keys generated by `./vendor/bin/generate-oauth2-keys`. They are used to verify the transmitted JWTs. ## Session and Cookie Settings -Sessions and cookies store data between HTTP requests, such as login information, preferences, or user behavior tracking. -Dotkernel configures cookies in `config/autoload/session.global.php`, which contains parameters that must be revised and adapted: +Sessions and cookies are used in web development to store data between HTTP requests. For example, they can be used to save login information or preferences, and to track user behavior. + +Dotkernel configures cookies in the `config/autoload/session.global.php` file. It contains several parameters that you must revise and adapt to your application: -- `session_config.cookie_httponly` -- `session_config.cookie_samesite` -- `session_config.cookie_secure` +- session_config.cookie_httponly +- session_config.cookie_samesite +- session_config.cookie_secure ## JavaScript Dependencies -JavaScript has its own dependencies, usually installed via npm or yarn. -The JavaScript ecosystem has recently been attacked by hackers targeting several widely used npm packages with billions of total uses. -Dotkernel uses npm to handle JavaScript dependencies, monitors the news for security issues, and uses packages from reliable sources. -`npm audit` should still be used regularly to check for vulnerabilities. +Very much like `composer` for PHP, JavaScript has its own dependencies, usually installed via `npm` or `yarn`. The JavaScript ecosystem has recently been attacked by hackers who targeted several widely used npm packages that have a total number of uses in the billions. + +Dotkernel uses `npm` to handle JavaScript dependencies. We monitor the news to stay on top of these security issues and use npm packages from reliable sources. Even so, you should regularly use the `npm audit` to check for vulnerabilities among your installed npm libraries. ## Other Security Considerations -All components of Dotkernel Headless Platform have configuration files named `*.global.php`, `*.php.dist`, and `*.local.php`. -Sensitive information must only go in `*.local.php` files, since they are ignored by the VCS by default. -Development mode enables features like debug mode, cache clear, and error details, which should be hidden from production to avoid exposing sensitive data or code. -The Laminas Continuous Integration GitHub Action is integral to Dotkernel API, running a matrix of static analysis, coding standards checks, and unit tests, most often triggered by commits. +All components of **Dotkernel Headless Platform** have several configuration files with the name format `*.global.php`, `*.php.dist` and `*.local.php`. You must **only** include sensitive information in the `*.local.php` files, since they are, by default, ignored by the VCS. + +The `development mode` is designed, as the name suggests, only for the development period. By enabling development mode, you enable features like debug mode, cache clear and show error details. These should be hidden from the production environment to avoid exposing sensitive data or code. + +The GitHub Action [Laminas Continuous Integration](https://github.com/laminas/laminas-continuous-integration-action) is an integral component of Dotkernel API. It ensures code quality by streamlining the execution of PHP quality assurance (QA) tasks within continuous integration (CI) workflows. Most often triggered by commits to the repository, it builds a matrix of tests: static analysis, coding standards checks, and unit tests. + +## Resources + +- [Basic Security in Dotkernel Admin](https://docs.dotkernel.org/admin-documentation/v6/security/basic-security/) +- [Basic Security in Dotkernel API](https://docs.dotkernel.org/api-documentation/v6/security/basic-security/) +- [Content Negotiation in Dotkernel REST API](https://www.dotkernel.com/dotkernel-api/content-negotiation-in-dotkernel-rest-api/) +- [laminas-form Documentation](https://docs.laminas.dev/laminas-form/v3/intro/) +- [CORS in Dotkernel API](https://docs.dotkernel.org/api-documentation/v6/tutorials/cors/) +- [CORS Policy Setup in Dotkernel](https://www.dotkernel.com/how-to/mezzio-cors-implementation-in-dotkernel/) +- [Error Reporting Endpoint](https://docs.dotkernel.org/api-documentation/v6/core-features/error-reporting/) +- [OpenAPI Documentation](https://docs.dotkernel.org/api-documentation/v6/openapi/introduction/) +- [mezzio/mezzio-authentication-oauth2 Configuration](https://docs.mezzio.dev/mezzio-authentication-oauth2/v1/intro/#configuration) ## FAQ @@ -143,15 +178,3 @@ A: Demo credentials are provided for convenience during installation testing, bu **Q: What are the security recommendations around OpenAPI documentation?** A: You should not include sensitive information for your endpoints in the OpenAPI documentation, and you should not enable the documentation in a production environment. - -## Resources - -- [Basic Security in Dotkernel Admin](https://docs.dotkernel.org/admin-documentation/v6/security/basic-security/) -- [Basic Security in Dotkernel API](https://docs.dotkernel.org/api-documentation/v6/security/basic-security/) -- [Content Negotiation in Dotkernel REST API](https://www.dotkernel.com/dotkernel-api/content-negotiation-in-dotkernel-rest-api/) -- [laminas-form Documentation](https://docs.laminas.dev/laminas-form/v3/intro/) -- [CORS in Dotkernel API](https://docs.dotkernel.org/api-documentation/v6/tutorials/cors/) -- [CORS Policy Setup in Dotkernel](https://www.dotkernel.com/how-to/mezzio-cors-implementation-in-dotkernel/) -- [Error Reporting Endpoint](https://docs.dotkernel.org/api-documentation/v6/core-features/error-reporting/) -- [OpenAPI Documentation](https://docs.dotkernel.org/api-documentation/v6/openapi/introduction/) -- [mezzio/mezzio-authentication-oauth2 Configuration](https://docs.mezzio.dev/mezzio-authentication-oauth2/v1/intro/#configuration) diff --git a/public/md-articles/best-practice/golden-rules-of-professional-php-coding.md b/public/md-articles/best-practice/golden-rules-of-professional-php-coding.md index cb136561..a371ec2e 100644 --- a/public/md-articles/best-practice/golden-rules-of-professional-php-coding.md +++ b/public/md-articles/best-practice/golden-rules-of-professional-php-coding.md @@ -10,36 +10,41 @@ language: "en" # Golden Rules of Professional PHP Coding -## The Rules - -1. Always use, in development and in staging, the highest error reporting level, and display_errors ON: - ```php - error_reporting(-1); - ini_set('display_errors', 1); - ``` -2. Fix every warning or notice that occurs. -3. Check regularly the server's error_log for notices/warnings. -4. Identify any temporary hack with a special mark, for example: - ```php - #@TODO masterpiece by @smartguy, to quick fix the division by zero - ``` -5. Each function must do a single task. -If it logs in the user and records the login in a stats table, create a separate function for the "record the login" part - maybe even a distinct class for stats. -6. Use a version control system. -SVN is NOT dead. -7. Use an IDE, such as Aptana 2, Aptana 3, Eclipse, or Zend Studio. -8. Know your IDE: code snippets, code assist, integration with Zend Framework, SVN integration, bug tracker integration, and so on. +1.  Always use in development and in staging highest **error reporting** level, and **display_errors** ON: + +```php +error_reporting(-1); +ini_set('display_errors', 1); +``` + +2. **Fix** every warning or notice that occurs. + +3. **Check** regularly server's error_log for notices/warnings + +4. Identify any **temporary hack** with a special **mark**. Maybe something like: + +```php +#@TODO masterpiece by @smartguy, to quick fix the division by zero +``` + +5. Each **function** must do a **single task**. If is log in the user and record the login in stats table, be nice and create a separate function for 'record the login' stuff. Maybe even a **distinct class** for stats? + +6. Use a version control system. **SVN is NOT dead.** + +7. Use an **[IDE](http://en.wikipedia.org/wiki/Integrated_development_environment)**. [Aptana 2](http://www.aptana.com/products/studio2/download), [Aptana 3](http://www.aptana.com/products/studio3/download), Eclipse, even [Zend Studio](http://www.zend.com/en/products/studio/). + +8. Know your **IDE**: code snippets, code assist, integration with Zend Framework, SVN integration, bug tracker integration, and so on ## FAQ **Q: What error reporting settings should be used in development and staging?** -A: Always use the highest error reporting level and turn display_errors ON, for example with `error_reporting(-1);` and `ini_set('display_errors', 1);`. +A: Always use the highest error reporting level and turn display_errors ON, for example with error_reporting(-1); and ini_set('display_errors', 1);. **Q: What should you do about warnings and notices?** A: Fix every warning or notice that occurs, and regularly check the server's error_log for notices and warnings. **Q: How should temporary hacks or quick fixes be marked in code?** -A: Identify any temporary hack with a special mark, such as a `#@TODO` comment noting who added it and why. +A: Identify any temporary hack with a special mark, such as a #@TODO comment noting who added it and why. **Q: What is the rule about what a function should do?** A: Each function must do a single task. diff --git a/public/md-articles/best-practice/htaccess-301-redirect-non-www-to-www.md b/public/md-articles/best-practice/htaccess-301-redirect-non-www-to-www.md index a241584a..aa164852 100644 --- a/public/md-articles/best-practice/htaccess-301-redirect-non-www-to-www.md +++ b/public/md-articles/best-practice/htaccess-301-redirect-non-www-to-www.md @@ -10,30 +10,26 @@ language: "en" # htaccess 301 redirect non-www to www -## Redirect non-www to www - -To always redirect users to the www site (for example: `http://dotboost.com` to `http://www.dotboost.com`), add the following lines to `.htaccess`, right after `RewriteEngine On`: +To always redirect users to the www site (for example: http://dotboost.com to http://www.dotboost.com), add the following lines to .htaccess, right after **RewriteEngine On**: ```shell RewriteCond %{HTTP_HOST} ^dotboost.com -RewriteRule ^(.*)$ http://www.dotboost.com/$1 +RewriteRule ^(.*)$ http://www.dotboost.com/$1 [L,R=301] ``` -## Redirect www to non-www - -If, instead, you want to redirect `http://www.dotboost.com` to `http://dotboost.com`, add the following lines instead: +If, on the other hand, you want to redirect http://www.dotboost.com to http://dotboost.com, add the following lines instead: ```shell RewriteCond %{HTTP_HOST} ^www.dotboost.com -RewriteRule ^(.*)$ http://dotboost.com/$1 +RewriteRule ^(.*)$ http://dotboost.com/$1 [L,R=301] ``` -Replace `dotboost.com` with your site's domain in either case. +Replace dotboost.com with your site's domain. ## FAQ **Q: How do I redirect a non-www domain to www using .htaccess?** -A: Add `RewriteCond %{HTTP_HOST} ^dotboost.com` and `RewriteRule ^(.*)$ http://www.dotboost.com/$1` to your .htaccess file, right after `RewriteEngine On`, replacing dotboost.com with your own domain. +A: Add RewriteCond %{HTTP_HOST} ^dotboost.com and RewriteRule ^(.*)$ http://www.dotboost.com/$1 to your .htaccess file, right after RewriteEngine On, replacing dotboost.com with your own domain. **Q: How do I redirect a www domain to non-www instead?** -A: Add `RewriteCond %{HTTP_HOST} ^www.dotboost.com` and `RewriteRule ^(.*)$ http://dotboost.com/$1` instead, again replacing dotboost.com with your own domain. +A: Add RewriteCond %{HTTP_HOST} ^www.dotboost.com and RewriteRule ^(.*)$ http://dotboost.com/$1 instead, again replacing dotboost.com with your own domain. diff --git a/public/md-articles/best-practice/insert-update-delete-statements-with-zend-db.md b/public/md-articles/best-practice/insert-update-delete-statements-with-zend-db.md index 81c039db..79e45302 100644 --- a/public/md-articles/best-practice/insert-update-delete-statements-with-zend-db.md +++ b/public/md-articles/best-practice/insert-update-delete-statements-with-zend-db.md @@ -15,22 +15,24 @@ language: "en" DML (Data Manipulation Language) statements change data values in database tables. This 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. -## Connecting to the database +Continuing the Zend_DB article [series](http://www.dotkernel.com/dotkernel/sql-queries-using-zend-db-select/), we are stopping now at DML statements. DML (Data Manipulation Language) statements are statements that change data values in database tables. There are 3 primary DML statements: + +- INSERT - Inserting new rows into database tables. +- UPDATE - Updating existing rows in database tables. +- DELETE - Deleting existing rows from database tables. ```php $db = Zend_Db::factory('Pdo_Mysql', $dbConnect); ``` -## INSERT - -SQL: +**INSERT** ```sql INSERT INTO user(email, password, firstName, lastName, active) VALUES ('$email', '$password', '$firstName', '$lastName', 1); ``` -Zend_Db: +The above SQL *INSERT* statement is translated in Zend_Db as follows: ```php $data = array( 'email' => $email, @@ -41,9 +43,7 @@ $data = array( 'email' => $email, $db->insert('user', $data); ``` -## UPDATE - -SQL: +**UPDATE** ```sql UPDATE user @@ -54,7 +54,7 @@ UPDATE user WHERE id = '$id' ``` -Zend_Db: +The above SQL *UPDATE* statement is translated in Zend_Db as follows: ```php $data = array('password' => $password, @@ -64,15 +64,13 @@ $data = array('password' => $password, $db->update('user', $data, 'id = '.$id); ``` -## DELETE - -SQL: +**DELETE** ```sql DELETE FROM user WHERE id = '$id' ``` -Zend_Db: +The above SQL *DELETE* statement is translated in Zend_Db as follows: ```php $db->delete('user', 'id = '.$id); diff --git a/public/md-articles/best-practice/sql-queries-using-zend-db-select.md b/public/md-articles/best-practice/sql-queries-using-zend-db-select.md index ad3e445e..f68ff064 100644 --- a/public/md-articles/best-practice/sql-queries-using-zend-db-select.md +++ b/public/md-articles/best-practice/sql-queries-using-zend-db-select.md @@ -15,15 +15,15 @@ language: "en" Zend_Db and its related classes provide a simple SQL database interface for Zend Framework. This 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. -## Connecting to the database +[Zend_Db](https://docs.laminas.dev/laminas-db/adapter/) and its related classes provide a simple SQL database interface for Zend Framework. To connect to MySQL database, we are using Pdo_Mysql adapter : ```php $db = Zend_Db::factory('Pdo_Mysql', $dbConnect); ``` -## SELECT query - WHERE clause +**SELECT query - WHERE clause** -The following two classical SQL queries are equivalent - the first is a simple comma join, the second uses INNER JOIN - but the result is the same: +The below 2 classical SQL queries are equivalent. First one is simple, the second one use INNER JOIN keyword, but the result is the same. ```sql SELECT a.id, a.name, b.order_id @@ -38,7 +38,7 @@ FROM `users` AS `a` INNER JOIN `orders` AS `b` ON a.id = b.user_id WHERE (a.id = '{$userId}') ``` -Translated into Zend_Db style: +The above queries are translated in Zend_Db style: ```php $select = $db->select() @@ -48,7 +48,7 @@ $select = $db->select() ->where('a.id = ?', $userId) ``` -If no column should be selected from the second table, the 3rd parameter of join() should be an empty string: +If we don't want to select any column from the second table, the 3rd parameter of join() method should be an empty string ```sql SELECT a.id, a.name @@ -65,7 +65,7 @@ $select = $db->select() ->where('a.id = ?', $userId) ``` -Note: if the 3rd parameter is not written at all, it will select all the fields from that table: +Note: If we don't write the 3rd parameter, it will select all the fields from that table: ```sql SELECT a.id, a.name, b.* @@ -82,7 +82,7 @@ $select = $db->select() ->where('a.id = ?', $userId) ``` -## SELECT query - WHERE IN clause +**SELECT query - WHERE IN clause** ```sql SELECT id @@ -96,9 +96,7 @@ $select = $db->select() ->where('aff_id IN (?)', array(1,2,3)); ``` -## Debugging a query - -If you are not sure the correct query is being generated, echo it before fetching: +**Note**: If you are not sure if you write the correct query, before you fetch it you can echo your query to visualize it: ```php echo $select->__toString();exit; @@ -128,6 +126,6 @@ A: Before fetching it, echo the query to visualize it: echo $select->__toString( ## Resources - [Zend_Db](https://docs.laminas.dev/laminas-db/adapter/) -- [What are returning the FETCH functions from Zend_Db](http://www.dotkernel.com/best-practice/sql-fetch-zend-db/) -- [Subqueries with Zend_Db](http://www.dotkernel.com/best-practice/subqueris-with-zend-db/) -- [INSERT, UPDATE, DELETE statements with Zend_Db](http://www.dotkernel.com/best-practice/iud-statements-with-zend-d/) +- [What are returning the FETCH functions from Zend_Db](http://www.dotkernel.com/best-practice/what-are-returning-the-fetch-functions-from-zend-db/) +- [Subqueries with Zend_Db](http://www.dotkernel.com/best-practice/subqueries-with-zend-db/) +- [INSERT, UPDATE, DELETE statements with Zend_Db](http://www.dotkernel.com/best-practice/insert-update-delete-statements-with-zend-db/) diff --git a/public/md-articles/best-practice/subqueries-with-zend-db.md b/public/md-articles/best-practice/subqueries-with-zend-db.md index 670e1fbd..f3a0f7ae 100644 --- a/public/md-articles/best-practice/subqueries-with-zend-db.md +++ b/public/md-articles/best-practice/subqueries-with-zend-db.md @@ -14,7 +14,9 @@ language: "en" 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. -## The SQL query +Continuing the Zend_DB article [series](http://www.dotkernel.com/dotkernel/sql-queries-using-zend-db-select/), 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: ```sql SELECT a.id, @@ -29,14 +31,12 @@ LEFT JOIN track_courses AS b ON (a.id = b.track_id) GROUP BY a.id ``` -## Connecting to the database +Initialize the connection to the MySQL database: ```php $db = Zend_Db::factory('Pdo_Mysql', $dbConnect); ``` -## Building the query in Zend_Db - ```php $db->select() ->from(array('a'=>'tracks'), @@ -56,8 +56,6 @@ $db->select() ->group('a.id'); ``` -The `count_files` column is built by wrapping a nested `$db->select()` call inside a `Zend_Db_Expr`, correlated back to the outer table via `c.track_id = a.id`. - ## FAQ **Q: What SQL techniques does this subquery example combine?** diff --git a/public/md-articles/best-practice/svn-export-in-a-virtual-host.md b/public/md-articles/best-practice/svn-export-in-a-virtual-host.md index 244cad46..0286abf1 100644 --- a/public/md-articles/best-practice/svn-export-in-a-virtual-host.md +++ b/public/md-articles/best-practice/svn-export-in-a-virtual-host.md @@ -15,18 +15,25 @@ language: "en" `svn export` lets you export the contents of a repository into a virtual host directory. The 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. -## Steps +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. -1. Make sure Subversion is installed on the host by running `svn --version`. -If you don't get a "command not found" message, it's installed; otherwise, install it. -2. Go to the directory where you want to export the contents of the repository (e.g. `cd /var/www/vhosts/example.com/httpdocs` or `cd /home/sitename/public_html`). -3. Run the export command: +1. First make sure that Subversion is installed on the host. To check if it is installed, run: + +``` +svn --version +``` + +If you don't get a "command not found" message, subversion is installed. Otherwise, you need to install it. + +2. The next step is to go to where you want to export the contents of the repository (eg.: "*cd /var/www/vhosts/example.com/httpdocs*" or "*cd /home/sitename/public_html*"). + +3. The command looks like this: ```shell svn export repositoryUrl repositoryUrl ``` -Where: +where: | Parameter | Meaning | |---|---| @@ -37,9 +44,9 @@ Where: | `targetDirectory` - `/var/www/vhosts/example.com/httpdocs` | Exports to an absolute path. | | `--force` | Optional. By default SVN will not export into an existing directory; this overrides that. **Be careful, this option can overwrite files.** | -4. For more information, run `svn help export`. +4. For more information, you can run **svn help export**. -## Examples +Examples: ```shell svn export http://v1.dotkernel.net/svn/trunk ./ --force @@ -47,9 +54,7 @@ svn export -r 423 http://v1.dotkernel.net/svn/trunk ./ --force svn export http://v1.dotkernel.net/svn/trunk /var/www/vhosts/domain.com/httpdocs/dk ``` -## Fixing permissions afterward - -If the repository was exported using a different user (e.g. root), change the permissions back as root: +If you've exported the repository using a different user (root for example), you can change the permissions back by running the following command as root: ```shell chown -R siteuser.psacln /var/www/vhosts/example.com/httpdocs diff --git a/public/md-articles/best-practice/svn-keywords-setup-in-php-ide-zend-studio.md b/public/md-articles/best-practice/svn-keywords-setup-in-php-ide-zend-studio.md index c3ed2522..3b39e348 100644 --- a/public/md-articles/best-practice/svn-keywords-setup-in-php-ide-zend-studio.md +++ b/public/md-articles/best-practice/svn-keywords-setup-in-php-ide-zend-studio.md @@ -15,11 +15,13 @@ language: "en" 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. This article lists which properties to set and how. -## Steps +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. -1. Right click on the **project**. -2. Go to **Team -> Set Propriety**. -3. Set `svn:ignore` so local settings aren't committed to the main repository: +1. Right click on **project**. + +2. Go to **Team->Set Propriety**. + +3. SVN Ignore files, below you have an example. As we do not want to commit your local settings to the main repository :-) ``` Name: svn:ignore @@ -33,7 +35,7 @@ cache *.ini ``` -4. Set up basic bug tracker integration: +4. Basic integration with a bug tracker ``` Name: bugtracq:label @@ -42,23 +44,25 @@ Propriety: Tracker ID: ``` Name: bugtraq:message -Propriety: +Propriety: [Tracker ID: #%BUGID%] ``` -5. If using a public bug tracker (e.g. Mantis), also set: +5. If you have a public bug tracker system, example Mantis ``` Name: bugtraq:url Propriety: http://www.dotkernel.net/view.php?id=%BUGID% ``` -For the properties above, apply them **only** to the project folder, **not** recursively. +For **above** Proprieties , apply **only** to project folder, **NOT** recursive -## Final step (svn:keywords only) +## Final step( below instructions are good **only** for **svn:keywords** ) -1. Check **Apply property recursively to:**. +1. Check the **Apply property recursively to:**. 2. Select **All resources**. -3. Check **Use filtration by the resource name** and add mask: `*.php`. +3. Check the **Use filtration by the resource name** and add **Mask:** *.php. + +[![svn-add](/uploads/article/019f8a80-cc87-71b3-80b8-478826d88044/svn-add.jpg)](/uploads/2013/02/svn-add.jpg) ## FAQ diff --git a/public/md-articles/best-practice/using-like-wildcards-with-zend-db.md b/public/md-articles/best-practice/using-like-wildcards-with-zend-db.md index ef7b9a21..6785a335 100644 --- a/public/md-articles/best-practice/using-like-wildcards-with-zend-db.md +++ b/public/md-articles/best-practice/using-like-wildcards-with-zend-db.md @@ -16,15 +16,22 @@ The LIKE condition allows pattern matching in the WHERE clause of SELECT, INSERT The `_` wildcard matches a single character, and `%` matches any string of any length (including zero). This article shows how to use LIKE and NOT LIKE with both wildcards in Zend_Db. -## Connecting to the database +Continuing the Zend_Db article [series](http://www.dotkernel.com/dotkernel/sql-queries-using-zend-db-select/), let's discuss the LIKE condition. + +The **LIKE** condition allows you to use wildcards in the *WHERE* clause of an SQL statement. This allows pattern matching. It can be used in any valid SQL statement (*SELECT*, *INSERT*, *UPDATE* or *DELETE*). + +**LIKE wildcards:** + +- ***_*** allows you to match a single character +- ***%*** allows you to match any string of any length (including zero length) ```php $db = Zend_Db::factory('Pdo_Mysql', $dbConnect); ``` -## LIKE _ +**LIKE _** -Return all ids that start with '1' and whose second digit is between 0 and 9 (10, 11, 12, ..., 18, 19): +- Return all ids which start with '1' and second digit is between 0 and 9 (10, 11, 12, ..., 18, 19): ```sql SELECT * FROM `table` WHERE (`id` LIKE '1_' ) @@ -39,7 +46,7 @@ $select = $this->db->select() $result = $this->db->fetchAll($select); ``` -Return all instances whose name is 4 characters long, starting with 'Fr' and ending with 'd' (Frad, Fred, Frod, etc.): +- Return all instances whose name is 4 characters long, where the first two characters are 'Fr' and the last character is 'd' (Frad, Fred, Frod, etc.): ```sql SELECT * FROM `table` WHERE (`name` LIKE 'Fr_d' ) @@ -54,9 +61,9 @@ $select = $this->db->select() $result = $this->db->fetchAll($select); ``` -## LIKE % +**LIKE %** -Returns all instances that have the 'gallery' string in the `source` field: +- Returns all instances that have the 'gallery' string in the *source* field: ```sql SELECT * FROM `table` WHERE (`source` LIKE '%gallery%' ) @@ -71,7 +78,7 @@ $select = $this->db->select() $result = $this->db->fetchAll($select); ``` -Returns all instances that have the 'gallery' or 'folder' strings in the `source` field: +- Returns all instances that have the 'gallery' or 'folder' strings in the *source* field: ```sql SELECT * FROM `table` WHERE (`source` LIKE '%gallery%' OR `source` LIKE ('%folder%') ) @@ -87,9 +94,9 @@ $select = $this->db->select() $result = $this->db->fetchAll($select); ``` -## NOT LIKE _ +**NOT LIKE _** -Returns all 2-digit ids that don't start with `1` (20->99) or that don't have exactly 2 digits (1, 2, ..., 8, 9, 100, 101, ...): +- Returns all 2-digit ids that don't start with *1* (20->99) or have a different number of digits than 2 (1, 2, ..., 8, 9, 100, 101, ...): ```sql SELECT * FROM `table` WHERE (`id` NOT LIKE '1_' ) @@ -104,9 +111,9 @@ $select = $this->db->select() $result = $this->db->fetchAll($select); ``` -## NOT LIKE % +**NOT LIKE %** -Returns all instances that don't have 'gallery', 'folder', or 'file' in the `source` field: +- Returns all instances that don't have 'gallery', 'folder' or 'file' strings in the *source* field: ```sql SELECT * FROM `table` WHERE (`source` NOT LIKE ('%gallery%') AND `source` NOT LIKE ('%folder%') AND `source` NOT LIKE ('%file%') ) @@ -123,7 +130,7 @@ $select = $this->db->select() $result = $this->db->fetchAll($select); ``` -## Other example +**Other example** ```sql SELECT * FROM `table` WHERE `number` LIKE '_6%' @@ -138,6 +145,11 @@ $select = $this->db->select() $result = $this->db->fetchAll($select); ``` +- The *number* column starts with a digit between 4 and 6 (*[4-6]*) +- The second character in the *number* column can be anything (*_*) +- The third character in the *number* column is 6 (*6*) +- The rest of the *number* column can be any string, of any length (*%*) + ## FAQ **Q: What do the LIKE wildcards _ and % mean?** @@ -155,4 +167,4 @@ A: Build the first condition with quoteInto, then append further ones with quote ", $pattern), as in the example matching 'gallery' or 'folder' in the source field. **Q: How does NOT LIKE differ from LIKE?** -A: NOT LIKE negates the pattern match - for example, id NOT LIKE '1_' returns ids that don't start with 1 or don't have exactly 2 digits, and NOT LIKE conditions can be chained with AND to exclude several patterns at once. +A: NOT LIKE negates the pattern match — for example, `id` NOT LIKE '1_' returns ids that don't start with 1 or don't have exactly 2 digits, and NOT LIKE conditions can be chained with AND to exclude several patterns at once. diff --git a/public/md-articles/best-practice/what-are-returning-the-fetch-functions-from-zend-db.md b/public/md-articles/best-practice/what-are-returning-the-fetch-functions-from-zend-db.md index cbf3460d..5fcb03d3 100644 --- a/public/md-articles/best-practice/what-are-returning-the-fetch-functions-from-zend-db.md +++ b/public/md-articles/best-practice/what-are-returning-the-fetch-functions-from-zend-db.md @@ -15,22 +15,18 @@ language: "en" 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. Each 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. -## Available FETCH Methods - -Continuing the Zend_Db article series, this article stops at the FETCH methods found in Zend_Db_Adapter_Abstract: +Continuing the Zend_DB article [series](http://www.dotkernel.com/dotkernel/sql-queries-using-zend-db-select/), we are stopping now at *FETCH* methods that are in [Zend_Db_Adapter_Abstract](https://docs.laminas.dev/laminas-db/adapter/): ```php -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, ...) +array fetchAll (string|Zend_Db_Select $sql, [mixed $bind = array()]) +array fetchAssoc (string|Zend_Db_Select $sql, [mixed $bind = array()]) +array fetchCol (string|Zend_Db_Select $sql, [mixed $bind = array()]) +string fetchOne (string|Zend_Db_Select $sql, [mixed $bind = array()]) +array fetchPairs (string|Zend_Db_Select $sql, [mixed $bind = array()]) +array fetchRow (string|Zend_Db_Select $sql, [mixed $bind = array()]) ``` -To make it easier to follow, each example below shows the classical, old-style query first, followed by the equivalent query written in Zend_Db style. - -## Connecting to the Database +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. Initialize the connection to the MySQL database: @@ -38,8 +34,6 @@ Initialize the connection to the MySQL database: $db = Zend_Db::factory('Pdo_Mysql', $dbConnect); ``` -## Setting Up the Query - Here is a SQL query that we want to fetch: ```sql @@ -47,23 +41,18 @@ $sql = "SELECT id, title FROM files"; $db->query($sql) ``` -Here is the same query written in Zend_Db style: - ```php $select = $db->select() ->from('files', array('id', 'title')) ``` -Note: the old style of fetching shown below uses an older class. -Here's what you need to know about its methods: +Note: for the old style of fetching we used an old class. What you need to know is: -- `query()` is similar to `mysqli_query()` from the Mysqli PHP extension -- `next_record()` is similar to `mysqli_next_result()` from the Mysqli PHP extension -- `f()` retrieves the value of the column specified as a parameter +- *query()* method is similar with mysqli_query() from *Mysqli* PHP extension +- *next_record()* method is similar with mysqli_next_result() from *Mysqli* PHP extension +- *f()* method retrieve the value of the column specified as parameter -## fetchAll - -Old style: +**fetchAll** ```php while($db->next_record()) @@ -75,35 +64,27 @@ while($db->next_record()) } ``` -Zend_Db style: - ```php $a = $db->fetchAll($select); ``` -## fetchAssoc - -Old style: +**fetchAssoc** ```php while($db->next_record()) { - $a = array( + $a[$db->f('id')] = array( 'id' => $db->f('id'), 'title' => $db->f('title') ); } ``` -Zend_Db style: - ```php $a = $db->fetchAssoc($select); ``` -## fetchCol - -Old style: +**fetchCol** ```php while($db->next_record()) @@ -112,47 +93,35 @@ while($db->next_record()) } ``` -Zend_Db style: - ```php $a = $db->fetchCol($select); ``` -## fetchOne - -Old style: +**fetchOne** ```php $db->next_record(); $a = $db->f('id'); ``` -Zend_Db style: - ```php $a = $db->fetchOne($select); ``` -## fetchPairs - -Old style: +**fetchPairs** ```php while($db->next_record()) { - $a = $db->f('title'); + $a[$db->f('id')] = $db->f('title'); } ``` -Zend_Db style: - ```php $a = $db->fetchPairs($select); ``` -## fetchRow - -Old style: +**fetchRow** ```php $db->next_record(); @@ -162,8 +131,6 @@ $a = array( ); ``` -Zend_Db style: - ```php $a = $db->fetchRow($select); ``` @@ -174,13 +141,13 @@ $a = $db->fetchRow($select); A: The article covers fetchAll, fetchAssoc, fetchCol, fetchOne, fetchPairs, and fetchRow. **Q: What does fetchAll do compared to the old query style?** -A: `$a = $db->fetchAll($select)` replaces the old-style loop that calls `next_record()` repeatedly and builds an array of associative rows using `f()` for each column. +A: $a = $db->fetchAll($select) replaces the old-style loop that calls next_record() repeatedly and builds an array of associative rows using f() for each column. **Q: What does fetchRow return?** -A: `$a = $db->fetchRow($select)` returns a single row as an associative array, replacing a single `next_record()` call followed by `f()` calls for each column. +A: $a = $db->fetchRow($select) returns a single row as an associative array, replacing a single next_record() call followed by f() calls for each column. **Q: What does fetchOne return?** -A: `$a = $db->fetchOne($select)` returns a single value, replacing a single `next_record()` call followed by one `f()` call. +A: $a = $db->fetchOne($select) returns a single value, replacing a single next_record() call followed by one f() call. **Q: How do the old-style query(), next_record(), and f() methods relate to Mysqli?** -A: `query()` is similar to `mysqli_query()`, `next_record()` is similar to `mysqli_next_result()`, and `f()` retrieves the value of the column specified as a parameter. +A: query() is similar to mysqli_query(), next_record() is similar to mysqli_next_result(), and f() retrieves the value of the column specified as a parameter. diff --git a/public/md-articles/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.md b/public/md-articles/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.md index 7cc4a590..07110f6d 100644 --- a/public/md-articles/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.md +++ b/public/md-articles/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.md @@ -16,38 +16,29 @@ On a TIMESTAMP field that records date and time when inserting a new record, it' This 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. Only one TIMESTAMP field per table can be DEFAULT CURRENT_TIMESTAMP. -## Why Use CURRENT_TIMESTAMP as a Default - -On a TIMESTAMP field that records date and time when inserting a new record, it is encouraged to use the CURRENT_TIMESTAMP constant as a DEFAULT value. -Because when inserting a new row in the table, there is no need to specifically add the value for the date and time field, either by creating it from PHP code with the Date/Time functions or with MySQL's NOW() function: +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](http://www.php.net/manual/en/ref.datetime.php) or with MySQL function [NOW()](http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html#function_now) ```sql ALTER TABLE `user` CHANGE `dateCreated` `dateCreated` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; ``` -## Automatically Updating with ON UPDATE CURRENT_TIMESTAMP - -CURRENT_TIMESTAMP is also a solution for updating date and time fields. -Use the `ON UPDATE CURRENT_TIMESTAMP` clause if you want the value of the field to be changed automatically each time the row is updated: +CURRENT_TIMESTAMP is also a solution for *updating* date and time fields. Use *`ON UPDATE CURRENT_TIMESTAMP`* clause, if you want the value of the field to be changed automatically each time the row is updated. ```sql ALTER TABLE `user` CHANGE `dateLogin` `dateLogin` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; ``` -## DEFAULT and ON UPDATE Clause Combinations - -DEFAULT and ON UPDATE clauses can be used together or separately, depending on your needs: +*DEFAULT* and *ON UPDATE* clauses can be used together or separately, depending on your needs: - With both `DEFAULT CURRENT_TIMESTAMP` and `ON UPDATE CURRENT_TIMESTAMP` clauses, the column has the current timestamp for its default value and is automatically updated. -- With neither `DEFAULT` nor `ON UPDATE` clauses, it is the same as `DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP` (only for the first TIMESTAMP field in the table). +- With neither `DEFAULT` nor `ON UPDATE` clauses, it is the same as `DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`. (Only for the *first* TIMESTAMP field from the table) - With a `DEFAULT CURRENT_TIMESTAMP` clause and no `ON UPDATE` clause, the column has the current timestamp for its default value but is not automatically updated. - With no `DEFAULT` clause and with an `ON UPDATE CURRENT_TIMESTAMP` clause, the column has a default of 0 and is automatically updated. -- With a constant `DEFAULT` value, the column has the given default and is not automatically initialized to the current timestamp. -If the column also has an `ON UPDATE CURRENT_TIMESTAMP` clause, it is automatically updated; otherwise, it has a constant default and is not automatically updated. +- With a constant `DEFAULT` value, the column has the given default and is not automatically initialized to the current timestamp. If the column also has an `ON UPDATE CURRENT_TIMESTAMP` clause, it is automatically updated; otherwise, it has a constant default and is not automatically updated. -For more details, check out the [MySQL Manual](https://dev.mysql.com/doc/refman/9.7/en/datetime.html). +For more details check out [MySQL Manual](https://dev.mysql.com/doc/refman/9.7/en/datetime.html) -Note: only one timestamp field can be `DEFAULT CURRENT_TIMESTAMP` in a table. +**Note**: Only one timestamp field can be `DEFAULT CURRENT_TIMESTAMP` in a table. ## FAQ @@ -55,7 +46,7 @@ Note: only one timestamp field can be `DEFAULT CURRENT_TIMESTAMP` in a table. A: Because when inserting a new row, there is no need to specifically set the date/time value yourself, either from PHP Date/Time functions or with MySQL's NOW() function. **Q: How do you make a field update its timestamp automatically on every UPDATE?** -A: Add the ON UPDATE CURRENT_TIMESTAMP clause, for example: `ALTER TABLE `user` CHANGE `dateLogin` `dateLogin` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP`. +A: Add the ON UPDATE CURRENT_TIMESTAMP clause, for example: ALTER TABLE `user` CHANGE `dateLogin` `dateLogin` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP. **Q: What happens if a TIMESTAMP column has neither a DEFAULT nor an ON UPDATE clause?** A: For the first TIMESTAMP field in the table, having neither clause is the same as DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP. diff --git a/public/md-articles/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.md b/public/md-articles/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.md index 038c5653..fbcfd46f 100644 --- a/public/md-articles/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.md +++ b/public/md-articles/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.md @@ -15,40 +15,28 @@ language: "en" 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. Maintaining 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. -## A Bit of History +It all started with the **announcement**: [Laminas MVC Is Retiring](https://getlaminas.org/blog/2025-06-06-laminas-mvc-is-retiring.html). 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. -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. +## A Bit of History -Laminas MVC is not even the first framework that has reached its end of life - look at Zend Framework and Apigility. -Letting go of a flagship product is a difficult decision, but it's made easier when you leave a solid alternative in its wake. -The developers who worked on Laminas MVC already had something better and fully-functional in place - the Mezzio microframework, built using Laminas components. -It has itself gone through rigorous development and testing since being released in 2015, when it was known as Zend Expressive, then was renamed into Mezzio to get to its current state. +Laminas MVC is not even the first framework that has reached its end of life - look at Zend Framework and Apigility. Letting go of a flagship product is a **difficult decision**, but it's made easier when you leave a solid alternative in its wake. The **developers** who worked on Laminas MVC already **had something better** and fully-functional in place - **Mezzio microframework**, built using Laminas components. It has itself gone through rigorous development and testing since being released in 2015 when it was known as Zend Expressive, then was renamed into Mezzio to get to its current state. ## What Is the Issue with Legacy Platforms? -Maintaining legacy platforms over the long term is often a costly and time-consuming endeavour. -Every few years, platform owners must consider the viability of migrating to a newer platform. +**Maintaining legacy platforms** over the long term is often a **costly** and **time-consuming** endeavour. Every few years, platform **owners** must consider the viability of **migrating to a newer platform**. -Newer platforms implement modern architectures, have an active community, and are actively being developed and maintained. -They also offer easier development, expansion, and maintenance, alongside vital security improvements and more reliable dependencies. +Newer platforms implement **modern architectures**, have an **active community** and are actively being **developed and maintained**. They also offer **easier development, expansion and maintenance**, alongside vital **security improvements** and more **reliable dependencies**. -Sounds like an easy decision? Sure, but it's a lot of work, and that's when the specialists come into play. +Sounds like an easy decision? Sure, but it's a lot of work... And that's when the specialists come into play. -We at Apidemia have been using the Zend Framework, Laminas MVC, and Mezzio for years. -We understand their ins-and-outs intimately, which enables us to analyze and perform the transfer of a legacy platform to Mezzio effectively. -Working with Mezzio ensures faster execution times, increased security, faster development, and long-term reliability from all points of view. -We encourage this change and are ready to offer guidance. +We at Apidemia have been using the Zend Framework, Laminas MVC and Mezzio for years. We understand their ins-and-outs intimately, which enables us to **analyze and perform the transfer of a legacy platform to Mezzio effectively**. Working with Mezzio ensures faster execution times, increased security, faster development and long-term reliability from all points of view. We encourage this change and are ready to offer guidance. ## Pain Points -The MVC architecture is obsolete. -It is yesterday's architecture, fit for monolithic websites. -The architecture of today and tomorrow is based on middleware, building headless platforms, websites, and microservices following the same coding approach. +The MVC architecture is obsolete. It is yesterday's architecture, fit for monolithic websites. The **architecture of today and tomorrow** is based on middleware, building headless platforms, websites and microservices following the same coding approach. | Pain Point | Apidemia Solution | -|---|---| +| --- | --- | | Legacy framework is deprecated and/or has no long-term support | Apidemia helps migrate to modern middleware architecture (Mezzio microframework with Laminas components) | | Legacy applications are hard to maintain | Modern architecture improves code quality, testability and performance | | Migration is risky or expensive | Apidemia uses a proven, phased migration strategy to reduce risk | @@ -56,17 +44,15 @@ The architecture of today and tomorrow is based on middleware, building headless ## How Apidemia Handles Migrations -Apidemia has created a complex process that involves several steps to ensure a smooth migration. -In a nutshell, the current project functionality must be understood, and only then can the move be implemented into the destination platform. -Over the long run, the Apidemia team offers support and training. +Apidemia has created a **complex process** that involves several steps to ensure a **smooth migration**. In a nutshell, the current project functionality must be understood and only then can the move be implemented into the destination platform. Over the long run, the Apidemia team offers support and training. -This is the simplified task list: +This is the simplified **task list**: -- Code audit & migration strategy - to understand the code and see what goes where. -- Partial or full migration to Laminas or PSR-compliant frameworks, like Mezzio or Symfony - this decision impacts both time to implement and cost, negotiated with the client. -- Refactoring and decoupling legacy modules - the old code must go and be replaced with the new. -- Unit testing and CI/CD pipeline setup - a vital step to ensure things function the same way in the destination platform. -- Post-migration support and team training - this step depends on the level of collaboration between the original developers and the Apidemia team, so the more closely they work together, the easier it is to onboard the devs for the long run. +- **Code audit & migration strategy** - to understand the code and see what goes where. +- **Partial or full migration to Laminas or PSR-compliant frameworks**, like Mezzio or Symfony - this decision impacts both time to implement and cost, negotiated with the client. +- **Refactoring and decoupling legacy modules** - the old code must go and be replaced with the new. +- **Unit testing and CI/CD pipeline setup** - a vital step to ensure things function the same way in the destination platform. +- **Post-migration support and team training** - this step depends on the level of collaboration between the original developers and the Apidemia team, so the more closely they work together, the easier it is to onboard the devs for the long run. ## FAQ diff --git a/public/md-articles/design-pattern/naming-pattern-for-psr-15-handlers-in-dotkernel-applications.md b/public/md-articles/design-pattern/naming-pattern-for-psr-15-handlers-in-dotkernel-applications.md index efec0fbf..1c90737d 100644 --- a/public/md-articles/design-pattern/naming-pattern-for-psr-15-handlers-in-dotkernel-applications.md +++ b/public/md-articles/design-pattern/naming-pattern-for-psr-15-handlers-in-dotkernel-applications.md @@ -59,6 +59,10 @@ We have chosen this wording for the performed actions (or CRUD): - **Edit** for Update - **Delete** +The image below contains the full list of handlers used in Dotkernel Admin. + +![Dotkernel Naming Convention](https://www.dotkernel.com/wp-content/uploads/2025/05/naming-convention-1024x767.png) + ## A practical example Let's assume your application requires you to create products managed by admin users. So how do you go about naming a new set of files for this purpose? diff --git a/public/md-articles/dotkernel-api/dotkernel-api-versus-laminas-api-tools.md b/public/md-articles/dotkernel-api/dotkernel-api-versus-laminas-api-tools.md index 50a359e0..fec262c0 100644 --- a/public/md-articles/dotkernel-api/dotkernel-api-versus-laminas-api-tools.md +++ b/public/md-articles/dotkernel-api/dotkernel-api-versus-laminas-api-tools.md @@ -15,21 +15,21 @@ language: "en" This article compares the basic features of Laminas API Tools and Dotkernel API side by side, covering architecture, versioning, documentation, authentication, and more. It 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. -Below is an analysis of the basic features available in Laminas API Tools and Dotkernel API. +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. > The table below refers to [Dotkernel API V7](https://github.com/dotkernel/api/tree/7.0). -| | API Tools (formerly Apigility) | Dotkernel API | -|---|---|---| +| | **API Tools (formerly Apigility)** | **Dotkernel API** | +| --- | --- | --- | | URL | [api-tools](https://api-tools.getlaminas.org/) | [Dotkernel API](https://www.dotkernel.org) | | First Release | 2012 | 2018 | -| PHP Version | <= 8.2 | Shown via a dynamic Packagist badge (see the project repository for the current supported version) | +| PHP Version | <= 8.2 | ![PHP Version](https://img.shields.io/packagist/php-v/dotkernel/api) | | Architecture | MVC, Event Driven | Middleware | -| OSS Lifecycle | Archived | Shown via a dynamic OSS Lifecycle badge (see the project repository for the current status) | +| OSS Lifecycle | Archived | ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/api?style=flat&label=) | | Style | REST, RPC | REST | | Versioning | Yes | Deprecations (API Evolution) * | -| Documentation | Swagger (Automated) | Postman (Manual), OpenAPI 3.0 (Swagger) | +| Documentation | Swagger (Automated) | Postman (*Manual*), OpenAPI 3.0 (Swagger) | | Content-Negotiation | Custom | Custom | | License | BSD-3 | MIT | | Default DB Layer | laminas-db | doctrine-orm 3.x | diff --git a/public/md-articles/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.md b/public/md-articles/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.md index e11097a6..983e168a 100644 --- a/public/md-articles/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.md +++ b/public/md-articles/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.md @@ -60,9 +60,9 @@ class MailChimpFactory */ public function __invoke(ContainerInterface $container) : MailChimp { - $config = $container->get('config') ?? []; + $config = $container->get('config')['mailChimp'] ?? []; - return new MailChimp($config ?? ''); + return new MailChimp($config['apiKey'] ?? ''); } } ``` diff --git a/public/md-articles/dotkernel/adding-a-cors-implementation-to-zend-expressive.md b/public/md-articles/dotkernel/adding-a-cors-implementation-to-zend-expressive.md index 003cf5d5..7a55e4f2 100644 --- a/public/md-articles/dotkernel/adding-a-cors-implementation-to-zend-expressive.md +++ b/public/md-articles/dotkernel/adding-a-cors-implementation-to-zend-expressive.md @@ -24,6 +24,10 @@ If you're facing the error: it means the server didn't send the header that lets you access its data through a local client (e.g. a browser). This issue is most common when trying to get data (usually JSON) that you want to process using JavaScript. +The error looks similar to the image below: + +![](/uploads/article/019f8a80-cc4d-71e9-9af2-595b3eb4c793/Screenshot-2019-04-06-at-15.03.21-1024x165-1-1024x165.png) + ## The solution A simple implementation uses [Tuupola's Cors Middleware](https://packagist.org/packages/tuupola/cors-middleware) package. diff --git a/public/md-articles/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.md b/public/md-articles/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.md index 414c49c9..70cc5c14 100644 --- a/public/md-articles/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.md +++ b/public/md-articles/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.md @@ -16,6 +16,9 @@ This article is the upgrade guide for applying that icon patch. ## Upgrade steps +![Icons Patch](/uploads/article/019f8a80-cc47-710e-9ef1-b2257262e376/icons.png) +*The new Icons listed in User and Admin Logins* + 1. Make sure your project is running version **1.5.0** or **newer**. 2. Download the [patch](http://www.dotkernel.com/download/?did=46). 3. Extract the archive into a folder, e.g. `icons_patch`. diff --git a/public/md-articles/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.md b/public/md-articles/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.md index 065f64b5..77ec8032 100644 --- a/public/md-articles/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.md +++ b/public/md-articles/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.md @@ -16,43 +16,35 @@ Dotkernel 1 is the original PHP Application Framework built on Zend Framework 1 Dotkernel 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. Since Dotkernel 3's release, the unqualified name "Dotkernel" refers to Dotkernel 3, while Dotkernel 1 is always referenced explicitly. -## What Is Dotkernel? +## What Is the Meaning Behind 'Dotkernel'? + +The name **Dotkernel** symbiotically combines the string **Dot,** as a representation of the Internet, and **Kernel**, the quintessential components of any IT application. -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 Internet development, ensuring increased development productivity and run-time performance. +In other words, **Dotkernel** aims to become the starting point for development Internet applications and hence ensure increased development productivity and run-time performance. -## What Is Dotkernel 1? +## What Was Dotkernel 1? -Dotkernel 1 is a PHP Application Framework, built on top of Zend Framework 1 (ZF1). -It had its first public release in July 2010. -It is tightly coupled with Zend Framework 1, and adds a set of custom or external features (such as Router, Template Engine, etc.). -It is composed of Zend Framework 1 and a set of custom or external features (such as Router, Template Engine, etc.). -Dotkernel 1's architecture is based on MVC. +Dotkernel 1 was a ***PHP* *Application Framework***, built on top of Zend Framework 1 (ZF1). -The latest version is 1.8 Long Term Support. -No new version will be released anymore, only bugfixes. +It had the first public release in July 2010. It was tightly coupled with **Zend Framework 1** and adds a set of custom or external features (such as Router, Template Engine, etc.). It was composed of **Zend Framework 1** and a set of custom or external features (such as Router, Template Engine, etc.). Dotkernel 1 architecture was based on **MVC**. -## What Is Dotkernel 3? +The latest version is **1.8 Long Term Support**. It will not be getting any new releases or bugfixes because Zend Framework 1 is also not supported. If you are still using either Dotkernel 1 or Zend Framework 1, you need to refactor your code to the [Dotkernel Headless Platform](https://docs.dotkernel.org/headless-documentation/). -A collection of PSR-7 Middleware applications built on top of the [Zend Expressive](https://docs.zendframework.com/zend-expressive/) microframework. -It is composed of a set of custom and extended [Zend Framework 3](https://framework.zend.com/) components. -Dotkernel 3's architecture is based on Middleware. -Dotkernel implements the following PSRs: PSR-1, PSR-2, PSR-4, PSR-7, PSR-11. +## What Is Dotkernel? -Currently there are 2 applications: Frontend and Admin, and a 3rd one is under development: API. +A **collection** of PSR-15 Middleware applications built on top of the [**Mezzio**](https://docs.mezzio.dev/mezzio/v3/getting-started/quick-start/) microframework. It is composed of a set of custom and extended [**Laminas**](https://docs.laminas.dev/) components. -## Dotkernel = Dotkernel 1 or Dotkernel 3? +Dotkernel architecture is based on **Middleware**. Dotkernel implements the following PSR's, where applicable: PSR-7, PSR-11, PSR-15, PSR-3, PSR-4, PSR-6, PSR-13, PSR-14, PSR-17, PSR-18, PSR-20. -In posts older than 2017, Dotkernel 1 was referred to as Dotkernel, because it was the only Dotkernel version. -Since the release of Dotkernel 3, it is referred to as Dotkernel 3 or Dotkernel. -All future references to Dotkernel 1 will be explicitly made. +Currently, there are three applications: -### As of Dotkernel 3 Release: +- API +- Admin +- Queue -Dotkernel 1 = Dotkernel 1 -Dotkernel 3 = Dotkernel 3 +## Dotkernel = Dotkernel 1 or the new Dotkernel? -#### Dotkernel = Dotkernel 3 +In posts older than 2017 **Dotkernel 1** was referred to as **Dotkernel** because it was the only Dotkernel version. Since the release of newer versions, we have dropped the number at the end, so currently we refer to our platform as 'Dotkernel'. ## FAQ @@ -62,11 +54,11 @@ A: It combines "Dot", as a representation of the Internet, with "Kernel", the qu **Q: What is Dotkernel 1?** A: A PHP Application Framework built on top of Zend Framework 1, first publicly released in July 2010, with an architecture based on MVC. Its latest version is 1.8 Long Term Support, which per the article will not be followed by a new version, only bugfixes. -**Q: What is Dotkernel 3?** -A: A collection of PSR-7 Middleware applications built on top of the Zend Expressive microframework, composed of a set of custom and extended Zend Framework 3 components, with an architecture based on Middleware. It implements PSR-1, PSR-2, PSR-4, PSR-7, and PSR-11. +**Q: What is Dotkernel?** +A: A collection of PSR-15 Middleware applications built on top of the Mezzio microframework. It implements PSR-7, PSR-11, PSR-15, PSR-3, PSR-4, PSR-6, PSR-13, PSR-14, PSR-17, PSR-18, PSR-20. -**Q: How many applications make up Dotkernel 3?** -A: At the time of the article, there were two available applications, Frontend and Admin, with a third one, API, under development. +**Q: How many applications make up Dotkernel?** +A: At the time of the article, there were three available applications, API, Admin and Queue. **Q: When someone writes just "Dotkernel", which version is meant?** A: In posts older than 2017, "Dotkernel" referred to Dotkernel 1, since it was the only version. Since the release of Dotkernel 3, "Dotkernel" refers to Dotkernel 3, and all future references to Dotkernel 1 are made explicitly. diff --git a/public/md-articles/dotkernel/doctrine-cache-using-symfony-cache.md b/public/md-articles/dotkernel/doctrine-cache-using-symfony-cache.md index 65bcfa97..0e653cf3 100644 --- a/public/md-articles/dotkernel/doctrine-cache-using-symfony-cache.md +++ b/public/md-articles/dotkernel/doctrine-cache-using-symfony-cache.md @@ -16,6 +16,23 @@ Caching stores data the first time it's requested so that later requests can be This 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. It covers the array and filesystem storage adapters, configuring Doctrine's four cache types (result, metadata, query, hydration), and marking entities and queries as cacheable. +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. + +When a user visits a website or interacts with a web application, various resources such as images, scripts, and database queries are requested from the server. +Retrieving these resources can sometimes be time-consuming, especially if they require complex processing or querying a database. + +To speed up this process and improve overall performance, developers implement caching mechanisms. +When data is first requested, it's stored in a cache. +Then, when subsequent requests for the same data are made, the application can retrieve it from the cache instead of fetching it from the original source. +This reduces the time it takes to serve the content to the user because accessing data from the cache is typically much faster than retrieving it from the original source. + +> This article is a follow-up to the [previous article](https://www.dotkernel.com/how-to/doctrine-cache-in-mezzio-and-dotkernel/) where we tackled the caching topic. + +![](/uploads/article/019f8a80-cc52-73f7-afe5-9255a3bb4681/sdasdadsa.drawio.png) + +In this article our focus will be on enabling the [dot-cache](https://packagist.org/packages/dotkernel/dot-cache) component and effectively implementing caching in [Dotkernel Admin](https://github.com/dotkernel/admin/). + ## Installation Run the following command in your project directory: diff --git a/public/md-articles/dotkernel/doctrine-enum-implementation-in-dotkernel.md b/public/md-articles/dotkernel/doctrine-enum-implementation-in-dotkernel.md index e8720a93..84a6f134 100644 --- a/public/md-articles/dotkernel/doctrine-enum-implementation-in-dotkernel.md +++ b/public/md-articles/dotkernel/doctrine-enum-implementation-in-dotkernel.md @@ -24,15 +24,18 @@ The update introduces the detection of `enumType` and `options.values` from a pr ### Old Setup ```php -# +#[Entity] class Card { - # - # - # + #[Id] + #[GeneratedValue] + #[Column] public int $id; - #], + #[Column( + type: Types::ENUM, + enumType: Suit::class, + options: ['values' => ['H', 'D', 'C', 'S']], )] public Suit $suit; } @@ -41,15 +44,15 @@ class Card ### New Setup ```php -# +#[Entity] class Card { - # - # - # + #[Id] + #[GeneratedValue] + #[Column] public int $id; - # + #[Column(type: Types::ENUM)] public Suit $suit; } ``` @@ -77,13 +80,16 @@ Our old implementation defined the values like below, for the `User` entity. ```php public const STATUS_PENDING = 'pending'; public const STATUS_ACTIVE = 'active'; -public const STATUSES = ; +public const STATUSES = [ + self::STATUS_PENDING, + self::STATUS_ACTIVE, +]; ``` The column for the ORM was defined like this, as a simple string, with `pending` as its default value: ```php -# +#[ORM\Column(name: "status", type: "string", length: 20)] protected string $status = self::STATUS_PENDING; ``` @@ -155,9 +161,10 @@ If you create your own enum types, make sure to update the `NAME` constant and t Let's register the custom type in `config/autoload/doctrine.global.php` under the `types` key: ```php -'types' => +'types' => [ +[...] UserStatusEnumType::NAME => UserStatusEnumType::class, - +[...] ], ``` @@ -170,7 +177,10 @@ $this->getFilterChain() ->attach(fn($value) => $value === null ? UserStatusEnum::Active : UserStatusEnum::from($value)); $this->getValidatorChain() - ->attachByName(InArray::class, , true); + ->attachByName(InArray::class, [ + 'haystack' => UserStatusEnum::cases(), + 'message' => sprintf(Message::INVALID_VALUE, 'status'), + ], true); ``` The above ensures that the new `UserStatusEnum` class is used for the `status` column updates. @@ -178,7 +188,7 @@ The above ensures that the new `UserStatusEnum` class is used for the `status` c The `User` entity uses the new `UserStatusEnum` class. ```php -#)] +#[ORM\Column(type: 'user_status_enum', options: ['default' => UserStatusEnum::Pending])] protected UserStatusEnum $status = UserStatusEnum::Pending; ``` @@ -200,7 +210,7 @@ Dotkernel checks the user status during login in `src/User/src/Repository/UserRe If the user is not activated, the login is rejected. ```php -if ($clientEntity->getName() === 'frontend' && $result !== UserStatusEnum::Active) { +if ($clientEntity->getName() === 'frontend' && $result['status'] !== UserStatusEnum::Active) { throw new OAuthServerException(Message::USER_NOT_ACTIVATED, 6, 'inactive_user', 401); } ``` @@ -210,14 +220,14 @@ A new user is created using the `enum` type and `pending` as the default. ```php $user = (new User()) ->setDetail($detail) - ->setIdentity($data) - ->usePassword($data) - ->setStatus($data ?? UserStatusEnum::Pending); + ->setIdentity($data['identity']) + ->usePassword($data['password']) + ->setStatus($data['status'] ?? UserStatusEnum::Pending); ``` Note the `status` column in the migration query which now looks like this: -```php +``` $this->addSql(' CREATE TABLE user ( uuid BINARY(16) NOT NULL, diff --git a/public/md-articles/dotkernel/geoip-city-removed-from-dotkernel.md b/public/md-articles/dotkernel/geoip-city-removed-from-dotkernel.md index 677534eb..5ac2408f 100644 --- a/public/md-articles/dotkernel/geoip-city-removed-from-dotkernel.md +++ b/public/md-articles/dotkernel/geoip-city-removed-from-dotkernel.md @@ -51,8 +51,8 @@ if(function_exists('geoip_db_avail') && geoip_db_avail(GEOIP_CITY_EDITION_REV0) $record = geoip_record_by_name($ip); if(!empty($record)) { - $countryCode = $record; - $countryName = $record; + $countryCode = $record['country_code']; + $countryName = $record['country_name']; $country = $countryCode != false ? $countryCode : 'unknown'; $country = $countryName != false ? $countryName : 'NA'; } @@ -79,8 +79,7 @@ A: Users running an older Dotkernel version together with GeoIP extension versio A: On some operating systems, Apache still responds with HTTP Status Code 200 (OK) but with a page containing a generic "internal error or misconfiguration" message, and the error might not be logged since it's generated by the GeoIP extension rather than by the PHP code. **Q: How can you get rid of the error if you don't need GeoIP City?** -A: Comment out or remove the relevant lines in Dotkernel/admin/System.php (the Admin/System model) and in library/Dot/GeoIp.php (the Dot_GeoIp class). -These changes were also made in the latest version of Dotkernel, but this fix should only be used if you don't intend to use GeoIP City. +A: Comment out or remove the relevant lines in Dotkernel/admin/System.php (the Admin/System model) and in library/Dot/GeoIp.php (the Dot_GeoIp class). These changes were also made in the latest version of Dotkernel, but this fix should only be used if you don't intend to use GeoIP City. **Q: What does the article recommend going forward?** A: It recommends special coding for GeoIP City and for the GeoIP extension in general, and notes that GeoIP is an obsolete extension, with Maxmind recommending GeoIP2 instead. diff --git a/public/md-articles/dotkernel/highcharts-integration-in-dotkernel-1-6-0.md b/public/md-articles/dotkernel/highcharts-integration-in-dotkernel-1-6-0.md index 23b8bf1c..e585e517 100644 --- a/public/md-articles/dotkernel/highcharts-integration-in-dotkernel-1-6-0.md +++ b/public/md-articles/dotkernel/highcharts-integration-in-dotkernel-1-6-0.md @@ -11,6 +11,7 @@ language: "en" # Highcharts Integration in Dotkernel 1.6.0 ## TL;DR + Dotkernel 1.6.0 integrates the Highcharts charting library, offering a new, intuitive and interactive charting experience. Sample charts (pie, column and line) were added to the admin, and the library ships in the project's externals directory. @@ -22,6 +23,8 @@ The admin includes samples built with Highcharts: - A column chart - A line chart +[![](/uploads/article/019f8a80-cc3b-71e4-913b-5c28321fc438/highcharts-1024x651.png)](/uploads/2012/05/highcharts.png) + The Highcharts library itself can be found in the **externals** directory of the project. ## Example: line chart configuration @@ -55,7 +58,9 @@ chart = new Highcharts.Chart({ min: 0 }, xAxis: { - categories: , + categories: ['1','2','3','4','5','6','7','8','9','10','11','12', + '13','14','15','16','17','18','19','20','21','22', + '23','24','25','26','27','28','29','30','31'], labels: { rotation: -45, align: 'right', diff --git a/public/md-articles/dotkernel/test-article.md b/public/md-articles/dotkernel/test-article.md index 610f7998..722fa093 100644 --- a/public/md-articles/dotkernel/test-article.md +++ b/public/md-articles/dotkernel/test-article.md @@ -16,7 +16,9 @@ This is a test article added to the dotkernel category for testing purposes. Thi ## Overview -This is a test article used for fixture and testing purposes. +![](/uploads/article/019fd6ae-4ce6-71cc-99aa-33741b77758c/test-article.png) + +This is a test article used for fixture and testing purposes. This article is now updated. ## FAQ diff --git a/public/md-articles/dotkernel/using-dotkernel-with-composer-dependencies.md b/public/md-articles/dotkernel/using-dotkernel-with-composer-dependencies.md index b8228e17..3244bf82 100644 --- a/public/md-articles/dotkernel/using-dotkernel-with-composer-dependencies.md +++ b/public/md-articles/dotkernel/using-dotkernel-with-composer-dependencies.md @@ -76,6 +76,8 @@ Barcode::factory( Both examples render the same barcode. +[![Resulting barcode.](/uploads/article/019f8a80-cc48-70bf-ba9f-ddc6d1436f1a/zend.barcode.introduction.example-1.png)](/uploads/2016/04/zend.barcode.introduction.example-1.png) + ### Tip The first (ZF1-style) example will work for both namespaced and non-namespaced dependencies if you add this as the first line: diff --git a/public/md-articles/dotkernel/zend-console-implementation-in-dotkernel.md b/public/md-articles/dotkernel/zend-console-implementation-in-dotkernel.md index f66d5c0e..acf47113 100644 --- a/public/md-articles/dotkernel/zend-console-implementation-in-dotkernel.md +++ b/public/md-articles/dotkernel/zend-console-implementation-in-dotkernel.md @@ -54,7 +54,7 @@ A: The bootstrap is found in Console/index.php, and it has one controller in Con A: There are two arguments: -a, the name of the action to execute, and -e, the environment as defined in application.ini (development, staging, or production), which is optional and defaults to production. **Q: What happens to extra arguments passed to the script?** -A: Any other arguments set when calling the script will be available in the controller in the registry's arguments array. +A: Any other arguments set when calling the script will be available in the controller in the `$registry->arguments` array. **Q: What example actions are bundled with the Console controller?** A: The bundled controller has two example actions: count-users, which demonstrates how to interact with models, and send-newsletter, which reads the newsletter from the command line. diff --git a/public/md-articles/dotkernel3/development-report-december-11-2017.md b/public/md-articles/dotkernel3/development-report-december-11-2017.md index 72832278..295b36a0 100644 --- a/public/md-articles/dotkernel3/development-report-december-11-2017.md +++ b/public/md-articles/dotkernel3/development-report-december-11-2017.md @@ -38,4 +38,3 @@ A: Release notes were added for Dotkernel3 frontend and admin, a Webpack tutoria **Q: Who was credited as a contributor in this report?** A: JapSeyz was thanked as a contributor. - diff --git a/public/md-articles/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.md b/public/md-articles/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.md index 2556ade6..1bb19c9c 100644 --- a/public/md-articles/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.md +++ b/public/md-articles/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.md @@ -55,6 +55,8 @@ Using both API and Admin together brings several benefits: The file structure can be configured to use a **Core** module - a common code repository shared between API and Admin, ensuring entities and queries stay consistent. Entities (products, articles, etc.) are the building blocks of the application, and queries handle CRUD interactions with them. +![](/uploads/article/019f8a80-cc94-7123-80d3-9c21a30c19ea/Core2.png) + ### Implement only the handlers you need Having many entities in the Core doesn't require implementing handlers for each one in both API and Admin. @@ -66,6 +68,10 @@ API and Admin share a file structure, so becoming familiar with one makes the ot This means onboarding is easy and the applications can be maintained by fewer developers - potentially a single developer, at least initially. By contrast, an API+Angular approach often needs two developers working together to implement a new feature. +![](/uploads/article/019f8a80-cc94-7123-80d3-9c21a30c19ea/files-api.jpg) + +![](/uploads/article/019f8a80-cc94-7123-80d3-9c21a30c19ea/files-admin-1.jpg) + ### Can satisfy any application size Thanks to the shared Core module, API and Admin evolve together, forming a good starting point for anything from microservices to enterprise-grade APIs. @@ -79,6 +85,8 @@ The Dotkernel architecture aims to support several architectural designs: - **Domain-Driven Design (DDD)** - the focus is on custom solutions for business logic; rather than building reusable services for a Service Oriented Architecture (SOA), only the specific components needed to satisfy requirements are implemented. - **Hexagonal Architecture** - divides a system into several loosely-coupled, interchangeable components (application core, database, user interface, test scripts, interfaces with other systems), as an alternative to traditional layered architecture. +![](/uploads/article/019f8a80-cc94-7123-80d3-9c21a30c19ea/Flow2.png) + ### Supported by an active community The Dotkernel development team actively investigates and implements recommended design patterns. diff --git a/public/md-articles/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.md b/public/md-articles/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.md index a7eba542..7b43665a 100644 --- a/public/md-articles/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.md +++ b/public/md-articles/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.md @@ -67,6 +67,8 @@ The queue system has an active daemon that listens for TCP connections on a spec This supports a large number of requests per second without overloading. Operations are then scheduled for execution when resources are available, using the FIFO (First-In, First-Out) method, where the oldest request is processed first, followed by newer requests. +![](/uploads/article/019f8a80-cc97-70d1-a431-cb612f641c19/Queue-process2.png) + ## Main Features The following features are already implemented and have been tried and tested extensively in the team's live projects. diff --git a/public/md-articles/headless-platform/dotmaker-generate-common-code-in-dotkernel.md b/public/md-articles/headless-platform/dotmaker-generate-common-code-in-dotkernel.md index 58beb980..db026bf2 100644 --- a/public/md-articles/headless-platform/dotmaker-generate-common-code-in-dotkernel.md +++ b/public/md-articles/headless-platform/dotmaker-generate-common-code-in-dotkernel.md @@ -15,6 +15,8 @@ language: "en" DotMaker (`dotkernel/dot-maker`) programmatically generates project files and directories matching the Dotkernel file structure inspired by Mezzio. It 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. +https://www.youtube.com/watch?v=CPDilXP2kAc + ## Why use dot-maker? Creating a new module manually requires a relatively large number of files and folders. diff --git a/public/md-articles/headless-platform/evolution-pattern-versus-api-versioning.md b/public/md-articles/headless-platform/evolution-pattern-versus-api-versioning.md index 1658160d..65fd5018 100644 --- a/public/md-articles/headless-platform/evolution-pattern-versus-api-versioning.md +++ b/public/md-articles/headless-platform/evolution-pattern-versus-api-versioning.md @@ -52,6 +52,20 @@ For example, on the `/user` endpoint: ### Announcing deprecation Deprecation is announced by including an updated response header via handler attributes, with customizable values for `sunset`, `link`, and `deprecationReason`. + +```php +#[ResourceDeprecation( + sunset: '2026-01-01', + link: 'https://docs.dotkernel.org/api-documentation/v7/tutorials/api-evolution/', + deprecationReason: 'Resource deprecation example.', + rel: 'sunset', + type: 'text/html' +)] +class OldHandler implements RequestHandlerInterface +{ +... +``` + A deprecated endpoint's response includes headers similar to: ``` diff --git a/public/md-articles/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.md b/public/md-articles/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.md index 308b253a..95c9a7ce 100644 --- a/public/md-articles/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.md +++ b/public/md-articles/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.md @@ -60,6 +60,10 @@ Generates temporary, unique 6-digit codes that change every 30 seconds, via an A 3. User submits the current 6-digit code, or alternatively a single-use recovery code. 4. If the code is valid, the user is logged in. +Below is a simplified flow for the 2FA with TOTP mechanism. + +![](/uploads/article/019f8a80-cc99-7003-89fb-1a4493d92a4c/totp-flow.jpg) + ## Installation steps ### Step 1 - Install the package @@ -75,29 +79,29 @@ composer require dotkernel/dot-totp Following the Dotkernel file structure, add the files below (downloadable from the [official code examples](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp)): Forms: -- `src/Admin/src/Form/RecoveryForm.php` -- `src/Admin/src/Form/TotpForm.php` +- [`src/Admin/src/Form/RecoveryForm.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/src/Form/RecoveryForm.php) +- [`src/Admin/src/Form/TotpForm.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/src/Form/TotpForm.php) Handlers (in `src/Admin/src/Handler/Account/`): -- `GetDisableTotpFormHandler.php` -- `GetEnableTotpFormHandler.php` -- `GetRecoveryFormHandler.php` -- `GetTotpHandler.php` -- `PostDisableTotpHandler.php` -- `PostEnableTotpHandler.php` -- `PostValidateRecoveryHandler.php` -- `PostValidateTotpHandler.php` +- [`GetDisableTotpFormHandler.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/src/Handler/Account/GetDisableTotpFormHandler.php) +- [`GetEnableTotpFormHandler.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/src/Handler/Account/GetEnableTotpFormHandler.php) +- [`GetRecoveryFormHandler.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/src/Handler/Account/GetRecoveryFormHandler.php) +- [`GetTotpHandler.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/src/Handler/Account/GetTotpHandler.php) +- [`PostDisableTotpHandler.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/src/Handler/Account/PostDisableTotpHandler.php) +- [`PostEnableTotpHandler.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/src/Handler/Account/PostEnableTotpHandler.php) +- [`PostValidateRecoveryHandler.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/src/Handler/Account/PostValidateRecoveryHandler.php) +- [`PostValidateTotpHandler.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/src/Handler/Account/PostValidateTotpHandler.php) Templates: -- `src/Admin/templates/admin/recovery-form.html.twig` +- [`src/Admin/templates/admin/recovery-form.html.twig`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Admin/templates/admin/recovery-form.html.twig) Middleware: -- `src/App/src/Middleware/CancelUrlMiddleware.php` -- `src/App/src/Middleware/TotpMiddleware.php` +- [`src/App/src/Middleware/CancelUrlMiddleware.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/App/src/Middleware/CancelUrlMiddleware.php) +- [`src/App/src/Middleware/TotpMiddleware.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/App/src/Middleware/TotpMiddleware.php) ### Step 3 - Apply the entity trait and migrate the database -Apply the trait at `src/Core/src/App/src/Entity/TotpTrait.php` to any entity that requires 2FA, then migrate the new columns onto that entity's table: `totpSecret`, `totp_enabled`, and `recovery_codes`. +Apply the trait at [`src/Core/src/App/src/Entity/TotpTrait.php`](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/src/Core/src/App/src/Entity/TotpTrait.php) to any entity that requires 2FA, then migrate the new columns onto that entity's table: `totpSecret`, `totp_enabled`, and `recovery_codes`. ### Step 4 - Register the remaining snippets @@ -105,10 +109,10 @@ The `_misc` folder in the code examples contains four required additions: | Snippet | Destination | |---|---| -| Enable/disable 2FA button (`totp-append-view-account.html.twig`) | `view-account.html.twig`, or a new page | -| Routes updates (`totp-append-routes.php`) | `src/Admin/src/RoutesDelegator.php` | -| Pipeline updates (`totp-append-Pipeline.php`) | `config/pipeline.php`, after `$app->pipe(AuthMiddleware::class);` | -| ConfigProvider updates (`totp-append-ConfigProvider.php`) | `src/Admin/src/ConfigProvider.php` | +| [Enable/disable 2FA button](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/_misc/totp-append-view-account.html.twig) (`totp-append-view-account.html.twig`) | `view-account.html.twig`, or a new page | +| [Routes updates](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/_misc/totp-append-routes.php) (`totp-append-routes.php`) | `src/Admin/src/RoutesDelegator.php` | +| [Pipeline updates](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/_misc/totp-append-Pipeline.php) (`totp-append-Pipeline.php`) | `config/pipeline.php`, after `$app->pipe(AuthMiddleware::class);` | +| [ConfigProvider updates](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp/_misc/totp-append-ConfigProvider.php) (`totp-append-ConfigProvider.php`) | `src/Admin/src/ConfigProvider.php` | ## Using TOTP in Dotkernel Admin (end-user flow) @@ -116,17 +120,29 @@ The `_misc` folder in the code examples contains four required additions: 1. Navigate to the account profile (top-right image in Dotkernel Admin). A TOTP box with an "Enable TOTP" button is shown. + +![](/uploads/article/019f8a80-cc99-7003-89fb-1a4493d92a4c/profile-totp-deactivated.jpg) + 2. Click "Enable TOTP". A QR code is displayed. An Authenticator app on a mobile device is required. + +![](/uploads/article/019f8a80-cc99-7003-89fb-1a4493d92a4c/totp-activate-qr.jpg) + 3. Scan the QR code with the mobile device. 4. Enter the 6-digit code generated by the Authenticator app. The code refreshes every 30 seconds. 5. Save the recovery codes shown during activation in a secure location - each is usable only once. + +![](/uploads/article/019f8a80-cc99-7003-89fb-1a4493d92a4c/totp-recovery-codes.jpg) + 6. If the code is valid, the user is logged in and TOTP is activated for the account. ### Logging in with TOTP enabled 1. Enter username and password as before. 2. Submit the current code from the Authenticator app, or alternatively a recovery code. + +![](/uploads/article/019f8a80-cc99-7003-89fb-1a4493d92a4c/totp-ask-code.jpg) + 3. On success, the user is logged in. ## FAQ @@ -150,7 +166,7 @@ A: You need to migrate three new columns onto the entity that uses the TotpTrait ## Resources -- dot-totp on GitHub: https://github.com/dotkernel/dot-totp -- Dotkernel Admin on GitHub: https://github.com/dotkernel/admin -- Official tutorial - Installing dot-totp into Dotkernel Admin: https://docs.dotkernel.org/admin-documentation/v7/tutorials/install-dot-totp/ -- Complete code examples: https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp +- [dot-totp on GitHub](https://github.com/dotkernel/dot-totp) +- [Dotkernel Admin on GitHub](https://github.com/dotkernel/admin) +- [Official tutorial - Installing dot-totp into Dotkernel Admin](https://docs.dotkernel.org/admin-documentation/v7/tutorials/install-dot-totp/) +- [Complete code examples](https://github.com/dotkernel/admin-documentation/tree/main/code_examples/totp) diff --git a/public/md-articles/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.md b/public/md-articles/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.md index 93d10376..7f0dbebb 100644 --- a/public/md-articles/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.md +++ b/public/md-articles/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.md @@ -83,6 +83,8 @@ Once the shared Core submodule is separated and imported into each application, - Admin + Core - Queue + Core +![](/uploads/article/019f8a80-cc95-70fa-bdb7-28854eb6d673/core-queue2.png) + > Each box in the image is a different Git repository. Whenever work begins on a new feature or update, the devs should normally have the most recent Core in their development environment. @@ -111,6 +113,8 @@ This design pattern ensures: As your platform expands, each new application connects to the Dotkernel Headless Platform via the central API which services everything the other applications require. This ensures consistency throughout your platform, while allowing any number of outside connections as requirements arise. +![](/uploads/article/019f8a80-cc95-70fa-bdb7-28854eb6d673/api-comms4-2.png) + ## FAQ **Q: What is the Core submodule in Dotkernel's Headless Platform?** diff --git a/public/md-articles/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.md b/public/md-articles/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.md index 4507da43..5b242e07 100644 --- a/public/md-articles/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.md +++ b/public/md-articles/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.md @@ -41,7 +41,7 @@ We use the package [ramsey/uuid](https://github.com/ramsey/uuid) to generate the In this way we have full control over the UUID version in use. This solution means you don't depend on extensions or a particular version of the database. -> To ensure you have support for the `UUID` data type, you must use PostgreSQL or MariaDB v10.7 or later. +> To ensure you have support for the `UUID` data type, you must use PostgreSQL or MariaDB v11.4 or later. This also brings along a less-impactful change that still deserves mentioning: the table columns named `uuid` have been renamed to `id`. @@ -65,7 +65,7 @@ A: v7 removes the Evolution pattern's support for Method Deprecation, and drops A: Dotkernel uses the ramsey/uuid package to generate the UUID before storing it in the database. This gives full control over the UUID version in use, so the application doesn't depend on extensions or a particular database version. **Q: Which databases support the UUID data type required by v7?** -A: You must use PostgreSQL or MariaDB v10.7 or later to have support for the UUID data type. +A: You must use PostgreSQL or MariaDB v11.4 or later to have support for the UUID data type. **Q: What else changed alongside the move to native UUID?** A: Table columns previously named `uuid` have been renamed to `id`. The database configuration was also clarified so it's more obvious which connection is the default and how to switch to another database connection. diff --git a/public/md-articles/how-to/using-the-urlgenerator-work-in-fastroute.md b/public/md-articles/how-to/using-the-urlgenerator-work-in-fastroute.md index f2215294..2deb2f0e 100644 --- a/public/md-articles/how-to/using-the-urlgenerator-work-in-fastroute.md +++ b/public/md-articles/how-to/using-the-urlgenerator-work-in-fastroute.md @@ -50,7 +50,7 @@ To use it in a controller, you have access to `$this->url();`, which takes the r A controller redirect may then look like this: ```php -return new RedirectResponse($this->url('contact', )); +return new RedirectResponse($this->url('contact', ['action' => 'thank-you'])); ``` Using the URLGenerator in views is approximately the same, since you have access to the global `path()` method. diff --git a/public/md-articles/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.md b/public/md-articles/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.md index 1e39d4cd..58fd7825 100644 --- a/public/md-articles/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.md +++ b/public/md-articles/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.md @@ -18,6 +18,8 @@ On top of that, it ships plugins for popular CMS platforms and PHP frameworks su ## Free PHP, HTML, CSS, JavaScript Editor (IDE) - Codelobster PHP Edition +![Free PHP, HTML, CSS, JavaScript editor (IDE) - Codelobster PHP Edition](http://www.codelobster.com/images/clphped.gif) + 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](http://www.codelobster.com/). diff --git a/public/md-articles/javascript/javascript-email-validator.md b/public/md-articles/javascript/javascript-email-validator.md index 772e75b9..eb6a8d78 100644 --- a/public/md-articles/javascript/javascript-email-validator.md +++ b/public/md-articles/javascript/javascript-email-validator.md @@ -25,10 +25,10 @@ This will also validate emails like `username1+username2@gmail-domain.co.uk`. ## FAQ **Q: What problem does this email validator solve?** -A: Common email regex patterns fail to allow the plus (+) character in the username and the dash (-) character anywhere in the address or domain, even though both are commonly used (plus for categorization on Gmail, dash in domain names). +A: Common email regex patterns fail to allow the plus (+) character in the username and the dash (-) character anywhere in the address or domain. The plus sign is used by many people, especially on Gmail, for categorization, and dashes commonly appear in domain names, so a validator that rejects them is too strict. **Q: What is the suggested regex solution?** -A: The regular expression `^+(\.+)*@+(\.+)*\.({2,})$` (case-insensitive) that permits both characters in the appropriate parts of the address. +A: The article proposes the regular expression ^+(\.+)*@+(\.+)*\.({2,})$ (case-insensitive) as a replacement that permits both the plus and dash characters in the appropriate parts of the address. **Q: What kind of email addresses does this regex validate?** -A: Addresses like `username1+username2@gmail-domain.co.uk`, combining a plus-separated username with a dashed domain. +A: According to the article, this pattern will also successfully validate addresses like username1+username2@gmail-domain.co.uk, which combine a plus-separated username with a dashed domain. diff --git a/public/md-articles/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.md b/public/md-articles/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.md index 94241d33..440145f6 100644 --- a/public/md-articles/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.md +++ b/public/md-articles/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.md @@ -15,59 +15,75 @@ language: "en" [Dotkernel Light](https://github.com/dotkernel/light) is a PSR-15 compliant application built on Mezzio and Laminas, aimed at simple websites like presentation sites. Since 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. +[Dotkernel Light](https://github.com/dotkernel/light) is the smallest complete Mezzio application - a PSR-15 pipeline, routing and templating, with nothing to strip out. A good starting point for a simple site, like a presentation site, that can be expanded as needed. + +Dotkernel Light has come a long way since our [last update](https://www.dotkernel.com/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site/). It's just as useful for a presentation site, but our perfectionist devs thought we could do better. Let's see what we have improved to make your use of Light that much more convenient. + ## Migrating from controllers to handlers -dotkernel/dot-controller isn't going away, but Dotkernel Light has switched to PSR-15 handlers instead. -PSR-7 defines the request handler as an individual component that processes a request and produces a response; since the request can be filtered or augmented in a middleware architecture by the time it reaches custom code, handlers split code into more manageable chunks and a cleaner file structure, and make it easier to understand an application's functionality. +[dotkernel/dot-controller](https://github.com/dotkernel/dot-controller) isn't going away, but we have switched to [PSR-15](https://www.php-fig.org/psr/psr-15/) handlers instead. +[PSR-7 HTTP message interfaces](https://www.php-fig.org/psr/psr-7/) defines the request handler as an individual component that processes a request and produces a response. +Given that it's used in a middleware architecture, the request can be wildly different (e.g. filtered, augmented) by the time it reaches your custom code. +Handlers split the code into more manageable chunks and cleaner file structure. +An added benefit is it allows us to figure out an application's functionality easier. +We will expand on this aspect further down in the article, in the 'Naming pattern' chapter. ## Implementing PSR-15 compatible handlers -PSR-15, defined by the PHP Framework Interop Group, specifies common interfaces for HTTP server request handlers and middleware that use HTTP messages as described by PSR-7. -The MVC design pattern is considered obsolete by respected members of the PHP community, so Dotkernel Light is replacing it with the middleware and HTTP message design pattern. +We are striving to implement every feature with the most modern standards and design patterns in mind. +The [PHP Framework Interop Group](https://www.php-fig.org/) defines PSR-15 as common interfaces for HTTP server request handlers and HTTP middleware that use HTTP messages as described by PSR-7. +The MVC design pattern is considered obsolete by respected members of the PHP community, so we are replacing it with Middleware and HTTP message design pattern. ## Adopting a naming pattern for PSR-15 handlers -A naming pattern was devised for PSR-15 handlers that highlights the method, resource, and action for each file, so navigation and onboarding are easier. +Now the file names reflect the functionality at a glance. +We devised a naming pattern for our PSR-15 handlers that highlights the method, resource and action for each file to make navigation and onboarding that much easier. +We like to keep things tidy, as well as informative. ## Implementing Vite -[Vite](https://vite.dev/) replaces [webpack](https://webpack.js.org) as the static modules bundler. -It concatenates and compresses `.css` and `.js` files for faster downloads, and preprocesses `.scss` files into `.css`. -Vite was configured to work similarly to webpack for existing Light developers; the migration was justified by easier dependency management and execution speed, and it's highly recommended by the PHP community. +[Vite](https://vite.dev/) replaces [webpack](https://webpack.js.org) as our static modules bundler. +It concatenates and compresses `.css` and `.js` files to enable faster downloads. +It also preprocesses `.scss` files into `.css`. +As far as devs using Light are concerned, Vite was configured to work similarly to webpack. +We decided the technical advantages like easier dependency management and execution speed justified the migration to Vite. +It's also highly recommended by the PHP community. ## Replacing Psalm with PHPStan -Both Psalm and PHPStan are respected, widely-used static analysis tools. -Some big names in the PHP ecosystem made the same switch, so Dotkernel followed suit. -Functionality is similar to the previous tool, but growing interest and improved detection quality made PHPStan the better choice. +Both [Psalm](https://psalm.dev/) and [PHPStan](https://phpstan.org/) are respected and widely-used static analysis tools. +Some of the big names in the PHP ecosystem have opted for the same switch, so we decided to follow suit. +Ultimately, the functionality is similar to our previous tool, but the growing interest and improved detection quality make PHPStan a great choice for our applications. PHPStan is configured to run at rule level 8 to help prevent bugs and write better code. ## Other updates +Some of the smaller updates involve: + - Adding support for PHP 8.4. -- Enabling PHPStan and Qodana to run for PHP 8.4 as well. +- Enabling [PHPStan](https://phpstan.org/) and [Qodana](https://www.jetbrains.com/qodana/) to run for PHP 8.4 as well. - Cleaning up and adapting the error configuration file to make use of the latest features. -- Updating laminas/laminas-coding-standard to the latest major version. -- Updating composer.json to be up-to-date with the latest releases for each dependency, and removing obsolete items. -- Implementing a more reliable and efficient post-install script. -- Removing dotkernel/dot-twigrenderer in favor of a direct implementation of mezzio/mezzio-twigrenderer. +- Updating [laminas/laminas-coding-standard](https://github.com/laminas/laminas-coding-standard) to the latest major version. +- Updating composer.json to make sure it's up-to-date with the latest releases for each dependency, as well as to remove any now-obsolete items. +- Implementing a more reliable and efficient post install script to help get you up and running that much faster. +- Removing [dotkernel/dot-twigrenderer](https://github.com/dotkernel/dot-twigrenderer) in favor of a direct implementation of [mezzio/mezzio-twigrenderer](https://github.com/mezzio/mezzio-twigrenderer). ## FAQ **Q: Why did Dotkernel Light switch from controllers to handlers?** -A: Handlers split code into more manageable chunks and a cleaner file structure, and make it easier to understand an application's functionality, especially given that requests can be filtered or augmented by middleware before reaching custom code. +A: Dotkernel Light switched to PSR-15 handlers instead of dotkernel/dot-controller (which isn't going away, but is no longer used here). Since the request can be filtered or augmented by the time it reaches custom code in a middleware architecture, handlers split the code into more manageable chunks and a cleaner file structure, and make an application's functionality easier to figure out. **Q: What naming pattern is used for the new PSR-15 handlers?** -A: One that highlights the method, resource, and action for each handler file, so file names reflect functionality at a glance. +A: A naming pattern was devised that highlights the method, resource, and action for each handler file, so file names reflect their functionality at a glance, making navigation and onboarding easier. **Q: Why was Vite adopted instead of webpack?** -A: For easier dependency management and execution speed, and because it's highly recommended by the PHP community; it was configured to work similarly to webpack for existing developers. +A: Vite replaces webpack as the static modules bundler, concatenating and compressing .css and .js files and preprocessing .scss files into .css. It was configured to work similarly to webpack for developers using Light, and was chosen for its easier dependency management, execution speed, and its recommendation by the PHP community. **Q: Why was Psalm replaced with PHPStan?** -A: Growing interest and improved detection quality, following the lead of other big names in the PHP ecosystem; PHPStan runs at rule level 8. +A: Both Psalm and PHPStan are respected, widely-used static analysis tools with similar functionality, but growing interest and improved detection quality led Dotkernel to follow other big names in the PHP ecosystem and switch to PHPStan, which is configured to run at rule level 8 to help prevent bugs and write better code. **Q: What other smaller updates were made to Dotkernel Light?** -A: PHP 8.4 support, PHPStan/Qodana running on PHP 8.4, error configuration cleanup, an updated coding standard, refreshed composer.json dependencies, a more reliable post-install script, and removal of dot-twigrenderer in favor of mezzio-twigrenderer directly. +A: Other updates include adding support for PHP 8.4, enabling PHPStan and Qodana to run for PHP 8.4, cleaning up the error configuration file, updating laminas/laminas-coding-standard to its latest major version, updating composer.json dependencies and removing obsolete items, implementing a more reliable and efficient post-install script, and removing dotkernel/dot-twigrenderer in favor of a direct implementation of mezzio/mezzio-twigrenderer. ## Resources diff --git a/public/md-articles/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.md b/public/md-articles/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.md index 70bad038..f4df2577 100644 --- a/public/md-articles/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.md +++ b/public/md-articles/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.md @@ -15,31 +15,72 @@ language: "en" 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`. The change is aimed at static pages only - any method other than GET (post, put, delete) returns a 405 status code. +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. + +`RoutesDelegator.php` is used to configure the routes in Dotkernel applications. +`Routing` allows web applications to respond to user requests by executing the correct code based on URL paths. +The dynamic aspect discussed in this article moves the relevant items for each route into the `local.php` file. +The RoutesDelegator then reads the route configuration and generates the routes. + +> This is the first in a series of articles for switching from controllers to handlers that are PSR-15 compliant. It's aimed at static pages alone, so any other method like `post`, `put` or `delete` will return a `405 status code`. + ## The old way of doing things -Routes were declared in a `RoutesDelegator.php` file present in the `src` folder of each module, with entries like: +In the past, declaring routes had a more static, hard-coded approach. All our modules have a `RoutesDelegator.php` file in the `src` folder for each module. Each route would have an entry like the one below: ```php -$app->get('/page', , 'page'); +$app->get('/page[/{action}]', [GetPageViewHandler::class], 'page'); ``` -This route serves urls like `/page/about` or `/page/who-we-are`. -Each entry required: +This route would be used for urls like `/page/about` or `/page/who-we-are` that we will reference later. + +These are the required items: -- A **method**, such as `get`, aimed at static pages. -- A **path** used to direct execution to a handler, such as `/page`, which also uses the optional `action` parameter. -- A **handler** to execute, such as `GetPageViewHandler`, designed to display static pages out of the box but expandable as needed. -- A unique **route name**, such as `page`, referenceable for redirects or authorization. +- A **method**, in this case `get` which is aimed at static pages. +- A **path** used to direct the execution to a handler, in this case `/page[/{action}]` which also uses the optional parameter `action`. +- A **handler** to be executed, like `GetPageViewHandler` which is designed to display static pages out of the box, but can be expanded as needed. +- A unique **route name**, like `page` which can be referenced to generate redirects or set up authorization. -In a live application the route list can grow to many entries across areas like product list, product details, checkout, contact us, reports, order history, and blog - grouped into modules, meaning a routing error could require digging through multiple RoutesDelegators. +In a live application the route list can quickly grow to many more entries, each with its own logic: + +- Product list +- Product details +- Checkout +- Contact us +- Reports +- Order History +- Blog + +These items can be grouped into modules, meaning you have to dig into multiple RoutesDelegators when a routing error occurs. ## The new approach -The update centralizes route configuration in `config/autoload/local.php`, under a `routes` array. -`RoutesDelegator.php` reads this configuration and generates the routes, so it doesn't need to be touched most of the time: +The update centralizes the route configuration in the `config/autoload/local.php` file. +Here is how the routing looks out of the box: + +```php +'routes' => [ + 'page' => [ + 'about' => 'about', + 'who-we-are' => 'who-we-are', + ], +], +``` + +This supports the urls `/page/about` and `/page/who-we-are`. + +Let's list the components under the `routes` array: + +- `page` is the module name. +- The key `about` is used to build the page's path. +- The value `about` is the template file. + +In this setup, the `RoutesDelegator` file doesn't need to be touched most of the time. +This is how it generates the routes for each page: ```php -$routes = $container->get('config') ?? []; +$routes = $container->get('config')['routes'] ?? []; foreach ($routes as $prefix => $moduleRoutes) { foreach ($moduleRoutes as $routeUri => $templateName) { $app->get( @@ -51,37 +92,38 @@ foreach ($routes as $prefix => $moduleRoutes) { } ``` -Under the `routes` array: the module name (e.g. `page`) is the top-level key, the key (e.g. `about`) builds the page's path, and the value (e.g. `about`) is the template file. -This supports `/page/about` and `/page/who-we-are`. +Each item under `routes` array for each module will have its own entry. +The result is you have a `get` for the `about` page and another for the `who-we-are` page. ## Advanced configuration -The same versatility as before is retained: +You have the same versatility as before for route configuration. +Below we explore some scenarios that showcase the control you still have over the routing. -1. **Change the template**: replace `$template = $request->getAttribute(RouteResult::class)->getMatchedRouteName();` with a fixed template, like `$template = 'my-template';`. -2. **Change the URL for SEO** (e.g. `/page/about` to `/about`): remove the `$moduleName` parameter from `sprintf('/%s/%s', $moduleName, $routeUri)`, making it `sprintf('/%s', $routeUri)` - carefully, to avoid breaking other routes. -3. **Change the URL segment**: edit `'about' => 'about',` to `'about-us' => 'about',` in `local.php`, changing the url to `/page/about-us` while still using the `about` template. -4. **Add a dynamic parameter**: edit `'about' => 'about',` to `'about/{id}' => 'about',`, supporting urls like `/page/about/us`, `/page/about/company`, `/page/about/123`, and read the value in the handler with `$request->getAttribute('id')`. +1. **Change the template**: the template file is taken from the path, but you can still change that in the `handler` based on your requirements. Replace `$template = $request->getAttribute(RouteResult::class)->getMatchedRouteName();` with a template of your choosing, like `$template = 'my-template';` +2. **Change the URL for SEO**: say you want to change the url from `/page/about`, to `/about` for SEO purposes. All you have to do is remove the `$moduleName` parameter from `sprintf('/%s/%s', $moduleName, $routeUri),` making it `sprintf('/%s', $routeUri),`. Take care not to break other routes, though! +3. **Change the URL segment**: if you need to change the url parameter, you can do that in the `local.php` file. Edit `'about' => 'about',` into `'about-us' => 'about',`. This changes the original url to `/page/about-us`, but uses the same `about` template as before. +4. **Add a dynamic parameter**: do you need a dynamic parameter? Edit the route entry from `'about' => 'about',` to `'about/{id}' => 'about',`. This expands the matched url to support something like `/page/about/us`, `/page/about/company`, `/page/about/123` which will allow you to customize each url with different content in the handler. All you need is to use `$request->getAttribute('id')` to tell what page you are on. ## FAQ **Q: What is the goal of this dynamic routing update?** -A: To replace static, hard-coded route declarations with a more dynamic, centralized implementation that is easier to set up and review. +A: The goal is to replace the static way of creating routes with a more dynamic implementation, resulting in a cleaner approach that is easier to set up and review at a glance. **Q: How were routes configured in the old, static approach?** -A: In a per-module `RoutesDelegator.php` file, with hard-coded entries specifying a method, path, handler, and route name. +A: Each module had its own RoutesDelegator.php file in the src folder, with hard-coded route entries such as $app->get('/page', , 'page');. Each route needed a method (e.g. get), a path used to direct execution to a handler, a handler like GetPageViewHandler, and a unique route name. In a live application the route list can grow to many entries grouped across modules, meaning multiple RoutesDelegators had to be checked when a routing error occurred. **Q: How does the new approach centralize route configuration?** -A: Route data moves into a `routes` array in `config/autoload/local.php`, which `RoutesDelegator.php` reads to generate routes automatically. +A: The update centralizes the route configuration in the config/autoload/local.php file, moving the relevant items for each route into a routes array. RoutesDelegator.php then reads this configuration and generates the routes automatically, so it doesn't need to be touched most of the time. **Q: What do the entries under the routes array represent?** -A: The module name, the URL path segment (key), and the template file to render (value). +A: For a module like page, the module name is the top-level key, the array key (e.g. about) is used to build the page's path, and its value (e.g. about) is the template file. This supports urls like /page/about and /page/who-we-are. **Q: Can routes still be customized beyond the default setup?** -A: Yes - templates, URL structure, URL segments, and dynamic parameters can all still be adjusted. +A: Yes. You can change the template used by editing the handler's template attribute, change a URL for SEO purposes by removing the module name parameter from the generated path, change the URL segment by editing the key in local.php (e.g. turning 'about' into 'about-us' while keeping the same template), or add a dynamic parameter (e.g. 'about/{id}') and read it in the handler via $request->getAttribute('id'). **Q: Does this update support methods other than GET?** -A: No - it targets static pages only; other HTTP methods return a 405 status code. +A: No - this is the first in a series of articles for switching from controllers to PSR-15 compliant handlers, and it is aimed at static pages alone, so any other method like post, put or delete will return a 405 status code. ## Resources diff --git a/public/md-articles/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.md b/public/md-articles/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.md index 228620b4..c436c0d5 100644 --- a/public/md-articles/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.md +++ b/public/md-articles/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.md @@ -14,9 +14,16 @@ language: "en" The goal of this update is to implement [PSR-15](https://www.php-fig.org/psr/psr-15/) handlers into [Dotkernel Light](https://github.com/dotkernel/light), keeping the application up-to-date with recommended design guidelines, secure, and aligned with standards widely adopted by the PHP community. +The goal of this update is to implement [PSR-15](https://www.php-fig.org/psr/psr-15/) handlers into [Dotkernel Light](https://github.com/dotkernel/light). +There are several advantages to using handlers, which we will explore below. + +We strive to keep our applications up-to-date with the recommended design guidelines. +This ensures that we keep the applications secure, while also implementing standards widely adopted by the PHP community. + ## What makes handlers better than controllers? -Handlers split code into manageable chunks instead of one large controller file with several actions, following the first SOLID principle (Single-responsibility). +It's all fine and good if you have one large controller file with several actions, but handlers split the code into manageable chunks that make your life a lot easier in the long run. +This follows the first of the SOLID principles. SOLID stands for: - S - Single-responsibility Principle @@ -25,18 +32,34 @@ SOLID stands for: - I - Interface Segregation Principle - D - Dependency Inversion Principle -With single-responsibility, handlers separate each action into its own class, which makes them easier to maintain, refactor, and test. Expanding an application is easier too - rather than searching for a place to fit new code into a controller, you simply create a new handler. -Refactoring is simpler because there's less code to worry about breaking, and tests only need to inject or bind mocks for a single action per handler instead of covering multiple branches. +We are focusing on that first `S` in the SOLID acronym. +Instead if having multiple actions we would normally include in controllers, with `single-responsibility` the handlers separate each action into its own class. +This makes handlers easier to maintain, refactor and test. + +Expanding your application is also helped by handlers. +Rather than searching for a place to fit in that new code, simply create a handler to keep thing orderly. +Your future self or the programmer that takes over from you will thank you for it. + +Refactoring is always easier if you don't have to worry about edge cases that are unexpectedly not supported because of an error on your part. +Simpler code means refactoring steps are more obvious. + +Writing tests for actions that have multiple branches tends to take a lot of time. +Since handlers only deal with a single action, your tests only have to inject or bind mocks for that specific action. ## How to implement the Page handler -Replacing controllers with handlers in Dotkernel Light means the `dot-controller` package is no longer needed and can be removed, along with any existing Controllers. +When it comes to [Dotkernel Light](https://github.com/dotkernel/light), replacing controllers with handlers means we don't need the `dot-controller` package any more. +Go ahead and remove it, along with any `Controllers` you may have. + +Below we are going to detail how to set up the `GetPageViewHandler`. +If you already have Controllers in your application, you will have to repeat the steps below for each controller. +Based on your application, you may have to split your actions over multiple Handlers. -For Dotkernel Light, most of the old Controller's actions were combined under a single Handler, `GetPageViewHandler`, since they all performed the same task - displaying static content. -The only exception is `IndexHandler.php`, which was kept separate but is set up similarly. +For Dotkernel Light we were able to combine the functionality of most of the old Controller's `actions` under a single Handler, since the actions performed a single task - displaying static content. +The only exception is `IndexHandler.php` which we opted to leave separate, but its setup is similar to GetPageViewHandler.php. -Handlers are registered in each module's `ConfigProvider`, mapping factories under `getDependencies()`. -Add `GetPageViewHandler` under the `factories` key and remove any reference to `PageController`: +Handlers use the `ConfigProvider` in each module to map factories under `getDependencies()`. +You should already have delegators and aliases, but make sure to add `GetPageViewHandler` under the `factories` key and remove any reference to PageController. ```php public function getDependencies(): array @@ -49,8 +72,8 @@ public function getDependencies(): array } ``` -`GetPageViewHandlerFactory.php` adds the template renderer as a dependency, making it available in the handler. -`PageControllerFactory.php` is no longer needed and can be deleted: +`GetPageViewHandlerFactory.php` adds the template as a dependency, making it available in the Handler. +We don't need the PageControllerFactory.php file, so go ahead and delete it. ```php root_password) +- http://localhost/phpmyadmin/ - PhpMyAdmin (login with username **root** + the root password you configured in **config.yml** under mariadb -> *root_password*) The complete guide with additional features is available [here](https://docs.dotkernel.org/development/). ## FAQ **Q: How do I check if my machine is ready for WSL2?** -A: Open Windows Terminal and run `wsl -v`. If it returns version information (WSL, Kernel, WSLg, MSRDC, Direct3D, DXCore, Windows versions), WSL2 is installed. If you get an error instead, WSL is not completely installed and you need to follow the linked setup guide first. +A: Open Windows Terminal and run wsl -v. If it returns version information (WSL, Kernel, WSLg, MSRDC, Direct3D, DXCore, Windows versions), WSL2 is installed. If you get an error instead, WSL is not completely installed and you need to follow the linked setup guide first. **Q: How do I download and install AlmaLinux 9?** A: Open Microsoft Store, search for AlmaLinux, select AlmaLinux 9 from the results, and install it. Once installed, clicking Open launches it in Windows Terminal, where the installer prompts for your username, password, and password confirmation. **Q: What are the main setup steps inside AlmaLinux 9?** -A: Install epel-release, dnf-utils and the Remi repository RPM, upgrade installed packages, install Ansible, clone the dotkernel/development Git repository, navigate to its wsl directory, fill in the empty fields in config.yml, then run the installer script's Step 1 with `ansible-playbook -i hosts install.yml --ask-become-pass` (it will prompt for the password set during installation). +A: Install epel-release, dnf-utils and the Remi repository RPM, upgrade installed packages, install Ansible, clone the dotkernel/development Git repository, navigate to its wsl directory, fill in the empty fields in config.yml, then run the installer script's Step 1 with ansible-playbook -i hosts install.yml --ask-become-pass (it will prompt for the password set during installation). **Q: Why do I need to restart AlmaLinux 9 partway through setup?** -A: After Step 1 of the installer script, you press Control+D, open Windows Terminal, stop AlmaLinux 9 with `wsl -t AlmaLinux9`, and start it again with `wsl -d AlmaLinux9`. Then you navigate back to the Ansible recipes directory and run Step 2 of the installer script (again with `ansible-playbook -i hosts install.yml --ask-become-pass`), which prompts for the password again. Once Step 2 finishes, the AlmaLinux 9 development environment is ready. +A: After Step 1 of the installer script, you press Control+D, open Windows Terminal, stop AlmaLinux 9 with wsl -t AlmaLinux9, and start it again with wsl -d AlmaLinux9. Then you navigate back to the Ansible recipes directory and run Step 2 of the installer script (again with ansible-playbook -i hosts install.yml --ask-become-pass), which prompts for the password again. Once Step 2 finishes, the AlmaLinux 9 development environment is ready. **Q: How do I verify the installation worked?** A: Check three URLs in your browser: http://localhost/ for Apache's default home page, http://localhost/info.php for the PHP info page, and http://localhost/phpmyadmin/ for PhpMyAdmin, logging in with username root and the root password configured in config.yml under mariadb -> root_password. diff --git a/public/md-articles/php-development/aptana-php-installation-in-aptana-2-x.md b/public/md-articles/php-development/aptana-php-installation-in-aptana-2-x.md index db260c27..ffc076fe 100644 --- a/public/md-articles/php-development/aptana-php-installation-in-aptana-2-x.md +++ b/public/md-articles/php-development/aptana-php-installation-in-aptana-2-x.md @@ -28,20 +28,3 @@ In case you don't have yet a SVN plugin, go to 1. Aptana -> Help -> Install Aptana Features 2. Others -> Subclipse 3. Follow the instructions - -## FAQ - -**Q: Why isn't the Aptana PHP plugin available by default in Aptana 2.x?** -A: It was discontinued in Aptana 2.x, in favor of PDT. - -**Q: Why not just use PDT instead of Aptana PHP?** -A: PDT is not suitable for professional PHP development, since major features are missing. - -**Q: How do I reinstall the Aptana PHP plugin?** -A: Go to Aptana -> Help -> Install New Software, add the update site http://update.aptana.com/install/php, then select Aptana PHP and install it. - -**Q: How do I add SVN support in Aptana?** -A: Go to Aptana -> Help -> Install Aptana Features, choose Others -> Subclipse, and follow the instructions. - -**Q: Do I need an SVN plugin to use Aptana PHP?** -A: Not specifically for Aptana PHP, but if you don't already have an SVN plugin, Subclipse can be installed separately through Aptana's Install Aptana Features menu. diff --git a/public/md-articles/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.md b/public/md-articles/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.md index 81e89519..1b14f2fa 100644 --- a/public/md-articles/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.md +++ b/public/md-articles/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.md @@ -48,20 +48,3 @@ collation_server=utf8mb4_general_ci ```ini collation_server=utf8mb4_unicode_ci ``` - -## FAQ - -**Q: What is utf8mb4 and why was it introduced?** -A: utf8mb4 is a character set introduced in MySQL 5.5 to provide better support for Unicode. - -**Q: What error do I get if I keep VARCHAR(255) columns after switching to utf8mb4?** -A: You get `#1071 - Specified key was too long; max key length is 767 bytes`, so VARCHAR(150) is used instead. - -**Q: How do I change the connection charset for the application?** -A: Edit the config/application.ini file and set `database.params.charset = utf8mb4`. - -**Q: What changes are needed in my.cnf?** -A: Replace the string utf8_* with utf8mb4_*, for example `character_set_server=utf8mb4` and `collation_server=utf8mb4_general_ci` or `collation_server=utf8mb4_unicode_ci`. - -**Q: Was Dotkernel's own sample SQL updated for utf8mb4?** -A: Yes, the sample dk.sql file, part of the Dotkernel framework, was updated for utf8mb4 in revision 793. diff --git a/public/md-articles/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.md b/public/md-articles/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.md index f43271ad..33e164ce 100644 --- a/public/md-articles/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.md +++ b/public/md-articles/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.md @@ -102,6 +102,8 @@ php bin/console It should print out all the doctrine CLI commands available, including our fixtures commands. +![](/uploads/article/019f8a80-cc6c-71e3-a52c-9a9b1a204472/Screenshot-2022-08-30-at-19.14.14-1024x334.png) + ## Usage ### List All Available Fixtures, by Order of Execution @@ -110,6 +112,8 @@ It should print out all the doctrine CLI commands available, including our fixtu php bin/doctrine fixtures:list ``` +![](/uploads/article/019f8a80-cc6c-71e3-a52c-9a9b1a204472/Screenshot-2022-08-30-at-19.22.45-1024x147.png) + By using this command you can check the execution order of your fixtures before executing them. ### Executing Fixtures @@ -271,27 +275,10 @@ class UserLoader implements FixtureInterface, DependentFixtureInterface Notice how `UserLoader.php` implements 2 interfaces, `FixtureInterface` and `DependentFixtureInterface`. The method `getDependencies()` returns an array containing the dependencies (fixtures) that need to be executed prior to the current one. After running all the fixtures using `php bin/doctrine fixtures:execute` the output should look like this: + +![](/uploads/article/019f8a80-cc6c-71e3-a52c-9a9b1a204472/Screenshot-2022-08-30-at-20.39.19.png) + `RoleLoader` was executed before `UserLoader` because `UserLoader` had `RoleLoader` as a dependency. To wrap things up, we implemented a database seeder and saw a practical example of how to use it. More details about Fixtures in this blogpost: [https://matthiasnoback.nl/2018/07/about-fixtures/](https://matthiasnoback.nl/2018/07/about-fixtures/). - -## FAQ - -**Q: Why did Dotkernel move from Phinx to doctrine/data-fixtures for seeding?** -A: Previous versions of Dotkernel 3 used cakephp/phinx for seeding, but the team wanted more flexibility, so they evaluated doctrine/data-fixtures since Doctrine is already used as the database abstraction layer. - -**Q: What's the catch with doctrine/data-fixtures?** -A: It provides a concrete implementation of data fixtures, but without a CLI interface, so Dotkernel built the dotkernel/dot-data-fixtures package to add one. - -**Q: Does dotkernel/dot-data-fixtures depend on other Dotkernel packages?** -A: No, its only dependency is Doctrine. - -**Q: How do you install dotkernel/dot-data-fixtures?** -A: Run `composer require dotkernel/dot-data-fixtures`, register its ConfigProvider in `config/config.php`, add a `fixtures` key pointing to your fixtures folder in your doctrine config, and register the ExecuteFixturesCommand and ListFixturesCommand in a `bin/doctrine` CLI file. - -**Q: How do you list and execute fixtures?** -A: Use `php bin/doctrine fixtures:list` to see the execution order, `php bin/doctrine fixtures:execute` to run all fixtures, or `php bin/doctrine fixtures:execute --class={FixtureClassName}` to run a specific one. - -**Q: How can fixtures be ordered?** -A: Either by implementing `OrderedFixtureInterface` to specify an explicit order, or by implementing `DependentFixtureInterface` and its `getDependencies()` method to declare which fixtures must run first. diff --git a/public/md-articles/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.md b/public/md-articles/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.md index 019586a1..a293d151 100644 --- a/public/md-articles/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.md +++ b/public/md-articles/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.md @@ -35,7 +35,7 @@ Zend_Db is the primary class used for accessing the database, but there is more: A short tip: you can use type casting to avoid SQL Injection in a WHERE clause where possible. ```php -$sql = 'SELECT * FROM table WHERE id = ' . (int)$_POST; +$sql = 'SELECT * FROM table WHERE id = ' . (int)$_POST['id']; ``` ## FAQ diff --git a/public/md-articles/php-development/using-php-7-express-in-zend-studio-13.md b/public/md-articles/php-development/using-php-7-express-in-zend-studio-13.md index 922c1d0c..45a4a6f2 100644 --- a/public/md-articles/php-development/using-php-7-express-in-zend-studio-13.md +++ b/public/md-articles/php-development/using-php-7-express-in-zend-studio-13.md @@ -27,6 +27,8 @@ If you want to create a new project for testing instead of modifying the origina Choosing the PHP 7 option will hide the PHP 7 Express feature, because Zend Studio assumes a PHP 7 project doesn't need compatibility checks, but if the project is actually using a different version of PHP, compatibility issues might appear. Long story short: use the exact PHP version your project is using at project creation. +[![The New Local PHP Project dialog in Zend Studio 13](/uploads/article/019f8a80-cc69-7286-91c9-b075c0b9be59/Screenshot-2015-12-22-16.21.56.png)](/uploads/2015/12/Screenshot-2015-12-22-16.21.56.png) The "New Local PHP Project" dialog in Zend Studio 13 and the PHP version selection + ## The 'PHP Interpreter' Selection If the PHP 7 Express feature won't show up, the issue might be the PHP Interpreter selected. @@ -37,6 +39,8 @@ To check and modify the PHP Interpreter: - The PHP -> Interpreter option should show up. - If you only need a specific interpreter for a specific project, check "Enable Project specific settings" - this way you won't affect the other projects in the current workspace. +[![Zend Studio PHP Interpreter Dialog](/uploads/article/019f8a80-cc69-7286-91c9-b075c0b9be59/zftest.png)](/uploads/2015/12/zftest.png) Zend Studio PHP Interpreter Dialog + The PHP 7 Express feature should now show up. ## Project Preparation @@ -60,6 +64,8 @@ Using PHP 7 Express is very easy: After the analysis is done, the PHP 7 Express view (or tab) appears in the bottom pane in Zend Studio, depending on the perspective. +[![Zend Framework 1 compatibility issues with PHP7](/uploads/article/019f8a80-cc69-7286-91c9-b075c0b9be59/php7errors.png)](/uploads/2015/12/php7errors.png) Zend Framework 1 compatibility issues with PHP7 + ## FAQ **Q: What is PHP 7 Express in Zend Studio?** diff --git a/public/md-articles/php-development/version-control-ignore-patterns-in-zend-studio.md b/public/md-articles/php-development/version-control-ignore-patterns-in-zend-studio.md index a19ea63a..5f2f8335 100644 --- a/public/md-articles/php-development/version-control-ignore-patterns-in-zend-studio.md +++ b/public/md-articles/php-development/version-control-ignore-patterns-in-zend-studio.md @@ -26,6 +26,8 @@ In order to globally manage the [Ignored Resources](https://www.dotkernel.com/be This is especially useful when you have both Git and SVN projects in your workspace, and also when you tend to be less careful about your code and workspace. Also, on each project, you can use either the global ignored patterns or specific ones. +[![ignore-patterns](/uploads/article/019f8a80-cc67-73be-b359-c0cc3c3adbed/ignore-patterns.png)](/uploads/2013/04/ignore-patterns.png) + ## FAQ **Q: How do you set global ignored resource patterns in Zend Studio?** diff --git a/public/md-articles/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.md b/public/md-articles/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.md index a657c180..00907b85 100644 --- a/public/md-articles/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.md +++ b/public/md-articles/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.md @@ -91,7 +91,7 @@ A: Typical errors include "Zend InputFilter requires intl PHP extension" and "Th A: Intl (Internationalization extension) is a wrapper for the ICU library that lets PHP programmers perform locale-aware operations, including formatting, transliteration, encoding conversion, and calendar operations. It can be installed bundled since PHP 5.3.0, or as a separate PECL extension since PHP 5.2.0. **Q: How do you find your PHP extensions directory?** -A: You can find the extension_dir either by calling phpinfo() or by running the command php -r "echo ini_get('extension_dir');" - both read the value straight from the PHP runtime configuration. +A: You can find the extension_dir either by calling phpinfo() or by running the command php -r "echo ini_get('extension_dir');" — both read the value straight from the PHP runtime configuration. **Q: How do you enable the Intl extension on a Linux server if the file already exists?** A: Confirm php_intl.so exists in your extensions directory, open the php.ini config file (usually /etc/php.ini), make sure the line "extension=php_intl.so" exists and isn't commented out, restart the web server (e.g. sudo service httpd restart), and verify with phpinfo(). diff --git a/public/md-articles/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.md b/public/md-articles/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.md index f838f3e7..3e31b3df 100644 --- a/public/md-articles/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.md +++ b/public/md-articles/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.md @@ -17,6 +17,9 @@ This article gives separate setup steps for a freshly cloned project versus an e 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). + +![](/uploads/article/019f8a80-cc90-7160-853d-18cb7411b9ff/image.png) + Whether you just cloned or you are already working on a project, follow the below guide on how to prepare your environment. ## When Cloning a Project @@ -39,9 +42,21 @@ There, if Coding standard is set to Custom and the field next to it contains the 5. Go to Step 1. After you have the above configurations, you should start seeing information in the top-right corner of the editor. -Once PHPStorm has finished analyzing the opened file, you should see either a green tick (meaning no errors) or a count of all the errors, warnings, and typos. + +![](/uploads/article/019f8a80-cc90-7160-853d-18cb7411b9ff/image-1-1024x83.png) + +Once PHPStorm has finished analyzing the opened file, you should see either a green tick (meaning no errors) + +![](/uploads/article/019f8a80-cc90-7160-853d-18cb7411b9ff/image-2-1024x66.png) + +or a count of all the errors, warnings, and typos. + +![](/uploads/article/019f8a80-cc90-7160-853d-18cb7411b9ff/image-3.png) + Clicking on them will open a section where you get detailed information on each item, their location, and recommendations on how to fix them. +![](/uploads/article/019f8a80-cc90-7160-853d-18cb7411b9ff/image-5.png) + ## FAQ **Q: What is phpcs and why do you need to configure it in PHPStorm?** diff --git a/public/md-articles/zend-framework/zend-framework-1-12-4-released-with-security-fixes.md b/public/md-articles/zend-framework/zend-framework-1-12-4-released-with-security-fixes.md index 65a6d1cb..139749c6 100644 --- a/public/md-articles/zend-framework/zend-framework-1-12-4-released-with-security-fixes.md +++ b/public/md-articles/zend-framework/zend-framework-1-12-4-released-with-security-fixes.md @@ -38,4 +38,4 @@ A: Matthew Weier O'Phinney announced the release of ZF 1.12.4, along with 2.1.6 A: Yes, the ZF PEAR channel was updated to the latest 1.12.4 release, upgradable via "pear upgrade zend/zend". **Q: Was there a follow-up issue after the 1.12.4 release?** -A: Yes - per the March 7, 2014 edit, Zend Framework 1.12.5 was released in order to fix a backward compatibility issue introduced in the 1.12.4 release. +A: Yes — per the March 7, 2014 edit, Zend Framework 1.12.5 was released in order to fix a backward compatibility issue introduced in the 1.12.4 release. diff --git a/public/md-articles/zend-framework/zend-framework-1-7-0-released.md b/public/md-articles/zend-framework/zend-framework-1-7-0-released.md index d350028a..029f9b39 100644 --- a/public/md-articles/zend-framework/zend-framework-1-7-0-released.md +++ b/public/md-articles/zend-framework/zend-framework-1-7-0-released.md @@ -22,7 +22,7 @@ You can [download the latest ZF](http://framework.zend.com/download/latest) rele ## FAQ **Q: What is described as the biggest news in the Zend Framework 1.7.0 release?** -A: AMF support - bringing Adobe's Action Message Format protocol to PHP 5 applications. +A: AMF support — bringing Adobe's Action Message Format protocol to PHP 5 applications. **Q: How can readers get this release?** A: Via the "Download latest ZF" link provided in the post, pointing to the official Zend Framework download page. diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index 7b61751a..965448c3 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -21,6 +21,8 @@ use Light\App\Factory\GitHubClientFactory; use Light\App\Factory\LlmsFullGeneratorFactory; use Light\App\Factory\LlmsGeneratorFactory; +use Light\App\Factory\MarkdownExtensionFactory; +use Light\App\Factory\MarkdownRuntimeLoaderFactory; use Light\App\Factory\PackageGeneratorFactory; use Light\App\Factory\SitemapGeneratorFactory; use Light\App\Handler\GetFeedViewHandler; @@ -39,6 +41,8 @@ use Mezzio\Application; use Roave\PsrContainerDoctrine\EntityManagerFactory; use Symfony\Component\Cache\Adapter\AdapterInterface; +use Twig\Extra\Markdown\MarkdownExtension; +use Twig\RuntimeLoader\RuntimeLoaderInterface; use function getcwd; @@ -144,6 +148,8 @@ public function getDependencies(): array PackageGenerator::class => PackageGeneratorFactory::class, LlmsFullGenerator::class => LlmsFullGeneratorFactory::class, LlmsGenerator::class => LlmsGeneratorFactory::class, + MarkdownExtension::class => MarkdownExtensionFactory::class, + RuntimeLoaderInterface::class => MarkdownRuntimeLoaderFactory::class, ], 'aliases' => [ EntityManager::class => 'doctrine.entity_manager.orm_default', diff --git a/src/App/src/Factory/MarkdownExtensionFactory.php b/src/App/src/Factory/MarkdownExtensionFactory.php new file mode 100644 index 00000000..b59cd738 --- /dev/null +++ b/src/App/src/Factory/MarkdownExtensionFactory.php @@ -0,0 +1,16 @@ + static fn (): MarkdownRuntime => new MarkdownRuntime( + new LeagueMarkdown(new GithubFlavoredMarkdownConverter()) + ), + ]); + } +} diff --git a/src/App/src/Fixture/PostLoader.php b/src/App/src/Fixture/PostLoader.php index 51f484d5..96474a44 100644 --- a/src/App/src/Fixture/PostLoader.php +++ b/src/App/src/Fixture/PostLoader.php @@ -81,6 +81,7 @@ public function load(ObjectManager $manager): void $excerpt = $articleData['excerpt'] ?? ''; $tlDr = $articleData['tl_dr'] ?? ''; $isObsolete = (bool) ($articleData['isObsolete'] ?? false); + $isTwig = (bool) ($articleData['isTwig'] ?? false); $openGraphImg = $articleData['opengraph_img'] ?? null; $article = $repository->findOneBy(['slug' => $slug]); @@ -96,6 +97,7 @@ public function load(ObjectManager $manager): void $article->setExcerpt($excerpt); $article->setTldr($tlDr); $article->setObsolete($isObsolete); + $article->setTwig($isTwig); $article->setOpenGraphImage($openGraphImg); $manager->persist($article); @@ -135,6 +137,10 @@ public function load(ObjectManager $manager): void $article->setObsolete($isObsolete); $changed = true; } + if ($article->isTwig() !== $isTwig) { + $article->setTwig($isTwig); + $changed = true; + } if ($article->getOpenGraphImage() !== $openGraphImg) { $article->setOpenGraphImage($openGraphImg); $changed = true; diff --git a/src/App/src/Fixture/articles_cleaned.json b/src/App/src/Fixture/articles_cleaned.json index d03a029a..b4d4e336 100644 --- a/src/App/src/Fixture/articles_cleaned.json +++ b/src/App/src/Fixture/articles_cleaned.json @@ -15,6 +15,7 @@ "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.", "isObsolete": false, + "isTwig": false, "opengraph_img": null, "tags": [] }, @@ -27,6 +28,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -41,6 +43,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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().", @@ -55,6 +58,7 @@ "github": "" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -69,6 +73,7 @@ "github": "" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -83,6 +88,7 @@ "github": "" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -97,6 +103,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -111,6 +118,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -125,6 +133,7 @@ "github": "" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -139,6 +148,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -153,6 +163,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -167,6 +178,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "excerpt": "In /var/www/vhosts/exampledomain.com/conf/vhost.", "tl_dr": "", @@ -194,6 +206,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -208,6 +221,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -222,6 +236,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -236,6 +251,7 @@ "github": "" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -250,6 +266,7 @@ "github": "" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -264,6 +281,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -278,6 +296,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -292,6 +311,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -306,6 +326,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -320,6 +341,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -334,6 +356,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "excerpt": "According to Matthew Weier O'Phinney, Zend Framework Project Leader, in the next release of ZF, 1.12.", "tl_dr": "", @@ -348,6 +371,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -362,6 +386,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -376,6 +401,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -390,6 +416,7 @@ "github": "" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -404,6 +431,7 @@ "github": "" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -418,6 +446,7 @@ "github": "arhimede" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -432,6 +461,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -446,6 +476,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -460,6 +491,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -474,6 +506,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -488,6 +521,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -502,6 +536,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -516,6 +551,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -530,6 +566,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -544,6 +581,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -558,6 +596,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -572,6 +611,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -586,6 +626,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -600,6 +641,7 @@ "github": "gabidj" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -614,6 +656,7 @@ "github": "gabidj" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -628,6 +671,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -642,6 +686,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -656,6 +701,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -670,6 +716,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -684,6 +731,7 @@ "github": "gabidj" }, "isObsolete": true, + "isTwig": false, "opengraph_img": null, "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.", @@ -698,6 +746,7 @@ "github": null }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -712,6 +761,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -726,6 +776,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -765,6 +816,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -816,6 +868,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -830,6 +883,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -844,6 +898,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -858,6 +913,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -872,6 +928,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -886,6 +943,7 @@ "github": "kakapiciu" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -900,6 +958,7 @@ "github": "marioradu05" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/doctrine-cache-using-symfony-cache-small.png", "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.", @@ -914,6 +973,7 @@ "github": "pinclau" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card.png", "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.", @@ -928,6 +988,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.png", "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.", @@ -942,6 +1003,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-Light-choice.png", "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.", @@ -956,6 +1018,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-enum.png", "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.", @@ -970,6 +1033,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-symfony-mailer.png", "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.", @@ -984,6 +1048,7 @@ "github": "OStefan2001" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-symfony-mailer.png", "excerpt": "This is a test article used for fixture and testing purposes.", "tl_dr": "This is a test article added to the dotkernel category for testing purposes. This article is now updated.", @@ -998,6 +1063,7 @@ "github": "OStefan2001" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-build-dotkernel.png", "excerpt": "How this blog itself came together: starting from the Dotkernel Light starter project, then following the Tutorial 101 to add Doctrine ORM and load real data into the database.", "tl_dr": "This project started from the Dotkernel Light starter (Mezzio, Twig, FastRoute, PSR-7 via Laminas Diactoros) as a bare-bones website skeleton.\nFollowing the official Tutorial 101, Doctrine ORM was added on top: entities for posts, categories, authors and tags, migrations to create the schema, and fixtures to load real content into the database.\nThe result is this: a Dotkernel Light project turned into a fully data-driven site.", @@ -1036,6 +1102,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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().", @@ -1055,6 +1122,7 @@ "github": null }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1090,6 +1158,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "excerpt": "Problem: email should allow +/- characters in user, - in domain. dash (-) should be allowed anywhere in an email address or domain.", "tl_dr": "", @@ -1128,6 +1197,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1147,6 +1217,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1161,6 +1232,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1175,6 +1247,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1189,6 +1262,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1216,6 +1290,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1230,6 +1305,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1244,6 +1320,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1265,6 +1342,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1279,6 +1357,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1302,6 +1381,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1325,6 +1405,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1339,6 +1420,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1353,6 +1435,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1367,6 +1450,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1381,6 +1465,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "excerpt": "PHP 5.3.", "tl_dr": "", @@ -1408,6 +1493,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1439,6 +1525,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1453,6 +1540,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1467,6 +1555,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1481,6 +1570,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1495,6 +1585,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1534,6 +1625,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1581,6 +1673,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1595,6 +1688,7 @@ "github": "marioradu05" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1609,6 +1703,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1623,6 +1718,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/wsl2-php-dotkernel.png", "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.", @@ -1637,6 +1733,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-static-analysis.png", "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.", @@ -1658,6 +1755,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1701,6 +1799,7 @@ "github": null }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1715,6 +1814,7 @@ "github": null }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1729,6 +1829,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1743,6 +1844,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1774,6 +1876,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1788,6 +1891,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1802,6 +1906,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1816,6 +1921,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1830,6 +1936,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1844,6 +1951,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1858,6 +1966,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1881,6 +1990,7 @@ "github": "marioradu05" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1895,6 +2005,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-almalinux10.png", "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.", @@ -1916,6 +2027,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -1959,6 +2071,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2017,6 +2130,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2048,6 +2162,7 @@ "github": "gabidj" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2062,6 +2177,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2076,6 +2192,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2090,6 +2207,7 @@ "github": "kakapiciu" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2104,6 +2222,7 @@ "github": "kakapiciu" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2127,6 +2246,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2141,6 +2261,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2155,6 +2276,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/PHP-8.3-support-in-Dotkernel-API.png", "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()).", @@ -2176,6 +2298,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-dynamic-routing.png", "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.", @@ -2190,6 +2313,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-controllers-to-handlers.png", "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.", @@ -2204,6 +2328,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-light-improvements.png", "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.", @@ -2225,6 +2350,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2248,6 +2374,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2271,6 +2398,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2294,6 +2422,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2317,6 +2446,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2331,6 +2461,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2354,6 +2485,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "excerpt": "To always redirect users to the www site (for example: http://dotboost.com to http://www.", "tl_dr": "", @@ -2368,6 +2500,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2382,6 +2515,7 @@ "github": "" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -2405,6 +2539,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -2419,6 +2554,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2433,6 +2569,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-retired-whats-next-1.png", "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.", @@ -2447,6 +2584,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-basic-security.png", "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.", @@ -2468,6 +2606,7 @@ "github": "n3vrax" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -2482,6 +2621,7 @@ "github": "n3vrax" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -2503,6 +2643,7 @@ "github": "arhimede" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -2517,6 +2658,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2531,6 +2673,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2545,6 +2688,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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": "", @@ -2559,6 +2703,7 @@ "github": "kakapiciu" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "excerpt": "API Endpoint to Collect Client Errors Let's say you have a (Client) Frontend (e.g.", "tl_dr": "", @@ -2573,6 +2718,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2587,6 +2733,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/OpenAPI-implementation-in-Dotkernel-API.png", "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.", @@ -2601,6 +2748,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/error-reporting-endoint-in-dotkernel-api.png", "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.", @@ -2615,6 +2763,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-content-negotiation.png", "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.", @@ -2629,6 +2778,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-postman-to-bruno.png", "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.", @@ -2650,6 +2800,7 @@ "github": "alexmerlin" }, "isObsolete": false, + "isTwig": false, "opengraph_img": null, "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.", @@ -2671,6 +2822,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/MIT-VERSUS-LGPL-IN-PRACTICE-1.png", "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.", @@ -2717,6 +2869,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-understanding-middleware.png", "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.", @@ -2731,6 +2884,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-config-provider.png", "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).", @@ -2745,6 +2899,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": true, "opengraph_img": "/opengraph/article/twitter-card-request-lifecycle.png", "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.", @@ -2766,6 +2921,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-naming-patten.png", "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": "", @@ -2787,6 +2943,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-headless-platform1.png", "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.", @@ -2801,6 +2958,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-core-submodule.png", "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.", @@ -2815,6 +2973,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-api-v6.png", "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.", @@ -2829,6 +2988,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-complementary-admin.png", "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.", @@ -2843,6 +3003,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-queue.png", "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.", @@ -2857,6 +3018,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-dotmaker-2.png", "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.", @@ -2871,6 +3033,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-api-evolution-versioning.png", "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.", @@ -2885,6 +3048,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-headless-v7.png", "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, 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 v11.4+ is required for UUID support.", @@ -2899,6 +3063,7 @@ "github": "bidi47" }, "isObsolete": false, + "isTwig": false, "opengraph_img": "/opengraph/article/twitter-card-totp.png", "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/Migration/Version20260907163051.php b/src/App/src/Migration/Version20260907163051.php new file mode 100644 index 00000000..ccc051b2 --- /dev/null +++ b/src/App/src/Migration/Version20260907163051.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE post ADD isTwig TINYINT DEFAULT 0 NOT 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 isTwig'); + } +} diff --git a/src/App/src/Service/ArticleBodyCleaner.php b/src/App/src/Service/ArticleBodyCleaner.php new file mode 100644 index 00000000..2eccbccf --- /dev/null +++ b/src/App/src/Service/ArticleBodyCleaner.php @@ -0,0 +1,24 @@ +, body: string} + */ + public static function extract(string $body): array + { + if (preg_match('/^## FAQ\s*$(.*)\z/ms', $body, $section, offset: 0) !== 1) { + return ['faq' => [], 'body' => $body]; + } + + $faq = []; + preg_match_all( + '/\*\*Q:\s*(.+?)\*\*\s*\n\s*A:\s*(.+?)(?=\n\s*\n|\z)/s', + $section[1], + $matches, + PREG_SET_ORDER + ); + foreach ($matches as $match) { + $faq[] = [ + 'question' => trim($match[1]), + 'answer' => trim(preg_replace('/\s+/', ' ', $match[2]) ?? ''), + ]; + } + + $body = trim((string) preg_replace('/^## FAQ\s*$.*\z/ms', '', $body)); + + return ['faq' => $faq, 'body' => $body]; + } +} diff --git a/src/App/src/Service/FrontMatter.php b/src/App/src/Service/FrontMatter.php new file mode 100644 index 00000000..686aa405 --- /dev/null +++ b/src/App/src/Service/FrontMatter.php @@ -0,0 +1,31 @@ +, body: string} + */ + public static function parse(string $raw): array + { + if (preg_match('/\A---\r?\n(.*?)\r?\n---\r?\n?(.*)\z/s', $raw, $matches) !== 1) { + return ['meta' => [], 'body' => trim($raw)]; + } + + /** @var array $meta */ + $meta = Yaml::parse($matches[1]) ?? []; + + return ['meta' => $meta, 'body' => trim($matches[2])]; + } +} diff --git a/src/App/templates/layout/blog-post.html.twig b/src/App/templates/layout/blog-post.html.twig index 825a748b..7013570b 100644 --- a/src/App/templates/layout/blog-post.html.twig +++ b/src/App/templates/layout/blog-post.html.twig @@ -1,6 +1,58 @@ {% extends '@layout/default.html.twig' %} -{% block json_ld %} {{ include('@jsonld/' ~ article.category.slug ~ '/' ~ article.slug ~ '.jsonld.twig') }} {% endblock %} +{% block json_ld %} + +{% endblock %} {% block title %}{{ article.title }}{% endblock %} diff --git a/src/Blog/src/Entity/Post.php b/src/Blog/src/Entity/Post.php index 3912e195..26287118 100644 --- a/src/Blog/src/Entity/Post.php +++ b/src/Blog/src/Entity/Post.php @@ -51,6 +51,9 @@ enumType: PostStatusEnum::class, #[ORM\Column(name: 'isObsolete', type: 'boolean')] private bool $isObsolete = false; + #[ORM\Column(name: 'isTwig', type: 'boolean', options: ['default' => false])] + private bool $isTwig = false; + #[ORM\Column(name: 'opengraph_img', type: 'string', length: 255, nullable: true)] private ?string $openGraphImage = null; @@ -155,6 +158,16 @@ public function setObsolete(bool $isObsolete): void $this->isObsolete = $isObsolete; } + public function isTwig(): bool + { + return $this->isTwig; + } + + public function setTwig(bool $isTwig): void + { + $this->isTwig = $isTwig; + } + public function getOpenGraphImage(): ?string { return $this->openGraphImage; @@ -183,6 +196,7 @@ public function getPostTags(): Collection * tlDr: string|null, * postDate: string, * isObsolete: bool, + * isTwig: bool, * openGraphImage: string|null, * category: array{id: non-empty-string, name: string, slug: string}, * author: array{id: non-empty-string, name: string, slug: string, github: string|null} @@ -198,6 +212,7 @@ public function getArrayCopy(): array 'excerpt' => $this->excerpt, 'tlDr' => $this->tlDr, 'isObsolete' => $this->isObsolete, + 'isTwig' => $this->isTwig, 'openGraphImage' => $this->openGraphImage, 'postDate' => $this->postDate->format('Y-m-d H:i:s'), 'category' => $this->category->getArrayCopy(), diff --git a/src/Blog/src/Handler/GetPostResourceHandler.php b/src/Blog/src/Handler/GetPostResourceHandler.php index 2b9cf898..b9a0915b 100644 --- a/src/Blog/src/Handler/GetPostResourceHandler.php +++ b/src/Blog/src/Handler/GetPostResourceHandler.php @@ -7,6 +7,9 @@ use Fig\Http\Message\StatusCodeInterface; use Laminas\Diactoros\Response\HtmlResponse; use Laminas\Diactoros\Response\TextResponse; +use Light\App\Service\ArticleBodyCleaner; +use Light\App\Service\FaqExtractor; +use Light\App\Service\FrontMatter; use Light\Blog\Enum\PostStatusEnum; use Light\Blog\Repository\CategoryRepository; use Light\Blog\Repository\PostRepository; @@ -59,15 +62,45 @@ public function handle(ServerRequestInterface $request): ResponseInterface } $meta = $article; $adjacent = $this->articleRepository->getAdjacentPosts($article); + + if ($article->isTwig()) { + try { + $html = $this->template->render( + "page::blog-resource/{$categorySlug}/{$slug}", + [ + 'article' => $article, + 'meta' => $meta, + 'categories' => $categories, + 'previousPost' => $adjacent['previous'], + 'nextPost' => $adjacent['next'], + 'faq' => [], + ] + ); + return new HtmlResponse($html); + } catch (Throwable $e) { + return $this->blogService->notFound($categories); + } + } + + $markdownFile = $this->blogService->resolveMarkdownFilePath($categorySlug, $slug); + if ($markdownFile === null) { + return $this->blogService->notFound($categories); + } + + $parsed = FrontMatter::parse((string) file_get_contents($markdownFile)); + $faq = FaqExtractor::extract(ArticleBodyCleaner::clean($parsed['body'])); + try { $html = $this->template->render( - 'page::blog-resource/' . $article->getCategory()->getSlug() . '/' . $slug, + 'page::markdown-article', [ 'article' => $article, 'meta' => $meta, 'categories' => $categories, 'previousPost' => $adjacent['previous'], 'nextPost' => $adjacent['next'], + 'content' => $faq['body'], + 'faq' => $faq['faq'], ] ); return new HtmlResponse($html); diff --git a/src/Blog/templates/page/JSON-LD/android/listen-for-android-install-referrer.jsonld.twig b/src/Blog/templates/page/JSON-LD/android/listen-for-android-install-referrer.jsonld.twig deleted file mode 100644 index e85bcfe5..00000000 --- a/src/Blog/templates/page/JSON-LD/android/listen-for-android-install-referrer.jsonld.twig +++ /dev/null @@ -1,78 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.jsonld.twig b/src/Blog/templates/page/JSON-LD/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.jsonld.twig deleted file mode 100644 index 16a3a40c..00000000 --- a/src/Blog/templates/page/JSON-LD/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.jsonld.twig +++ /dev/null @@ -1,64 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/architecture/configprovider-bootstrap-modern-php-applications.jsonld.twig b/src/Blog/templates/page/JSON-LD/architecture/configprovider-bootstrap-modern-php-applications.jsonld.twig deleted file mode 100644 index fe7ffe08..00000000 --- a/src/Blog/templates/page/JSON-LD/architecture/configprovider-bootstrap-modern-php-applications.jsonld.twig +++ /dev/null @@ -1,96 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/architecture/request-lifecycle-for-a-mezzio-based-application.jsonld.twig b/src/Blog/templates/page/JSON-LD/architecture/request-lifecycle-for-a-mezzio-based-application.jsonld.twig deleted file mode 100644 index 1ca462e0..00000000 --- a/src/Blog/templates/page/JSON-LD/architecture/request-lifecycle-for-a-mezzio-based-application.jsonld.twig +++ /dev/null @@ -1,96 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/architecture/understanding-middleware.jsonld.twig b/src/Blog/templates/page/JSON-LD/architecture/understanding-middleware.jsonld.twig deleted file mode 100644 index 3ef25500..00000000 --- a/src/Blog/templates/page/JSON-LD/architecture/understanding-middleware.jsonld.twig +++ /dev/null @@ -1,96 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/aptana-set-svn-keywords.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/aptana-set-svn-keywords.jsonld.twig deleted file mode 100644 index de9cd76a..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/aptana-set-svn-keywords.jsonld.twig +++ /dev/null @@ -1,72 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/basic-security-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/basic-security-in-dotkernel-headless-platform.jsonld.twig deleted file mode 100644 index 3af5dc1a..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/basic-security-in-dotkernel-headless-platform.jsonld.twig +++ /dev/null @@ -1,96 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/golden-rules-of-professional-php-coding.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/golden-rules-of-professional-php-coding.jsonld.twig deleted file mode 100644 index 5b28d2c2..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/golden-rules-of-professional-php-coding.jsonld.twig +++ /dev/null @@ -1,88 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/htaccess-301-redirect-non-www-to-www.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/htaccess-301-redirect-non-www-to-www.jsonld.twig deleted file mode 100644 index 76c99b97..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/htaccess-301-redirect-non-www-to-www.jsonld.twig +++ /dev/null @@ -1,64 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/insert-update-delete-statements-with-zend-db.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/insert-update-delete-statements-with-zend-db.jsonld.twig deleted file mode 100644 index 89f50730..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/insert-update-delete-statements-with-zend-db.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/sql-queries-using-zend-db-select.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/sql-queries-using-zend-db-select.jsonld.twig deleted file mode 100644 index a0d6da75..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/sql-queries-using-zend-db-select.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/subqueries-with-zend-db.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/subqueries-with-zend-db.jsonld.twig deleted file mode 100644 index 312c117d..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/subqueries-with-zend-db.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/svn-export-in-a-virtual-host.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/svn-export-in-a-virtual-host.jsonld.twig deleted file mode 100644 index 1f66d24b..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/svn-export-in-a-virtual-host.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/svn-keywords-setup-in-php-ide-zend-studio.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/svn-keywords-setup-in-php-ide-zend-studio.jsonld.twig deleted file mode 100644 index b306c2b5..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/svn-keywords-setup-in-php-ide-zend-studio.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/using-like-wildcards-with-zend-db.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/using-like-wildcards-with-zend-db.jsonld.twig deleted file mode 100644 index 845814ac..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/using-like-wildcards-with-zend-db.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/what-are-returning-the-fetch-functions-from-zend-db.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/what-are-returning-the-fetch-functions-from-zend-db.jsonld.twig deleted file mode 100644 index 1ad7156a..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/what-are-returning-the-fetch-functions-from-zend-db.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.jsonld.twig deleted file mode 100644 index 11f47479..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.jsonld.twig b/src/Blog/templates/page/JSON-LD/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.jsonld.twig deleted file mode 100644 index ae2fc695..00000000 --- a/src/Blog/templates/page/JSON-LD/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig deleted file mode 100644 index ab71fce6..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-client-migration-from-postman-to-bruno.jsonld.twig +++ /dev/null @@ -1,96 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig deleted file mode 100644 index c3d46c1c..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/api-endpoint-to-collect-client-errors.jsonld.twig +++ /dev/null @@ -1,72 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig deleted file mode 100644 index 109c05d1..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/content-negotiation-in-dotkernel-rest-api.jsonld.twig +++ /dev/null @@ -1,96 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig deleted file mode 100644 index e6fd5ece..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-1-0-0-released.jsonld.twig +++ /dev/null @@ -1,72 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig deleted file mode 100644 index 707213b5..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-client-side-authorization.jsonld.twig +++ /dev/null @@ -1,72 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig deleted file mode 100644 index 8826651b..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-server-side-authorization.jsonld.twig +++ /dev/null @@ -1,88 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-versus-laminas-api-tools.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-versus-laminas-api-tools.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/dotkernel-api-versus-laminas-api-tools.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig deleted file mode 100644 index a1d15cab..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig deleted file mode 100644 index 9fc489f9..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel-api/openapi-implementation-in-dotkernel-api.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig deleted file mode 100644 index 504917b4..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-cors-implementation-to-zend-expressive.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig deleted file mode 100644 index 9fa5ce9a..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig deleted file mode 100644 index a108a9d5..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-composer-support-in-your-dotkernel-project.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig deleted file mode 100644 index 90ce277b..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig deleted file mode 100644 index 79ebcb89..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig deleted file mode 100644 index fa772f3a..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig deleted file mode 100644 index ff0c3333..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/caching-in-dotkernel-using-zend-framework.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig deleted file mode 100644 index 26d42fe7..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/camelcase-table-names-in-mysql-on-windows.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig deleted file mode 100644 index c19602aa..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.jsonld.twig +++ /dev/null @@ -1,49 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig deleted file mode 100644 index f962e1ff..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/configuring-the-cache-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig deleted file mode 100644 index 16fdb988..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.jsonld.twig deleted file mode 100644 index 90f1d5b4..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/disable-wurfl-redirect-for-mobile-browsers.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/disable-wurfl-redirect-for-mobile-browsers.jsonld.twig deleted file mode 100644 index 44b09bb1..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/disable-wurfl-redirect-for-mobile-browsers.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.jsonld.twig deleted file mode 100644 index 11f11168..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-cache-using-symfony-cache.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-cache-using-symfony-cache.jsonld.twig deleted file mode 100644 index 6fe45383..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-cache-using-symfony-cache.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-enum-implementation-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-enum-implementation-in-dotkernel.jsonld.twig deleted file mode 100644 index 250a32b3..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/doctrine-enum-implementation-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotboost-technologies-products-and-services-north-american-relaunch.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotboost-technologies-products-and-services-north-american-relaunch.jsonld.twig deleted file mode 100644 index 658420cb..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotboost-technologies-products-and-services-north-american-relaunch.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-0-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-0-release.jsonld.twig deleted file mode 100644 index 67ecf61d..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-0-release.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig deleted file mode 100644 index a2ef2cc4..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-2-2-release.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig deleted file mode 100644 index 026d7600..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-0-release.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig deleted file mode 100644 index 6003d778..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-3-2-release.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig deleted file mode 100644 index 4e19dd5e..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-5-0-released.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig deleted file mode 100644 index 527db6df..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-0-lts-released.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig deleted file mode 100644 index 83a00b9e..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig deleted file mode 100644 index 160d96eb..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-coding-standard.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig deleted file mode 100644 index 13d82fe1..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-database-naming-conventions-for-mysql.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig deleted file mode 100644 index 652b30f2..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig deleted file mode 100644 index a4ef8587..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig deleted file mode 100644 index 000f8762..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-on-nginx.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig deleted file mode 100644 index 9791e9e9..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-reserved-variable-names-for-caching.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig deleted file mode 100644 index 09e45499..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-template-engine.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig deleted file mode 100644 index 57d74900..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/dotkernel-version-1-0-in-action.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig deleted file mode 100644 index bfc38ba1..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig deleted file mode 100644 index 062875d0..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-city-removed-from-dotkernel.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig deleted file mode 100644 index 08cf7f62..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/geoip-ip-address-location-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig deleted file mode 100644 index 46f79986..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig deleted file mode 100644 index 32cb10b7..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/highcharts-integration-in-dotkernel-1-6-0.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-build-this-website-starting-from-dotkernel-light.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-build-this-website-starting-from-dotkernel-light.jsonld.twig deleted file mode 100644 index 1c05e3db..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-build-this-website-starting-from-dotkernel-light.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig deleted file mode 100644 index a96e3fee..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-group-log-files-by-date-using-dot-log.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig deleted file mode 100644 index 9ea63ef5..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig deleted file mode 100644 index c3c354bd..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/how-to-use-alerts-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig deleted file mode 100644 index b62eec6e..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig deleted file mode 100644 index 56cd011f..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig deleted file mode 100644 index 9b42a185..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig deleted file mode 100644 index 16fb9c19..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/manual-upgrade-of-wurfl-xml-file-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/manual-upgrade-of-wurfl-xml-file-in-dotkernel.jsonld.twig deleted file mode 100644 index 7afb8453..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/manual-upgrade-of-wurfl-xml-file-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig deleted file mode 100644 index f01e6919..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/migration-of-zend-framework-1-pear-channel.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig deleted file mode 100644 index 3c0e3b6a..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/new-features-in-zend-framework-1-12.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig deleted file mode 100644 index 6bbd6830..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig deleted file mode 100644 index 48c5b218..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig deleted file mode 100644 index 08bca9fe..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.jsonld.twig +++ /dev/null @@ -1,110 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig deleted file mode 100644 index 9a5b3986..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig deleted file mode 100644 index 4d78b925..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/sending-emails-using-dot-email-component-and-zend-email.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig deleted file mode 100644 index e594a297..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/templating-in-dotkernel3.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig deleted file mode 100644 index a6d6888b..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig +++ /dev/null @@ -1,49 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig deleted file mode 100644 index 3c506f38..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/using-dotkernel-with-composer-dependencies.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig deleted file mode 100644 index e5e636b0..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/using-utf8-charset-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig deleted file mode 100644 index 4c131a63..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig deleted file mode 100644 index 73da9526..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig deleted file mode 100644 index bc2e6ced..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/wurfl-zend-framework-integration-into-dotkernel.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig deleted file mode 100644 index fdfc3d6f..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig deleted file mode 100644 index b0fce6a6..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-console-implementation-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig deleted file mode 100644 index 04ae7be6..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig deleted file mode 100644 index e390c7b0..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-registry-usage-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig deleted file mode 100644 index b5f2a001..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig deleted file mode 100644 index a4fbcd0c..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.jsonld.twig +++ /dev/null @@ -1,64 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig deleted file mode 100644 index f27347b8..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/development-report-december-11-2017.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig deleted file mode 100644 index 791af397..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-v4.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig deleted file mode 100644 index 3db23871..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-admin-version-3-launched.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-api-architecture-and-components.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-api-architecture-and-components.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-api-architecture-and-components.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-frontend-version-3-launched.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-frontend-version-3-launched.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel-frontend-version-3-launched.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel3-stable-release-version-1-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel3-stable-release-version-1-0.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/dotkernel3-stable-release-version-1-0.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-admin.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-admin.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-admin.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig deleted file mode 100644 index 819417bd..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-api.jsonld.twig +++ /dev/null @@ -1,102 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig deleted file mode 100644 index fe336c59..00000000 --- a/src/Blog/templates/page/JSON-LD/dotkernel3/php-8-3-support-in-dotkernel-frontend.jsonld.twig +++ /dev/null @@ -1,102 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig deleted file mode 100644 index a0c0b466..00000000 --- a/src/Blog/templates/page/JSON-LD/headless-platform/complementary-admin-in-dotkernel-headless-platform.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig deleted file mode 100644 index 42fc8407..00000000 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.jsonld.twig +++ /dev/null @@ -1,102 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig deleted file mode 100644 index daa5f565..00000000 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.jsonld.twig +++ /dev/null @@ -1,102 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig deleted file mode 100644 index b2b1b9d1..00000000 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.jsonld.twig +++ /dev/null @@ -1,110 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig deleted file mode 100644 index 3f63680c..00000000 --- a/src/Blog/templates/page/JSON-LD/headless-platform/dotmaker-generate-common-code-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,110 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig deleted file mode 100644 index 770c7898..00000000 --- a/src/Blog/templates/page/JSON-LD/headless-platform/evolution-pattern-versus-api-versioning.jsonld.twig +++ /dev/null @@ -1,94 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig deleted file mode 100644 index 2e1cbbe5..00000000 --- a/src/Blog/templates/page/JSON-LD/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,102 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig deleted file mode 100644 index 3b37e074..00000000 --- a/src/Blog/templates/page/JSON-LD/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig b/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig deleted file mode 100644 index b251d44f..00000000 --- a/src/Blog/templates/page/JSON-LD/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig deleted file mode 100644 index bfe460af..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig deleted file mode 100644 index 289eb980..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/creating-admin-accounts-in-dotkernel-api.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig deleted file mode 100644 index 131bdf25..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/database-migrations-and-how-to-use-them.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig deleted file mode 100644 index fc149965..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/doctrine-cache-in-mezzio-and-dotkernel.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig deleted file mode 100644 index 9d6605ca..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig deleted file mode 100644 index 9ffbe451..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig deleted file mode 100644 index 143a3aa1..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/migrating-dotkernel-3-from-zend-expressive-2-to-zend-expressive-3.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/migrating-dotkernel-3-from-zend-expressive-2-to-zend-expressive-3.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/migrating-dotkernel-3-from-zend-expressive-2-to-zend-expressive-3.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/using-postman-for-documentation-in-dotkernel-api-3.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/using-postman-for-documentation-in-dotkernel-api-3.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/using-postman-for-documentation-in-dotkernel-api-3.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/using-the-urlgenerator-work-in-fastroute.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/using-the-urlgenerator-work-in-fastroute.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/using-the-urlgenerator-work-in-fastroute.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/what-is-cross-origin-token-redemption.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/what-is-cross-origin-token-redemption.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/what-is-cross-origin-token-redemption.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/how-to/what-is-psr-7-and-how-to-use-it.jsonld.twig b/src/Blog/templates/page/JSON-LD/how-to/what-is-psr-7-and-how-to-use-it.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/how-to/what-is-psr-7-and-how-to-use-it.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.jsonld.twig b/src/Blog/templates/page/JSON-LD/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/javascript/intro-to-jquery.jsonld.twig b/src/Blog/templates/page/JSON-LD/javascript/intro-to-jquery.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/javascript/intro-to-jquery.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig b/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig deleted file mode 100644 index a13fefdb..00000000 --- a/src/Blog/templates/page/JSON-LD/javascript/javascript-email-validator.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig b/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig deleted file mode 100644 index 1625e92d..00000000 --- a/src/Blog/templates/page/JSON-LD/licensing/mit-versus-lgpl-in-practice-dotkernel-case.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig b/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig deleted file mode 100644 index e50cee7c..00000000 --- a/src/Blog/templates/page/JSON-LD/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig b/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig deleted file mode 100644 index 2e7b0c87..00000000 --- a/src/Blog/templates/page/JSON-LD/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig b/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig deleted file mode 100644 index 26b5ea9d..00000000 --- a/src/Blog/templates/page/JSON-LD/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig deleted file mode 100644 index e8cf1a4f..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/aptana-php-installation-in-aptana-2-x.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/aptana-php-installation-in-aptana-2-x.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/aptana-php-installation-in-aptana-2-x.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig deleted file mode 100644 index ea129cb2..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/end-of-support-for-php-5-2-x-branch.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig deleted file mode 100644 index c5629a2d..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig deleted file mode 100644 index b198c433..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/how-to-upgrade-wamp-to-php-5-3-4.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig deleted file mode 100644 index eefe2fa9..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/mezzio-app-development-in-wsl2.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig deleted file mode 100644 index 85c80d94..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig deleted file mode 100644 index d8d1d11b..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/php-environment-development-staging-production.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig deleted file mode 100644 index 673c750c..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/php-support-back-in-aptana-3-0.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig deleted file mode 100644 index 615e84d2..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig deleted file mode 100644 index d04ef55f..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.jsonld.twig +++ /dev/null @@ -1,54 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/remote-connections-to-mysql-server-on-plesk-based-servers.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/remote-connections-to-mysql-server-on-plesk-based-servers.jsonld.twig deleted file mode 100644 index 8e31b413..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/remote-connections-to-mysql-server-on-plesk-based-servers.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/static-analysis-replacing-psalm-with-phpstan.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/static-analysis-replacing-psalm-with-phpstan.jsonld.twig deleted file mode 100644 index eb8cb359..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/static-analysis-replacing-psalm-with-phpstan.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/using-aptana-to-connect-to-dotkernel-tracker-mantis.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/using-aptana-to-connect-to-dotkernel-tracker-mantis.jsonld.twig deleted file mode 100644 index b3af0290..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/using-aptana-to-connect-to-dotkernel-tracker-mantis.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/using-php-7-express-in-zend-studio-13.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/using-php-7-express-in-zend-studio-13.jsonld.twig deleted file mode 100644 index 144197be..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/using-php-7-express-in-zend-studio-13.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/version-control-ignore-patterns-in-zend-studio.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/version-control-ignore-patterns-in-zend-studio.jsonld.twig deleted file mode 100644 index db8bca15..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/version-control-ignore-patterns-in-zend-studio.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/welcome-to-the-10th-zend-certified-engineer-in-dotboost-team.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/welcome-to-the-10th-zend-certified-engineer-in-dotboost-team.jsonld.twig deleted file mode 100644 index cb38e280..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/welcome-to-the-10th-zend-certified-engineer-in-dotboost-team.jsonld.twig +++ /dev/null @@ -1,50 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-development/zend-server-5-5-quick-setup-on-windows.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-development/zend-server-5-5-quick-setup-on-windows.jsonld.twig deleted file mode 100644 index 73842d4d..00000000 --- a/src/Blog/templates/page/JSON-LD/php-development/zend-server-5-5-quick-setup-on-windows.jsonld.twig +++ /dev/null @@ -1,51 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.jsonld.twig deleted file mode 100644 index 8c681244..00000000 --- a/src/Blog/templates/page/JSON-LD/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.jsonld.twig +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.jsonld.twig b/src/Blog/templates/page/JSON-LD/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.jsonld.twig deleted file mode 100644 index 6cbee1f9..00000000 --- a/src/Blog/templates/page/JSON-LD/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.jsonld.twig +++ /dev/null @@ -1,53 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.jsonld.twig b/src/Blog/templates/page/JSON-LD/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/scienta-zf-debug-bar-a-very-helpfull-zf-debug-tool.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/scienta-zf-debug-bar-a-very-helpfull-zf-debug-tool.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/zend-framework/scienta-zf-debug-bar-a-very-helpfull-zf-debug-tool.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/sunsetting-pear-channel-for-zend-framework-1.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/sunsetting-pear-channel-for-zend-framework-1.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/zend-framework/sunsetting-pear-channel-for-zend-framework-1.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/wurfl-php-api-libraries-gpl-versions.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/wurfl-php-api-libraries-gpl-versions.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/zend-framework/wurfl-php-api-libraries-gpl-versions.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-12-4-released-with-security-fixes.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-12-4-released-with-security-fixes.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-12-4-released-with-security-fixes.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-7-0-released.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-7-0-released.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-7-0-released.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-end-of-life.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-end-of-life.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-1-end-of-life.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-as-pear-accessible-repository-on-plesk-server.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-as-pear-accessible-repository-on-plesk-server.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/zend-framework/zend-framework-as-pear-accessible-repository-on-plesk-server.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/JSON-LD/zend-framework/zend-mail-and-zend-http-security-fixes-in-zend-framework-1-12-12.jsonld.twig b/src/Blog/templates/page/JSON-LD/zend-framework/zend-mail-and-zend-http-security-fixes-in-zend-framework-1-12-12.jsonld.twig deleted file mode 100644 index e87ea3ab..00000000 --- a/src/Blog/templates/page/JSON-LD/zend-framework/zend-mail-and-zend-http-security-fixes-in-zend-framework-1-12-12.jsonld.twig +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/src/Blog/templates/page/blog-resource/android/listen-for-android-install-referrer.html.twig b/src/Blog/templates/page/blog-resource/android/listen-for-android-install-referrer.html.twig deleted file mode 100644 index 4780de81..00000000 --- a/src/Blog/templates/page/blog-resource/android/listen-for-android-install-referrer.html.twig +++ /dev/null @@ -1,26 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

Getting Referrer Data at Install Time

- -

Have you ever wondered if Android market sends you information at the moment of app install? Wouldn't it 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 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...

- -

Frequently Asked Questions

- -
-
- Does Android send information when the app is installed? + -
-

Yes. Android market broadcasts an intent containing referrer information at the moment the app is installed.

-
-
-
- When is this referrer information available to the app? + -
-

It's delivered as a broadcasted intent at install time, before the app is ever opened.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.html.twig b/src/Blog/templates/page/blog-resource/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.html.twig deleted file mode 100644 index 687045fb..00000000 --- a/src/Blog/templates/page/blog-resource/android/multiple-broadcast-receivers-in-the-same-app-for-the-same-action.html.twig +++ /dev/null @@ -1,26 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -The following is a solution for this kind of problem, a code snippet inspired by the way Admob for android seems to solve this, as shown in their documentation, using meta-data in manifest file...[read more]. - -

Frequently Asked Questions

- -
-
- What problem does this article address? + -
-

When multiple broadcast receivers are registered separately to listen for the same intent in the same Android app, this can lead to unexpected results: one broadcast receiver might consume the broadcasted intent, leaving the others with nothing to receive.

-
-
-
- When is this issue most likely to occur? + -
-

This can happen when you use 3rd party libraries that already define their own broadcast receivers alongside your app's own receivers.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/architecture/configprovider-bootstrap-modern-php-applications.html.twig b/src/Blog/templates/page/blog-resource/architecture/configprovider-bootstrap-modern-php-applications.html.twig deleted file mode 100644 index a40ae306..00000000 --- a/src/Blog/templates/page/blog-resource/architecture/configprovider-bootstrap-modern-php-applications.html.twig +++ /dev/null @@ -1,167 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

If you're talking specifically about the ConfigProvider in the Laminas/Mezzio ecosystem, it's literally an array of configuration, settings, or anything else your application needs.

- -

Where Is the ConfigProvider Used?

- -

Mezzio (formerly Zend Expressive), Laminas, Slim, the Dotkernel Headless Platform, or other middleware-based frameworks often have a ConfigProvider class. In Laminas/Mezzio specifically, each module or package may contain a ConfigProvider that returns:

- - - -

Example in Dotkernel, which is an approach similar to Laminas/Mezzio:

- -
class ConfigProvider
-{
-    public function __invoke(): array
-    {
-        return [
-            'dependencies' => $this->getDependencies(),
-            'templates'    => $this->getTemplates(),
-        ];
-    }
-
-    public function getDependencies(): array
-    {
-        return ,
-           'invokables'   => ,
-        ];
-    }
-
-    public function getTemplates(): array
-    {
-        return ,
-        'error'  => ,
-        ];
-    }
-}
- -

What each item above means:

- - - -

How the ConfigProvider works

- -

The ConfigProvider is automatically picked up by the framework during application bootstrap. Let's look at it step by step:

- - - -
$config = $container->get('config') ?? [];
- - - -

Below you can see how Mezzio and Dotkernel merge and use ConfigProviders to build the middleware pipeline and dependencies.

- -
- -

Benefits

- - - -

Dotkernel is an exception to this rule: new ConfigProviders have to be added manually in config/config.php, because all the initial ConfigProviders required to install the applications are already injected.

-
- - - -

Frequently Asked Questions

- -
-
- What is a ConfigProvider in PHP? + -
-

It is a class that is part of an application's bootstrap process: a class or callable that returns configuration data telling the platform which middleware should run, in what order, and sometimes under what conditions.

-
-
-
- What does the ConfigProvider return in the Laminas/Mezzio ecosystem? + -
-

In the Laminas/Mezzio ecosystem, it's literally an array of configuration, settings, or anything else the application needs, and each module or package may contain its own ConfigProvider returning middleware pipeline configuration, dependency injection mappings, and request handlers.

-
-
-
- What is the difference between 'factories' and 'invokables' in the dependencies array? + -
-

factories will have the factory build the service, while invokables will use new directly. You can also use aliases to redirect to another service name and delegators to wrap the original service.

-
-
-
- How does the ConfigProvider get used during application bootstrap? + -
-

It is automatically picked up by the framework during bootstrap: all ConfigProviders are merged into one array, the configuration array is read (similar to $config = $container->get('config') ?? [];), each item is resolved via $app->pipe(), the error-handling middleware is placed last in the pipeline, and at runtime Laminas Stratigility iterates over the pipeline in the order it was registered.

-
-
-
- Are new ConfigProviders auto-discovered in Dotkernel? + -
-

Dotkernel is an exception to the usual auto-discovery rule: new ConfigProviders have to be added manually in config/config.php, because all the initial ConfigProviders required to install the applications are already injected.

-
-
-
- What are the benefits of using a ConfigProvider? + -
-

Benefits include centralized setup instead of hardcoded bootstrap code, modularity so each package can ship its own config, container-friendliness with DI containers like Laminas ServiceManager, PHP-DI or Pimple, standardized service definitions, environment-agnostic configuration for dev/test/prod, and better testability of the pipeline setup.

-
-
-
- -

Additional Resources

- - - -

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/architecture/understanding-middleware.html.twig b/src/Blog/templates/page/blog-resource/architecture/understanding-middleware.html.twig deleted file mode 100644 index cec46ff6..00000000 --- a/src/Blog/templates/page/blog-resource/architecture/understanding-middleware.html.twig +++ /dev/null @@ -1,159 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. In web services the Input represents the Request received, and Output represents the Response to be sent.

- -

Using middleware

- -

Middleware can be used to, but is not limited to, the following purposes:

- - - -

Usage

- -

According to PSR-15: HTTP Server Request Handlers, a component that processes an incoming request and generates a response is a middleware. To be compliant with the PSR-15 standard, the middleware must implement Psr\Http\Server\MiddlewareInterface.

- -
class MyMiddleware implements MiddlewareInterface
- -

The middleware class must then implement the process method.

- -
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
- -

Below is an example implementation of a middleware which processes the request.

- -
class ExampleMiddleware implements MiddlewareInterface
-{
-    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
-    {
-        //process request
-
-        return $handler->handle($request);
-    }
-}
- -

This is an example implementation of a middleware which processes the response.

- -
class ExampleMiddleware implements MiddlewareInterface
-{
-    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
-    {
-        $response = $handler->handle($request);
-
-        //process response
-
-        return $response;
-    }
-}
- -

This approach processes both the request and response.

- -
class ExampleMiddleware implements MiddlewareInterface
-{
-    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
-    {
-        //process request
-
-        $response = $handler->handle($request);
-
-        //process response
-
-        return $response;
-    }
-}
- -

How middleware is called

- -

The application pipeline defines the execution flow. The request passes through the middleware in the pipeline, one by one, in the order they are placed in the pipeline. Each middleware processes the request and/or response and either passes control to the next middleware in the chain or it terminates the request and returns a reponse.

- - - -

Middleware in practice

- -

A simple real world example of middleware usage can be the enhancement of a request with the user IP for logging porposes or building reports based on geographical data. For this example the pipeline has a single middleware.

- -

The flow begins with a request. The execution passes the control to the IP middleware which enhances the request with the user's IP and other relevant data. The control passes to your custom handler that processes the request and returns a response. The flow continues in reverse order, back to the IP middleware which can, if needed, change the output before it gets returned to the user that initiated the request.

- -

Frequently Asked Questions

- -
-
- What is middleware? + -
-

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.

-
-
-
- What is 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. In web services, the Input represents the Request received, and Output represents the Response to be sent.

-
-
-
- What can middleware be used for? + -
-

Middleware can be used for purposes such as A/B testing, debugging, caching, CORS, authentication (HTTP Basic Auth, OAuth 2.0, OpenID), CSRF protection, rate limiting, referrals, and IP restriction.

-
-
-
- What interface must PHP middleware implement to be PSR-15 compliant? + -
-

According to PSR-15, a compliant middleware must implement Psr\Http\Server\MiddlewareInterface, which requires a process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface method.

-
-
-
- How does middleware get called within the application pipeline? + -
-

The application pipeline defines the execution flow: the request passes through the middleware one by one, in the order they are placed. If control passes through all middleware successfully, execution is passed to your custom code, which generates a response, and execution then passes back through the middleware in reverse order. If execution is terminated before reaching your custom code (e.g. via an exception), the response is generated by the last middleware reached.

-
-
-
- What is a practical, real-world example of middleware? + -
-

A simple example is enhancing a request with the user's IP for logging purposes or geographical reporting. The request first passes through the IP middleware, which enhances the request with the user's IP and other relevant data, then control passes to the custom handler that processes the request and returns a response. The flow continues in reverse, back through the IP middleware, which can change the output before it's returned to the user.

-
-
-
- -

Additional resources:

- - -{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/aptana-set-svn-keywords.html.twig b/src/Blog/templates/page/blog-resource/best-practice/aptana-set-svn-keywords.html.twig deleted file mode 100644 index a6aa857c..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/aptana-set-svn-keywords.html.twig +++ /dev/null @@ -1,47 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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: - - - - - - - - -When you make the SVN commit of the file, the $Id$ keyword will be replaced with text in the format shown below: - - - -

Frequently Asked Questions

- -
-
- How do you set the svn:keywords property for a file in Aptana? + -
-

Write the keyword marker (for example $Id$) in the file, then right click the file and follow Team -> Set Property..., select svn:keywords, and write Id in the text field.

-
-
-
- Why is "Set Property..." not active when I right click the file? + -
-

Set Property... will not be active if the file hasn't first been added to SVN. Use Team -> Add to Version Controller before trying to set the property.

-
-
-
- What happens to the $Id$ keyword after an SVN commit? + -
-

After the SVN commit of the file, the $Id$ keyword is replaced with text containing the file's SVN metadata, in a specific format.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/basic-security-in-dotkernel-headless-platform.html.twig b/src/Blog/templates/page/blog-resource/best-practice/basic-security-in-dotkernel-headless-platform.html.twig deleted file mode 100644 index 2a12e508..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/basic-security-in-dotkernel-headless-platform.html.twig +++ /dev/null @@ -1,227 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. You ignore security at your own risk, with potentially major costs to your personal or your organization's image, and to your client's trust in your abilities. The costs to recover the damages caused by a lacking security are sometimes astronomical, enough to put a company out of business.

- -

There are many facets of software security, meaning there are a lot of potential ways a hacker can access your code or your data fraudulently:

- - - -

To keep a platform safe, you must actively mitigate these risks with recommended coding practices that include tight security. You wouldn't build a nice house, only to leave the door unlocked, right?

- -

- -

The Tenets of Software Security in Dotkernel Headless Platform

- -

We at Dotkernel aim to:

- - - -

We do all this to attempt to stay ahead of the vulnerabilities that can lead from otherwise useful, productive code, to data loss and a GDPR fine, or a loss of funds for our clients.

- -

Let's take a practical view on software security in Dotkernel.

- -

Form Input Validation

- -

You should never trust that the user inputs correct data by passing it directly into your business logic. By defining the configuration for an input filter, you ensure that a field is both present, and of the correct type.

- -

Dotkernel API makes use of laminas/laminas-inputfilter for this purpose.

- -

In addition to the above filtering, Dotkernel Admin also makes use of laminas/laminas-form. laminas-form contains:

- - - -

laminas-form ensures that data validation, filtering, and rendering enforce strong security practices by design. It also has integration with the Laminas Security Ecosystem that contains laminas-escaper, laminas-validator, laminas-session, and laminas-filter.

- -

Content Negotiation

- -

Content negotiation is used in RESTful APIs to ensure that systems work seamlessly together by having the client and server agree on the format and language of data they exchange.

- -

Dotkernel API handles content negotiation via a middleware configured in the config/autoload/content-negotiation.global.php file. It handles client-side content negotiation via the use of two HTTP request headers: Content-Type and Accept, and returns application/json, application/hal+json data formats.

- -

Cross-Origin Resource Sharing

- -

Cross-Origin Resource Sharing (or CORS) is a security mechanism implemented into web browsers to control how web pages can request resources from a different domain than the one where the request originated from.

- -

In Dotkernel API, CORS is handled by mezzio/mezzio-cors and configured in the config/autoload/cors.local.php file. mezzio-cors starts to detect the proper cors configuration whenever it detects a cors preflight. Cors validates the call using several configuration items: origins, headers, max age, credentials.

- -

When configuring your pipeline, make sure to add the CorsMiddleware BEFORE the RouteMiddleware.

-
- -

Role-Based Access Control

- -

Role-Based Access Control (or RBAC) is a security model used in software systems to manage access to resource. It does this by assigning roles to users types which are in turn assigned to users who require a certain level of access.

- -

Dotkernel API uses mezzio/mezzio-authorization-rbac for this purpose. There are several roles predefined, which you can configure to suit your project by editing the config/autoload/authorization.global.php file.

- -

Demo Credentials

- -

Demo credentials are provided in Dotkernel API for your convenience, to allow you to test the installation easily.

- -

It is important to update or remove these accounts in your production environment.

-
- -

Error Reporting Endpoint and ErrorReportingTokens

- -

The purpose for the error reporting endpoint is to have a reliable channel through which 3rd-party developers can report issues to you directly.

- -

Dotkernel API has a dedicated endpoint /error-report for this purpose. It uses and ErrorReportingToken set up in the configuration file config/autoload/error-handling.global.php.

- -

OpenAPI Documentation

- -

OpenAPI documentation (formerly known as Swagger) provides a standardized, machine-readable way to describe APIs, meaning their requests and responses. It's critical for:

- - - -

Dotkernel API implements zircote/swagger-php to provide an interactive documentation.

- -

Do not include sensitive information for you endpoints. Do not enable documentation in a production environment.

-
- -

PHP Dependencies

- -

Modern PHP projects rely heavily on external packages via package managers like Composer. There is a tangible risk of exposing your application by using insecure dependencies.

- -

Dotkernel API has regular checks for vulnerable and outdated packages. Often the dependencies used in projects have transient dependencies which must also be checked.

- -

Always use dependencies from reliable sources and keep them updated to their latest version.

-
- -

OAuth2 Security

- -

OAuth 2.0 is a secure authorization framework that allows one application to access resources or data on behalf of a user, without requiring the user's password. It is considered an industry standard for secure authorization across web, mobile, and API-based systems.

- -

Dotkernel API uses the mezzio/mezzio-authentication-oauth2 for the OAuth2 authentication service. The package itself is secure, but you still need to make sure you use it properly:

- - - -

Session and Cookie Settings

- -

Sessions and cookies are used in web development to store data between HTTP requests. For example, they can be used to save login information or preferences, and to track user behavior.

- -

Dotkernel configures cookies in the config/autoload/session.global.php file. It contains several parameters that you must revise and adapt to your application:

- - - -

JavaScript Dependencies

- -

Very much like composer for PHP, JavaScript has its own dependencies, usually installed via npm or yarn. The JavaScript ecosystem has recently been attacked by hackers who targetted several widely used npm packages that have a total number of uses in the billions.

- -

Dotkernel uses npm to handle JavaScript dependencies. We monitor the news to stay on top of these security issues and use npm packages from reliable sources. Even so, you should regularly use the npm audit to check for vulnerabilities among your installed npm libraries.

- -

Other Security Considerations

- -

All components of Dotkernel Headless Platform have several configuration files with the name format *.global.php', '*.php.dist and *.local.php. You must only include sensitive information in the *.local.php files, since they are, by default, ignored by the VCS.

- -

The development mode is designed, as the name suggests, only for the development period. By enabling development mode, you enable features like debug mode, cache clear and show error details. These should be hidden from the production environment to avoid exposing sensitive data or code.

- -

The GitHub Action Laminas Continuous Integration is an integral component of Dotkernel API. It ensures code quality by streamlining the execution of PHP quality assurance (QA) tasks within continuous integration (CI) workflows. Most often triggered by commits to the repository, it builds a matrix of tests: static analysis, coding standards checks, and unit tests.

- -

Frequently Asked Questions

- -
-
- What are the main facets of software security to consider? + -
-

Software security spans many areas: authentication and access control, data protection, input validation and injection, web and API security, dependency and supply chain risks, configuration and deployment, network and infrastructure security, logging/monitoring and incident response, secure software development lifecycle, and human and organizational factors.

-
-
-
- How does Dotkernel handle form input validation? + -
-

Dotkernel API uses laminas/laminas-inputfilter to ensure a field is present and of the correct type. Dotkernel Admin additionally uses laminas/laminas-form, which provides form element objects, an InputFilter for each input (or custom validators), and methods for binding data to and from the form, integrating with laminas-escaper, laminas-validator, laminas-session, and laminas-filter.

-
-
-
- How does Dotkernel API handle content negotiation? + -
-

Content negotiation is handled via a middleware configured in the config/autoload/content-negotiation.global.php file. It uses the Content-Type and Accept HTTP request headers to negotiate with the client, returning application/json or application/hal+json data formats.

-
-
-
- How is CORS handled and configured in Dotkernel API? + -
-

CORS is handled by mezzio/mezzio-cors and configured in the config/autoload/cors.local.php file, validating calls using configuration items like origins, headers, max age, and credentials. When configuring the pipeline, the CorsMiddleware must be added before the RouteMiddleware.

-
-
-
- What should be done with the demo credentials before going to production? + -
-

Demo credentials are provided for convenience during installation testing, but it is important to update or remove these accounts in your production environment.

-
-
-
- What are the security recommendations around OpenAPI documentation? + -
-

You should not include sensitive information for your endpoints in the OpenAPI documentation, and you should not enable the documentation in a production environment.

-
-
-
- -

Additional Resources

- - - -

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/golden-rules-of-professional-php-coding.html.twig b/src/Blog/templates/page/blog-resource/best-practice/golden-rules-of-professional-php-coding.html.twig deleted file mode 100644 index 2becc9f2..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/golden-rules-of-professional-php-coding.html.twig +++ /dev/null @@ -1,57 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. Fix every warning or notice that occur. -3. Check regularly server's error_log for notices/warnings - -4. Identify any temporary hack with a special mark. Maybe something like: -
#@TODO masterpiece by @smartguy, to quick fix the division by zero
-  - -5. Each function must do a single task. If is log in the user and record the login in stats table, be nice and create a separate function for 'record the login' stuff. -Maybe even a distinct class for stats ? - -6. Use a version control system. SVN is NOT dead. - -7. Use an IDE Aptana 2, Aptana 3, Eclipse, even Zend Studio . - -8. Know your IDE: code snippets, code assist, integration with Zend Framework, SVN integration, bug tracker integration, and so on - -

Frequently Asked Questions

- -
-
- What error reporting settings should be used in development and staging? + -
-

Always use the highest error reporting level and turn display_errors ON, for example with error_reporting(-1); and ini_set('display_errors', 1);.

-
-
-
- What should you do about warnings and notices? + -
-

Fix every warning or notice that occurs, and regularly check the server's error_log for notices and warnings.

-
-
-
- How should temporary hacks or quick fixes be marked in code? + -
-

Identify any temporary hack with a special mark, such as a #@TODO comment noting who added it and why.

-
-
-
- What is the rule about what a function should do? + -
-

Each function must do a single task. For example, if you're logging in a user and also recording that login in a stats table, create a separate function (or even a distinct class) for the stats recording, rather than combining both tasks in one function.

-
-
-
- What tools does the article recommend for professional PHP development? + -
-

It recommends using a version control system (noting that SVN is not dead) and using an IDE such as Aptana 2, Aptana 3, Eclipse, or Zend Studio, and knowing your IDE's code snippets, code assist, Zend Framework integration, SVN integration, and bug tracker integration.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/htaccess-301-redirect-non-www-to-www.html.twig b/src/Blog/templates/page/blog-resource/best-practice/htaccess-301-redirect-non-www-to-www.html.twig deleted file mode 100644 index c4d7ae4a..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/htaccess-301-redirect-non-www-to-www.html.twig +++ /dev/null @@ -1,32 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -To always redirect users to the www site (for example: http://dotboost.com to http://www.dotboost.com), add the following lines to .htaccess, right after RewriteEngine On: - -
RewriteCond %{HTTP_HOST} ^dotboost.com
-RewriteRule ^(.*)$ http://www.dotboost.com/$1 [L,R=301]
- -If, on the other hand, you want to redirect http://www.dotboost.com to http://dotboost.com, add the following lines instead: - -
RewriteCond %{HTTP_HOST} ^www.dotboost.com
-RewriteRule ^(.*)$ http://dotboost.com/$1 [L,R=301]
- -Replace dotboost.com with your site's domain. - -

Frequently Asked Questions

- -
-
- How do I redirect a non-www domain to www using .htaccess? + -
-

Add RewriteCond %{HTTP_HOST} ^dotboost.com and RewriteRule ^(.*)$ http://www.dotboost.com/$1 to your .htaccess file, right after RewriteEngine On, replacing dotboost.com with your own domain.

-
-
-
- How do I redirect a www domain to non-www instead? + -
-

Add RewriteCond %{HTTP_HOST} ^www.dotboost.com and RewriteRule ^(.*)$ http://dotboost.com/$1 instead, again replacing dotboost.com with your own domain.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/insert-update-delete-statements-with-zend-db.html.twig b/src/Blog/templates/page/blog-resource/best-practice/insert-update-delete-statements-with-zend-db.html.twig deleted file mode 100644 index e440671b..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/insert-update-delete-statements-with-zend-db.html.twig +++ /dev/null @@ -1,87 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. There are 3 primary DML statements: - -
- -Note*: -
-$db = Zend_Db::factory('Pdo_Mysql', $dbConnect);
-
-
-INSERT -
-INSERT INTO user(email, password, firstName, lastName, active)
-       VALUES ('$email', '$password', '$firstName', '$lastName', 1);
-
-The above SQL INSERT statement is translated in Zend_Db as follow: -
-$data = array( 'email' => $email,
-            'password' => $password,
-            'firstName' => $firstName,
-            'lastName' => $lastName,
-            'active' => '1');
-$db->insert('user', $data);
-

-UPDATE -
-UPDATE user
-   SET password = '$password',
-       firstName = '$firstName',
-       lastName = '$lastName',
-       accountUpdate = (accountUpdate +1)
- WHERE id = '$id'
-
-The above SQL UPDATE statemnet is translated in Zend_Db as follow: -
-$data = array('password' => $password,
-              'firstName' => $firstName,
-              'lastName' => $vlastname,
-              'accountUpdate' => new Zend_Db_Expr('accountUpdate+1'));
-$db->update('user', $data, 'id = '.$id);
-
-
-DELETE -
-DELETE FROM user WHERE id = '$id'
-
-The above SQL DELETE statemnet is translated in Zend_Db as follow: -
-$db->delete('user', 'id = '.$id);
-
- -

Frequently Asked Questions

- -
-
- What are DML statements? + -
-

DML (Data Manipulation Language) statements are statements that change data values in database tables. There are 3 primary DML statements: INSERT, UPDATE, and DELETE.

-
-
-
- How do you insert a new row with Zend_Db? + -
-

Build an associative array of column names to values (e.g. email, password, firstName, lastName, active) and pass it to $db->insert('user', $data), which corresponds to an SQL INSERT INTO ... VALUES statement.

-
-
-
- How do you update rows with Zend_Db, including incrementing a column? + -
-

Build a $data array of the columns to update, using a Zend_Db_Expr for expressions such as incrementing accountUpdate (new Zend_Db_Expr('accountUpdate+1')), then call $db->update('user', $data, 'id = '.$id).

-
-
-
- How do you delete a row with Zend_Db? + -
-

Call $db->delete('user', 'id = '.$id), which is equivalent to the SQL statement DELETE FROM user WHERE id = '$id'.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/sql-queries-using-zend-db-select.html.twig b/src/Blog/templates/page/blog-resource/best-practice/sql-queries-using-zend-db-select.html.twig deleted file mode 100644 index a1d69542..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/sql-queries-using-zend-db-select.html.twig +++ /dev/null @@ -1,134 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. First one is simple, the second one use INNER JOIN keyword, but the result is the same. -
-SELECT a.id, a.name, b.order_id
-FROM users AS a, orders AS b
-WHERE a.id = b.user_id
-AND a.id = {$userId}
-
-
-
-SELECT `a`.`id`, `a`.`name`, `b`.`order_id`
-FROM `users` AS `a` INNER JOIN `orders` AS `b` ON a.id = b.user_id
-WHERE (a.id = '{$userId}')
-
-
-The above querys are translated in Zend_Db style: -
-
-$select = $db->select()
-             ->from(array('a'=>'users'),
-                    array('a.id', 'a.name'))
-             ->join(array('b'=>'orders'), 'a.id = b.user_id', array('b.order_id'))
-             ->where('a.id = ?', $userId)
-
-
-If we don't want to select any column from the second table, the 3rd parameter of join() method should be an empty string -
-SELECT a.id, a.name
-FROM users AS a, orders AS b
-WHERE a.id = b.user_id
-AND a.id = {$userId}
-
-
-
 >
-$select = $db->select()
-             ->from(array('a'=>'users'),
-                    array('a.id', 'a.name'))
-             ->join(array('b'=>'orders'), 'a.id = b.user_id', '')
-             ->where('a.id = ?', $userId)
-
-
-Note*: If we don't write the 3rd parameter, it will select all the fields from that table: -
-SELECT a.id, a.name, b.*
-FROM users AS a, orders AS b
-WHERE a.id = b.user_id
-AND a.id = {$user_id}
-
-
-
 >
-$select = $db->select()
-             ->from(array('a'=>'users'),
-                    array('a.id', 'a.name'))
-              ->join(array('b'=>'orders'), 'a.id = b.user_id')
-              ->where('a.id = ?', $userId)
-
-
-SELECT query - WHERE IN clause -
-
-SELECT id
-FROM users
-WHERE aff_id IN ('1','2','3')
-
-
-
 >
-$select = $db->select()
-             ->from('users', array('id'))
-             ->where('aff_id IN (?)', array(1,2,3));
-
-
-Note*: If you are not sure if you write the correct query, before you fetch it you can echo your query to visualize it: -
-echo $select->__toString();exit;
-
-
-Also see: - - What are returning the FETCH functions from Zend_Db
- - Subqueries with Zend_Db
- - INSERT, UPDATE, DELETE statements with Zend_Db - -

Frequently Asked Questions

- -
-
- What does Zend_Db provide? + -
-

Zend_Db and its related classes provide a simple SQL database interface for Zend Framework. To connect to a MySQL database, the Pdo_Mysql adapter is used via Zend_Db::factory('Pdo_Mysql', $dbConnect).

-
-
-
- How do you write a SELECT with a JOIN and a WHERE clause in Zend_Db style? + -
-

Use $db->select()->from(array('a'=>'users'), array('a.id','a.name'))->join(array('b'=>'orders'), 'a.id = b.user_id', array('b.order_id'))->where('a.id = ?', $userId), which is equivalent to a classical SQL query using INNER JOIN.

-
-
-
- How do you join a table without selecting any of its columns? + -
-

Pass an empty string as the 3rd parameter of the join() method, e.g. ->join(array('b'=>'orders'), 'a.id = b.user_id', '').

-
-
-
- What happens if the 3rd parameter of join() is omitted entirely? + -
-

If the 3rd parameter is not written, it will select all the fields from that joined table (equivalent to SELECT ..., b.* in SQL).

-
-
-
- How do you write a WHERE IN clause with Zend_Db? + -
-

Use ->where('aff_id IN (?)', array(1,2,3)) on the select object, equivalent to SQL's WHERE aff_id IN ('1','2','3').

-
-
-
- How can you check that a Zend_Db select is generating the correct query? + -
-

Before fetching it, echo the query to visualize it: echo $select->__toString();exit;

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/subqueries-with-zend-db.html.twig b/src/Blog/templates/page/blog-resource/best-practice/subqueries-with-zend-db.html.twig deleted file mode 100644 index 97826ef2..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/subqueries-with-zend-db.html.twig +++ /dev/null @@ -1,68 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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.id,
-       a.title,
-       (SELECT COUNT(c.track_id)
-        FROM track_files AS c
-        WHERE c.track_id = a.id
-       ) AS `count_files`,
-       COUNT(b.track_id) AS count_courses
-FROM tracks AS a
-LEFT JOIN track_courses AS b ON (a.id = b.track_id)
-GROUP BY a.id
-
-
-Initialize the connection to our MySql database: -
-$db = Zend_Db::factory('Pdo_Mysql', $dbConnect);
-
-
-
-$db->select()
-   ->from(array('a'=>'tracks'),
-         array('id',
-              'title',
-             'count_files' => new Zend_Db_Expr(
-                        '('.$db->select()
-                           ->from(array('c'=>'track_files'),
-                                     array(new Zend_Db_Expr('COUNT(c.track_id)')))
-                           ->where('c.track_id = a.id').')' )
-               )
-          )
-   ->joinLeft(array('b'=>'track_courses'),
-         'a.id = b.track_id',
-         array('count_courses' => 'COUNT(b.track_id)')
-         )
-   ->group('a.id');
-
- -

Frequently Asked Questions

- -
-
- What SQL techniques does this subquery example combine? + -
-

The example combines COUNT(), LEFT JOIN, and GROUP BY, selecting from 3 tables and counting rows from 2 different tables.

-
-
-
- How do you embed a subquery as a selected column in a Zend_Db select? + -
-

Wrap a nested $db->select() call inside a Zend_Db_Expr, building the subquery string with the outer table's correlated WHERE condition (e.g. c.track_id = a.id), as shown for the count_files column.

-
-
-
- How is the LEFT JOIN with a COUNT expressed in Zend_Db? + -
-

Use ->joinLeft(array('b'=>'track_courses'), 'a.id = b.track_id', array('count_courses' => 'COUNT(b.track_id)')) followed by ->group('a.id').

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/svn-export-in-a-virtual-host.html.twig b/src/Blog/templates/page/blog-resource/best-practice/svn-export-in-a-virtual-host.html.twig deleted file mode 100644 index 321a6283..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/svn-export-in-a-virtual-host.html.twig +++ /dev/null @@ -1,69 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -First make sure that Subversion is installed on the host. To check if it is installed, run: -
svn --version
-If you don't get a "command not found" message, subversion is installed. Otherwise, you need to install it. - -The next step is to go to where you want to export the contents of the repository (eg.: "cd /var/www/vhosts/example.com/httpdocs" or "cd /home/sitename/public_html"). - -The command looks like this:
svn export repositoryUrl repositoryUrl
- where: - -Examples: -
svn export http://v1.dotkernel.net/svn/trunk ./ --force
-svn export -r 423 http://v1.dotkernel.net/svn/trunk ./ --force
-svn export http://v1.dotkernel.net/svn/trunk /var/www/vhosts/domain.com/httpdocs/dk
-For more information, you can run svn help export. - -If you've exported the repository using a different user (root for example), you can change the permissions back by running the following command as root: -
chown -R siteuser.psacln /var/www/vhosts/example.com/httpdocs
- -

Frequently Asked Questions

- -
-
- How do you check if Subversion is installed on the host? + -
-

Run svn --version. If you don't get a "command not found" message, Subversion is installed; otherwise, you need to install it.

-
-
-
- What is the basic command to export a repository? + -
-

The command is svn export repositoryUrl targetDirectory, run from the host where you want to export the repository, ideally using the domain's user rather than root.

-
-
-
- What does the -r option do? + -
-

-r revisionNumber is optional and exports a specific revision; by default, the latest revision is used.

-
-
-
- What does the --force option do, and what is the risk? + -
-

By default SVN will not export into an existing directory; --force overrides this. Be careful, since this option can overwrite files.

-
-
-
- How do you fix file permissions if you exported the repository as a different user? + -
-

As root, run chown -R siteuser.psacln /var/www/vhosts/example.com/httpdocs to change the permissions back.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig b/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig deleted file mode 100644 index 14a57104..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/svn-keywords-setup-in-php-ide-zend-studio.html.twig +++ /dev/null @@ -1,79 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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 - -
Name: svn:ignore
-Propriety:
-*.project
-*.prefs
-.project
-cache
-.settings
-.buildpath
-*.ini
- -
Name: bugtracq:label
-Propriety: Tracker ID:
-
Name: bugtraq:message
-Propriety: [Tracker ID: #%BUGID%]
- -
Name: bugtraq:url
-Propriety: http://www.dotkernel.net/view.php?id=%BUGID%
-For above Proprieties , apply only to project folder, NOT recursive -

Final step( below instructions are good only for svn:keywords )

-  - -Check the Apply property recursively to: -Select All resources -Check the Use filtration by the resource name and add Mask: *.php - -svn-add - -

Frequently Asked Questions

- -
-
- Why set these SVN properties on each project? + -
-

They provide better integration of SVN, your PHP IDE (Zend Studio), and a bug tracker of choice, and must be set for each project you have.

-
-
-
- What does the svn:ignore property do here? + -
-

It tells SVN to ignore local settings files such as *.project, *.prefs, .project, cache, .settings, .buildpath, and *.ini, since you don't want to commit your local settings to the main repository.

-
-
-
- How do you set up basic bug tracker integration? + -
-

Set the bugtracq:label property to "Tracker ID:" and bugtraq:message; if you have a public bug tracker such as Mantis, also set bugtraq:url to a URL pattern like http://www.dotkernel.net/view.php?id=%BUGID%.

-
-
-
- Should these properties be applied recursively? + -
-

No. For the properties above, apply them only to the project folder, not recursively.

-
-
-
- How is the svn:keywords property applied differently? + -
-

Check "Apply property recursively to:", select "All resources", then check "Use filtration by the resource name" and add the mask *.php.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/using-like-wildcards-with-zend-db.html.twig b/src/Blog/templates/page/blog-resource/best-practice/using-like-wildcards-with-zend-db.html.twig deleted file mode 100644 index ca831424..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/using-like-wildcards-with-zend-db.html.twig +++ /dev/null @@ -1,161 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. This allows pattern matching. It can be used in any valid SQL statement (SELECT, INSERT, UPDATE or DELETE). - -LIKE wildcards: - - - -Note*: -
$db = Zend_Db::factory('Pdo_Mysql', $dbConnect);
-LIKE _ - -
SELECT * FROM `table` WHERE (`id` LIKE '1_' )
-
$col = $this->db->quoteIdentifier('id');
-$where = $this->db->quoteInto("$col LIKE ? ", '1_');
-$select = $this->db->select()
-    ->from('table')
-    ->where($where);
-$result = $this->db->fetchAll($select);
- -
-SELECT * FROM `table` WHERE (`name` LIKE 'Fr_d' )
-
-
-$col = $this->db->quoteIdentifier('name');
-$where = $this->db->quoteInto("$col LIKE ? ", 'Fr_d');
-$select = $this->db->select()
-    ->from('table')
-    ->where($where);
-$result = $this->db->fetchAll($select);
-
-
-
-LIKE % - -
-SELECT * FROM `table` WHERE (`source` LIKE '%gallery%' )
-
-
-$col = $this->db->quoteIdentifier('source');
-$where = $this->db->quoteInto("$col LIKE ? ", '%gallery%');
-$select = $this->db->select()
-    ->from('table')
-    ->where($where);
-$result = $this->db->fetchAll($select);
-
- -
-SELECT * FROM `table` WHERE (`source` LIKE '%gallery%' OR `source` LIKE ('%folder%') )
-
-
-$col = $this->db->quoteIdentifier('source');
-$where = $this->db->quoteInto("$col LIKE ? ", '%gallery%');
-$where .= $this->db->quoteInto("OR $col LIKE (?) ", '%folder%');
-$select = $this->db->select()
-    ->from('table')
-    ->where($where);
-$result = $this->db->fetchAll($select);
-
-
-
-NOT LIKE _ - -
-SELECT * FROM `table` WHERE (`id` NOT LIKE '1_' )
-
-
-$col = $this->db->quoteIdentifier('id');
-$where = $this->db->quoteInto("$col NOT LIKE ? ", '1_');
-$select = $this->db->select()
-    ->from('table')
-    ->where($where);
-$result = $this->db->fetchAll($select);
-
-
-
-NOT LIKE % - -
-SELECT * FROM `table` WHERE (`source` NOT LIKE ('%gallery%') AND `source` NOT LIKE ('%folder%') AND `source` NOT LIKE ('%file%') )
-
-
-$col = $this->db->quoteIdentifier('source');
-$where = $this->db->quoteInto("$col NOT LIKE (?) ", '%gallery%');
-$where .= $this->db->quoteInto("AND $col NOT LIKE (?) ", '%folder%');
-$where .= $this->db->quoteInto("AND $col NOT LIKE (?) ", '%file%');
-$select = $this->db->select()
-    ->from('table')
-    ->where($where);
-$result = $this->db->fetchAll($select);
-
-
-
-OTHER Example -
SELECT * FROM `table` WHERE `number` LIKE '_6%' 
-
$col = $this->db->quoteIdentifier('number');
-$where = $this->db->quoteInto("$col LIKE ? ", '_6%');
-$select = $this->db->select()
-    ->from('table')
-    ->where($where);
-$result = $this->db->fetchAll($select);
- - -

Frequently Asked Questions

- -
-
- What do the LIKE wildcards _ and % mean? + -
-

The _ wildcard matches a single character, while % matches any string of any length, including zero length.

-
-
-
- Which SQL statements can use the LIKE condition? + -
-

LIKE allows pattern matching in the WHERE clause and can be used in any valid SQL statement: SELECT, INSERT, UPDATE, or DELETE.

-
-
-
- How do you build a LIKE query with Zend_Db? + -
-

Quote the column with $this->db->quoteIdentifier(), build the condition with $this->db->quoteInto("$col LIKE ? ", $pattern), and pass the resulting $where string into ->where() on a select, then run it with $this->db->fetchAll($select).

-
-
-
- How do you combine multiple LIKE conditions with OR? + -
-

Build the first condition with quoteInto, then append further ones with quoteInto("OR $col LIKE (?) ", $pattern), as in the example matching 'gallery' or 'folder' in the source field.

-
-
-
- How does NOT LIKE differ from LIKE? + -
-

NOT LIKE negates the pattern match — for example, `id` NOT LIKE '1_' returns ids that don't start with 1 or don't have exactly 2 digits, and NOT LIKE conditions can be chained with AND to exclude several patterns at once.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/what-are-returning-the-fetch-functions-from-zend-db.html.twig b/src/Blog/templates/page/blog-resource/best-practice/what-are-returning-the-fetch-functions-from-zend-db.html.twig deleted file mode 100644 index 508d22b9..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/what-are-returning-the-fetch-functions-from-zend-db.html.twig +++ /dev/null @@ -1,154 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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, [mixed $bind = array()])
-array fetchCol (string|Zend_Db_Select $sql, [mixed $bind = array()])
-string fetchOne (string|Zend_Db_Select $sql, [mixed $bind = array()])
-array fetchPairs (string|Zend_Db_Select $sql, [mixed $bind = array()])
-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. -Initialize the connection to our MySql database: -
-$db = Zend_Db::factory('Pdo_Mysql', $dbConnect);
-
-Here is a SQL query, that we want to fetch: -
-
-$sql = "SELECT id, title FROM files";
-$db->query($sql)
-
-
-
-$select = $db->select()
-             ->from('files', array('id', 'title'))
-
-Note*: for the old style of fetching we used an old class. What you need to know is:
-- query() method is similar with mysqli_query() from Mysqli PHP extension
-- next_record() method is similar with mysqli_next_result() from Mysqli PHP extension
-- f() method retrieve the value of the column specified as parameter -
-fetchAll -
-
-while($db->next_record())
-{
-    $a[] = array(
-                 'id' => $db->f('id'),
-                 'title' => $db->f('title')
-                 );
-}
-
-
-
-$a = $db->fetchAll($select);
-
-
-fetchAssoc -
-
-while($db->next_record())
-{
-    $a[$db->f('id')] = array(
-                             'id' => $db->f('id'),
-                             'title' => $db->f('title')
-                            );
-}
-
-
-
-$a = $db->fetchAssoc($select);
-
-
-fetchCol -
-
-while($db->next_record())
-{
-    $a[] = $db->f('id');
-}
-
-
-
-$a = $db->fetchCol($select);
-
-
-fetchOne -
-
-$db->next_record();
-$a = $db->f('id');
-
-
-
-$a = $db->fetchOne($select);
-
-
-fetchPairs -
-
-while($db->next_record())
-{
-    $a[$db->f('id')] = $db->f('title');
-}
-
-
-
-$a = $db->fetchPairs($select);
-
-
-fetchRow -
-
-$db->next_record();
-$a = array(
-           'id' => $db->f('id'),
-           'title' => $db->f('title')
-          );
-
-
-
-$a = $db->fetchRow($select);
-
- -

Frequently Asked Questions

- -
-
- What FETCH methods are available in Zend_Db_Adapter_Abstract? + -
-

The article covers fetchAll, fetchAssoc, fetchCol, fetchOne, fetchPairs, and fetchRow.

-
-
-
- What does fetchAll do compared to the old query style? + -
-

$a = $db->fetchAll($select) replaces the old-style loop that calls next_record() repeatedly and builds an array of associative rows using f() for each column.

-
-
-
- What does fetchRow return? + -
-

$a = $db->fetchRow($select) returns a single row as an associative array, replacing a single next_record() call followed by f() calls for each column.

-
-
-
- What does fetchOne return? + -
-

$a = $db->fetchOne($select) returns a single value, replacing a single next_record() call followed by one f() call.

-
-
-
- How do the old-style query(), next_record(), and f() methods relate to Mysqli? + -
-

query() is similar to mysqli_query(), next_record() is similar to mysqli_next_result(), and f() retrieves the value of the column specified as a parameter.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.html.twig b/src/Blog/templates/page/blog-resource/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.html.twig deleted file mode 100644 index bf33eca3..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/why-use-current-timestamp-on-a-field-that-record-date-time.html.twig +++ /dev/null @@ -1,57 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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 NULL DEFAULT CURRENT_TIMESTAMP;
-CURRENT_TIMESTAMP is also a solution for  updating date and time fields. Use ON UPDATE CURRENT_TIMESTAMP clause, if you want the value of the field to be changed automatically each time the row is updated. -
ALTER TABLE `user` CHANGE `dateLogin` `dateLogin` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
-DEFAULT and ON UPDATE clauses can be used together or separately, depending on your needs: - -For more details check out MySQL Manual - -Note*: Only one timestamp field can be DEFAULT CURRENT_TIMESTAMP in a table. - -

Frequently Asked Questions

- -
-
- Why use CURRENT_TIMESTAMP as a DEFAULT value for a date/time field? + -
-

Because when inserting a new row, there is no need to specifically set the date/time value yourself, either from PHP Date/Time functions or with MySQL's NOW() function.

-
-
-
- How do you make a field update its timestamp automatically on every UPDATE? + -
-

Add the ON UPDATE CURRENT_TIMESTAMP clause, for example: ALTER TABLE `user` CHANGE `dateLogin` `dateLogin` TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP.

-
-
-
- What happens if a TIMESTAMP column has neither a DEFAULT nor an ON UPDATE clause? + -
-

For the first TIMESTAMP field in the table, having neither clause is the same as DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP.

-
-
-
- What happens with a DEFAULT CURRENT_TIMESTAMP clause but no ON UPDATE clause? + -
-

The column gets the current timestamp as its default value but is not automatically updated afterward.

-
-
-
- Can more than one TIMESTAMP column default to CURRENT_TIMESTAMP in the same table? + -
-

No. Only one timestamp field in a table can be DEFAULT CURRENT_TIMESTAMP.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.html.twig b/src/Blog/templates/page/blog-resource/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.html.twig deleted file mode 100644 index c3c303b8..00000000 --- a/src/Blog/templates/page/blog-resource/best-practice/zf-is-retired-laminas-mvc-is-retiring-consider-it-solved.html.twig +++ /dev/null @@ -1,92 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

A Bit of History

- -

Laminas MVC is not even the first framework that has reached its end of life - look at Zend Framework and Apigility. Letting go of a flagship product is a difficult decision, but it's made easier when you leave a solid alternative in its wake. The developers who worked on Laminas MVC already had something better and fully-functional in place - Mezzio microframework, built using Laminas components. It has itself gone through rigorous development and testing since being released in 2015 when it was known as Zend Expressive, then was renamed into Mezzio to get to its current state.

- -

What Is the Issue with Legacy Platforms?

- -

Maintaining legacy platforms over the long term is often a costly and time-consuming endeavour. Every few years, platform owners must consider the viablity of migrating to a newer platform.

- -

Newer platforms implement modern architectures, have an active community and are actively being developed and maintained. They also offer easier development, expansion and maintenance, alongside vital security improvements and more reliable dependencies.

- -

Sounds like an easy decision? Sure, but it's a lot of work... And that's when the specialists come into play.

- -

We at Apidemia have been using the Zend Framework, Laminas MVC and Mezzio for years. We understand their ins-and-outs intimately, which enables us to analyze and to perform the transfer of a legacy platform to Mezzio effectively. Working with Mezzio ensures faster execution times, increased security, faster development and long-term reliability from all points of view. We encourage this change and are ready to offer guidance.

- -

Pain Points

- -

The MVC architecture is obsolete. It is yesterday's architecture, fit for monolithic websites. The architecture of today and tomorrow is based on middleware, building headless platforms, websites and microservices following the same coding approach.

- -
Pain PointApidemia Solution
Legacy framework is deprecated and/or has no long-term supportApidemia helps migrate to modern middleware architecture (Mezzio microframework with Laminas components)
Legacy applications are hard to maintainModern architecture improves code quality, testability and performance
Migration is risky or expensiveApidemia uses a proven, phased migration strategy to reduce risk
Lack of internal development expertiseApidemia provides end-to-end guidance, refactoring, training and support
- -

How Apidemia handles migrations

- -

Apidemia have created a complex process that involves several steps to ensure a smooth migration. In a nutshell, the current project functionality must be understood and only then can the move be implemented into the destination platform. Over the long run, the Apidemia team offers support and training.

- -

This is the simplified task list:

- - - -

Frequently Asked Questions

- -
-
- What is Laminas MVC being replaced by? + -
-

Mezzio microframework, built using Laminas components. It has gone through rigorous development and testing since being released in 2015, when it was known as Zend Expressive, before being renamed Mezzio.

-
-
-
- Why is maintaining legacy platforms a problem? + -
-

Maintaining legacy platforms over the long term is often costly and time-consuming, so every few years platform owners must consider migrating to a newer platform that offers modern architecture, an active community, easier development/expansion/maintenance, security improvements, and more reliable dependencies.

-
-
-
- What is the core architectural pain point with legacy MVC platforms? + -
-

The MVC architecture is obsolete, fit for monolithic websites. Today's and tomorrow's architecture is based on middleware, building headless platforms, websites, and microservices following the same coding approach.

-
-
-
- What steps does Apidemia's migration process involve? + -
-

A simplified task list: code audit & migration strategy; partial or full migration to Laminas or PSR-compliant frameworks like Mezzio or Symfony; refactoring and decoupling legacy modules; unit testing and CI/CD pipeline setup; and post-migration support and team training.

-
-
-
- Who offers this migration guidance? + -
-

Apidemia, who has used Zend Framework, Laminas MVC, and Mezzio for years and can analyze and perform the transfer of a legacy platform to Mezzio, offering faster execution times, increased security, faster development, and long-term reliability.

-
-
-
- -

Additional resources

- - - -

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/design-pattern/naming-pattern-for-psr-15-handlers-in-dotkernel-applications.html.twig b/src/Blog/templates/page/blog-resource/design-pattern/naming-pattern-for-psr-15-handlers-in-dotkernel-applications.html.twig deleted file mode 100644 index b1cf49d0..00000000 --- a/src/Blog/templates/page/blog-resource/design-pattern/naming-pattern-for-psr-15-handlers-in-dotkernel-applications.html.twig +++ /dev/null @@ -1,57 +0,0 @@ -{% extends '@layout/default.html.twig' %} - -{% block body %} -
-

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. Each developer will use slight variations when it comes to naming files, which is not going to be consistent, sometimes not even with work he has done on the same project in the previous month.

-

It might seem ok to use the file names 'CreateUser.php' as much as 'AddUser.php' or 'InsertUser.php', since they perform the same action. But consider that you might have 'CreateUser.php', 'AddProduct.php' and 'InsertOrder.php' in the same application. It's still readable, but chaotic. We will show you a better way to handle file naming, using naming patterns.

-

What is a naming pattern?

-

A naming pattern helps you organize and quickly identify your files by using relevant strings in file names like:

- -

Naming patterns can be defined for different types of files, since image files might need other information than php files or simple txt files used for logs.

-

Here is a list of items to consider:

- -

After defining your naming pattern, the most import item by far is to communicate the pattern to the team. A top-level README file with the documentation should be kept handy for any developer who creates new files.

-

The naming pattern for Dotkernel Handlers

-

HTTP request handlers are at the core of any web application. They receive a request, process it and return a response.

-

Even the first paragraph above mentions several elements that are relevant. The naming pattern for our Handlers contains:

- -

In this way, the developer can easily figure out what each handler does, just by looking at the name.

-

We have chosen this wording for the performed actions (or CRUD):

- -

The image below contains the full list of handlers used in Dotkernel Admin.

-
Dotkernel Naming Convention
-

A practical example

-

Let's assume your application requires you to create products managed by admin users. So how do you go about naming a new set of files for this purpose?

-

First, you need a form to fill out. The form is returned by a handler called with get. Its purpose is to perform a create action for an product, hence you name it: GetProductCreateFormHandler.

-

Then you send the form data to be saved as a new product. The second handler will create the new product account via post, so you get: PostProductCreateHandler.

-

You will likely need to update and delete products further down the line, so you create the handlers: GetProductEditFormHandler, PostProductEditHandler and PostProductDeleteHandler. The final handler that makes sense in this group is GetProductListHandler that will list all the products you have created, with filtering included.

-

It takes only a minute to build the proper name for each handler, which takes you and other team members no more than a second to figure out what it does. You will thank yourself in the future.

-

Additional resources

-

PSR-15

-

Dotkernel Application Repositories

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/api-client-migration-from-postman-to-bruno.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/api-client-migration-from-postman-to-bruno.html.twig deleted file mode 100644 index 0e12af91..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/api-client-migration-from-postman-to-bruno.html.twig +++ /dev/null @@ -1,138 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

Over the years we have been using Postman as our go-to API tester, but recently we have considered shifting to Bruno, a lightweight alternative. We are not the first to consider this change. There is a general trend in the PHP community toward local-first, Git-native developer tools.

- -

Comparing Postman to Bruno

- -
- -

Architecture

- -

Primarily, we use collections to save our API endpoints and then share them among our developers to streamline testing. Postman has recently decided to only allow one user account in their free plan. In some cases, it might be enough, but this is one reason why Postman is less reliable right now.

- -

Bruno comes with a different approach: a fully-offline experience based on shared .bru files. Being that it's offline means that there is no restriction on the number of developers using Bruno's files, so it's an advantage for the way we do things.

- -

Version Control

- -

Postman focuses on storing collections and handling version control in the cloud, forcing developers to always be online. You can still export/import collections via their UI.

- -

Bruno's .bru files can be saved in the Git repository and easily accessed by all members of the dev team. Version control for Bruno files is handled entirely via Git, just like for any other file on your project.

- -

Feature Scope

- -

A major difference between Postman and Bruno is the feature scope. Where Postman offers a complete platform for the API lifecycle, Bruno focuses mainly on interacting with the API, writing simple tests, and building local collections.

- -

Postman is the definite winner in this department, if you also use features like mocking, documentation, and CI/CD integration. Given that we use different tool for these additional features, Postman doesn't really benefit us compared to Bruno.

- -

Performance

- -

Bruno is the clear winner here, given that it uses much less RAM and is generally faster.

- -

Postman needs to regularly synchronize with the cloud and store its advanced features in the RAM, which can introduce delays in some cases.

- -

Security

- -

Postman offers Single Sign-On (SSO) and Role-Based Access Control (RBAC), but we find these not to be useful for our workflow.

- -

Bruno's local files do offer a distinct advantage in that these files never leave your dev environment. It can be argued that it's more secure this way, especially since we try to not share our client's files with any online tool if we can help it.

- -

Working with Bruno Collections

- -

One of the most important functions that Postman has limited right now is the ability to share collection with dev team made up of multiple members. Bruno offers several options, like saving a collection to a Git, to a .zip file or a single .yaml file. By far the best option is sharing via Git which allows a single or multiple developers to quickly sync between multiple work locations, to track change history, and to easily publish their work for public consumption.

- -

We intend to create a separate Git repository within each project for the Bruno files to streamline the sharing process. For each new proejct, we normally allocate multiple developers from the get-to: one for frontend, another for backend. But the teams often grow as the project becomes more complex. Developers may be reallocated, so being able to swiftly onboard a new member to the team is vital.

- -

Comparison Conclusion

- -

Postman is currently a better fit for larger teams that are willing to allocate a budget for their more feature-rich platform. Bruno stores collections in Git, so everything is offline, and thus as far as we are concerned it's more secure, while also being generally faster.

- -

Alternative API clients

- -

Bruno is only one of the alternatives to Postman. Let's see some of the other API clients available on the market right now:

- - - -

As far as we are concerned, any one of them can get the job done. The decision comes down to choosing a simple, reliable tool we can use for the forseeable future.

- -

Bruno for Dotkernel

- -

At the moment, Bruno seems to be the best match for us. It offers similar functionality to Postman, with the added benefits of:

- - - -

The similar functionality is to be expected, since it's an API client, first and foremost. The offline feature is perhaps what weighed the most in our decision in favor of Bruno.

- -

Tool Migration

- -

Most of us have only worked with Postman, so switching to another tool can impact efficiency, at least at first. In general, Tool Migration can have an emotional impact on developers who use a tool, because they have to learn the new tool's ins and outs before getting back to the real work.

- -

Given Bruno's straightforward approach and reasonable learning curve, this should be mitigated easily within our company. In fact, we see the switch as an expansion of our expertise. We thus prevent getting tied up to one tool, something similar to vendor lock-in for code.

- -

How Long Will Bruno Last?

- -

We fully expect Bruno to eventually restrict developers with paid plans, just like Postman did, but we'll cross that bridge when we get to it. Hopefully, we won't have to develop our own API client (fingers crossed). For now, Bruno becomes our de facto API client and will encourage our whole team to adopt it as soon as possible.

- -

Frequently Asked Questions

- -
-
- Why is the team considering a switch from Postman to Bruno? + -
-

They want a reliable API testing client that is free, stores and shares endpoint collections easily with the team, and is fast and secure. This reflects a broader trend in the PHP community toward local-first, Git-native developer tools.

-
-
-
- What is the main architectural difference between Postman and Bruno? + -
-

Postman's free plan now only allows one user account, while Bruno offers a fully-offline experience based on shared .bru files, so there is no restriction on the number of developers using them.

-
-
-
- How does version control differ between the two tools? + -
-

Postman stores collections and handles version control in the cloud, forcing developers to stay online (though collections can be exported/imported via its UI). Bruno's .bru files can be saved directly in a Git repository and are version-controlled through Git like any other project file.

-
-
-
- How does performance compare between Postman and Bruno? + -
-

Bruno is the clear winner on performance: it uses much less RAM and is generally faster. Postman needs to regularly synchronize with the cloud and store its advanced features in RAM, which can introduce delays.

-
-
-
- Is Bruno more secure than Postman? + -
-

Postman offers Single Sign-On (SSO) and Role-Based Access Control (RBAC), which the team doesn't find useful for its workflow. Bruno's local files never leave the dev environment, which the article argues makes it more secure, especially for avoiding sharing client files with online tools.

-
-
-
- What's the overall conclusion on Postman versus Bruno? + -
-

Postman is currently a better fit for larger teams willing to allocate a budget for a more feature-rich platform. Bruno stores collections in Git so everything works offline, which the team considers more secure while also being generally faster, and it has become their de facto API client.

-
-
-
- -

Additional Resources

- -

Bruno homepage

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/api-endpoint-to-collect-client-errors.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/api-endpoint-to-collect-client-errors.html.twig deleted file mode 100644 index eee3d56b..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/api-endpoint-to-collect-client-errors.html.twig +++ /dev/null @@ -1,44 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

API Endpoint to Collect Client Errors

- -

Let's say you have a (Client) Frontend (e.g. Angular) over a Dotkernel API and there may be cases when there are errors, eighter the API changed it's response(s) over night or just a simple variable being undefined for some reason.

- -

Since in our case Frontend is running on user client there is so little to do but we've come with an ideea for "writing down" any inconveniences.

- -

We have created an endpoint where Clients can submit the error message when things are going down hill.

- -

A simple POST to your Dotkernel API on route: https://api.dotkernel.net/error-report

- -

With body:

- -
{
-    "message": "My awesome error!!!"
-}
- -

Note: The error message will be stored by default in /log/error-report-endpoint-log.log, a separate log for Client and the message will be saved with a timestamp.

- -

Frequently Asked Questions

- -
-
- Why was this endpoint created? + -
-

When a Frontend client (e.g. Angular) running on the user's machine hits an error against the Dotkernel API - whether from an overnight API response change or a simple undefined variable - there is little that can be done from the client side, so this endpoint lets clients "write down" the error instead.

-
-
-
- How do I submit an error from the client? + -
-

Send a simple POST request to your Dotkernel API's https://api.dotkernel.net/error-report route, with a body such as { "message": "My awesome error!!!" }.

-
-
-
- Where is the submitted error message stored? + -
-

By default, it is stored in a separate log file for Client, /log/error-report-endpoint-log.log, with the message saved alongside a timestamp.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig deleted file mode 100644 index f84d42bd..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/content-negotiation-in-dotkernel-rest-api.html.twig +++ /dev/null @@ -1,149 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

- -

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.

- -

What is the purpose of Content Negotiation?

- -

RESTful resources can support multiple representations. Each team of developers implements one of more ways (e.g. data formats) to receive requests and return responses on the server side. Efficient communication between client and server can only be guaranteed if the both sides agree on how the exchange takes place. This is especially valid if the request and response support multiple formats. The act of agreeing on a way to represent the exchanged data format is content negotiation.

- -

Content negotiation ensures the following:

- - - -

Who decides the data format?

- -

There are two sides to the exchange:

- - - -

Technically, either side can decide on how the data is transferred between the two.

- -

For server-side negotiation the server must decide based on various factors what the most appropriate format should be. This incurs assumptions that can be erroneous and the server-side implementation can also be more complex. This forces the client to adhere to the rules set up on the server-side.

- -

For client-side negotiation the client lets the server know what format it prefers. This approach is more versatile and thus makes more sense.

- -

There are two ways to communicate the data format:

- - - -

HTTP Request Headers

- -

The HTTP request headers Content-Type and Accept are used to determine the data format that will be sent in the request and the response. There are several types to choose from. Here are some examples:

- - - -

Below is an example of what the keys look like in the content package. Note the Accept type

- -
Content-Type: application/json, text/plain
-Accept: application/json
- -

If the Accept header is not present, the server gets to decide the format of the response.

- -

Content Negotiation using URL Patterns

- -

Below are a couple of ways to communicate a preferred data format.

- -

Via the extension on the URL:

- -
https://www.example-api.com/record/47.xml
-https://www.example-api.com/record/47.json
- -

or via an extra parameter:

- -
https://www.example-api.com/record/47?format=xml
-https://www.example-api.com/record/47?format=json
- -

Defining preferences via a quality factor

- -

The Accept header may hold multiple values with an added value that defines preference or priority.

- -

In this example, the client declares it accpets both json and xml formats, with json being preferred over xml, as defined by the numeric value in q which can be between 0 and 1. If the server can only satisfy the xml format, it will respond with that. The final alternative is if the server can't respond with either json, or xml, so it responds with what it can.

- -
Accept: application/json,application/xml;q=0.9,*/*;q=0.8
- -

How does Dotkernel API handle Content Negotiation?

- -

Out of the box, Dotkernel API uses HTTP request headers Content-Type and Accept to handle client-side content negotiation. It has both application/json, application/hal+json included. Of course, you can change these as development progresses for your project. There is also support for per-route content negotiation, if you should need it.

- -

The configuration is done is its own configuration file. The validation is automatic and several explicit errors are handled, based on what format is supported.

- -

Check out the relevant links below for exact details on the Dotkernel implementation of content negotiation.

- -

Frequently Asked Questions

- -
-
- What is content negotiation? + -
-

It's the act of a client and server agreeing on the format and language of the data they exchange, which is important for RESTful APIs since resources can support multiple representations.

-
-
-
- What does content negotiation ensure? + -
-

It ensures support for diverse clients (e.g. Accept: application/json or Accept: application/xml), data format flexibility for smaller responses (e.g. Accept: application/msgpack, a binary serialization), and language localization via headers like Accept-Language: en-US.

-
-
-
- Who decides the data format, the client or the server? + -
-

Either side technically can. In server-side negotiation, the server decides based on various factors, which can introduce erroneous assumptions and more complex implementation, forcing the client to adhere to server rules. In client-side negotiation, the client tells the server what format it prefers, which is more versatile and makes more sense.

-
-
-
- How can the preferred data format be communicated? + -
-

Via HTTP request headers (Content-Type and Accept) or via resource URI patterns, such as a file extension in the URL (e.g. /record/47.json) or an extra query parameter (e.g. /record/47?format=json). If the Accept header is not present, the server decides the response format.

-
-
-
- How does the quality factor (q) work in the Accept header? + -
-

The Accept header can list multiple accepted formats with a q value between 0 and 1 to express preference, e.g. Accept: application/json,application/xml;q=0.9,*/*;q=0.8. The server responds with the most preferred format it can satisfy, falling back further down the list if needed.

-
-
-
- How does Dotkernel API handle content negotiation? + -
-

Out of the box, Dotkernel API uses the Content-Type and Accept HTTP request headers to handle client-side content negotiation, supporting both application/json and application/hal+json. These can be changed as needed, and per-route content negotiation is also supported.

-
-
-
- -

Relevant Links

- -

Content Negotiation in Dotkernel API

- -

Content types on iana.org

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig deleted file mode 100644 index e19526a2..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-1-0-0-released.html.twig +++ /dev/null @@ -1,61 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

-
- -

Dotkernel API 1.0.0 was just released!

-

 

-

What is Dotkernel API?

-

It is a Zend Expressive 3 application aiming to help developers quickly and efficiently develop an API.

-

 

-

How does it work?

-

Under the hood it uses the following libraries:

- -

 

-

What does it offer?

-

Out-of-the-box, Dotkernel API provides the following features:

- - -

Frequently Asked Questions

- -
-
- What is Dotkernel API? + -
-

It is a Zend Expressive 3 application aiming to help developers quickly and efficiently develop an API.

-
-
-
- What key libraries does Dotkernel API 1.0.0 use? + -
-

Among others, it's built on the ezimuel/zend-expressive-api skeleton, and uses dotkernel/dot-annotated-services for dependency injection, dotkernel/dot-console for console applications, dotkernel/dot-errorhandler for error logging, dotkernel/dot-mail for SMTP email, zend-expressive-authentication-oauth2 for OAuth2 authentication, zend-expressive-authorization-rbac for role-based permissions, and swagger-api/swagger-ui for OpenAPI 3 documentation.

-
-
-
- What features does Dotkernel API 1.0.0 offer out of the box? + -
-

Secure authentication via OAuth2, two user roles (admin and member), where admins can manage any user account and members can manage only their own, plus OpenAPI 3 documentation with an interactive interface developers can use to integrate the API.

-
-
-
-

 

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig deleted file mode 100644 index b4222e95..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-client-side-authorization.html.twig +++ /dev/null @@ -1,54 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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": "...",
-"refresh_token": "..."
-}
-
- -When sending API requests to an endpoint which requires authorization, an Authorization header must be present containing the following data: "Bearer {access_token}", where {access_token} represents the content of the key with the same name found in the above response. - -

Frequently Asked Questions

- -
-
- What does a client send to request authorization? + -
-

The client application sends a POST request to the backend with a JSON object containing grant_type (set to "password"), client_id, client_secret, scope, username/email, and password.

-
-
-
- What does the API return when authorization succeeds? + -
-

If the credentials are correct, the API returns a JSON object containing token_type ("Bearer"), expires_in (86400 seconds), an access_token, and a refresh_token.

-
-
-
- How do I use the access token in subsequent requests? + -
-

When sending API requests to an endpoint that requires authorization, include an Authorization header containing "Bearer {access_token}", where {access_token} is the value returned in the authorization response.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig deleted file mode 100644 index a9a71e57..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-server-side-authorization.html.twig +++ /dev/null @@ -1,95 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -This article covers the basic authorization of a Server Side application  built using Dotkernel API -

Protecting an endpoint

- -Configuring access to the endpoints is done by editing the following config file: - - config/autoload/authorization.local.php -
NOTE -If this file is missing from your application, locate it's dist file: config/autoload/authorization.local.php.dist -and copy-paste it as the above-mentioned config/autoload/authorization.local.php
-You should look for the array inside this config key: zend-expressive-authorization-rbac -
'zend-expressive-authorization-rbac' => ,
-        'member' => ,
-        'guest'  => ,
-    ],
-    'permissions' => ,
-    ],
-]
-
-Under the key roles you can define role inheritance. In the above example - -Of course, this setup is just a model, you should not use it in live projects because guests will end up having the same rights as admins. - -Under the key permissions you can define which routes are accessible to a role. In the above example, a member has access to the routes named avatar, users and user. -

1. No-auth endpoints:

-These endpoints can be accessed without authentication/authorization. Examples could be: login, register, contact etc... -Creating a route for such an endpoint will use only the handler(s) responsible for returning the content: - -
-$app->get('/users', UserHandler::class, 'users');
-
-

2. Endpoints requiring Authentication:

-These endpoints can be accessed only if a valid Bearer token is present in the request headers. Else, the API will return a **401 Unauthorized** response. Creating a route for such an endpoint will have a structure similar to the following example: - -
-$app->get('/users', , 'users');
-
-

3. Endpoints requiring Authorization:

-These endpoints can be accessed only if a valid Bearer token is present in the request headers. Else, the API will return a **403 Forbidden** response. Creating a route for such an endpoint will have a structure similar to the following example: - -
-$app->get('/users', , 'users');
-
- -

Frequently Asked Questions

- -
-
- What are the three access levels for protecting an endpoint? + -
-

no-auth, where the resource can be accessed without authentication/authorization; authentication, where only authenticated users can access the resource; and authorization, where only authenticated AND authorized users can access it.

-
-
-
- Where do I configure access to the endpoints? + -
-

In config/autoload/authorization.local.php. If that file is missing from your application, locate its dist file config/autoload/authorization.local.php.dist and copy it as config/autoload/authorization.local.php, then look for the array under the zend-expressive-authorization-rbac config key.

-
-
-
- How does role inheritance work under the roles key? + -
-

In the article's example, admin inherits from no other role, member inherits from admin, and guest inherits from member. The article warns this exact setup is just a model and should not be used in live projects, because guests would end up having the same rights as admins.

-
-
-
- How do I control which routes a role can access? + -
-

Under the permissions key you define which routes are accessible to a role. In the article's example, a member has access to the routes named avatar, users, and user.

-
-
-
- What response codes are returned for authentication and authorization endpoints? + -
-

Endpoints requiring authentication return a 401 Unauthorized response if a valid Bearer token isn't present in the request headers. Endpoints requiring authorization return a 403 Forbidden response instead under the same condition.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig deleted file mode 100644 index 7ed56d9e..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/dotkernel-api-versus-laminas-api-tools.html.twig +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

The table below refers to Dotkernel API V7

-
- -
API Tools (formerly Apigility)Dotkernel API
URLapi-toolsDotkernel API
First Release20122018
PHP Version<= 8.2PHP Version
ArchitectureMVC, Event DrivenMiddleware
OSS LifecycleArchivedOSS Lifecycle
StyleREST, RPCREST
VersioningYesDeprecations (API Evolution) *
DocumentationSwagger (Automated)Postman (Manual), OpenAPI 3.0 (Swagger)
Content-NegotiationCustomCustom
LicenseBSD-3MIT
Default DB Layerlaminas-dbdoctrine-orm 3.x
AuthorizationACLRBAC-guard
AuthenticationHTTP Basic/Digest OAuth2.0OAuth2.0
CI/CDYesYes
Unit TestsYesYes
Code (Endpoint) GeneratorYesdot-maker
PSRPSR-7PSR-7, PSR-15
- -

Note

- -
-
- -

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.html.twig deleted file mode 100644 index fc41189b..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/error-reporting-endpoint-in-dotkernel-api.html.twig +++ /dev/null @@ -1,119 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

- -

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. Still, it's not unlikely that some hidden issues remain in fringe use cases that we simply haven't explored. The occurrence of bugs increases when the API is used in a complex frontend project.

- -

Fatal errors are easily found in the API logs, but it's another matter altogether to deal with incorrect data processing that doesn't generate errors in the frontend that interfaces with the API. The error reporting endpoint was designed to allow the frontend developers of your API to report any bugs they encounter in a secure way that is fully under your control.

- -

Example case usage

- - - -

How to use it on the API side

- -

Error reporting is done by sending a POST request to the /error-report endpoint, together with a token in the header. In the sections below we will detail how to configure error reporting in your API and how the endpoint is used by the frontend developers.

- -

Generating a token and adding it to your API config

- -

First you need to generate a token for your request. This is done by using the below command.

- -
php ./bin/cli.php token:generate error-reporting
- -

The resulting token has this format 0123456789abcdef0123456789abcdef01234567.
Note: this example is provided just to let you know what to look for.

- -

Copy the generated token in your config/autoload/error-handling.global.php file. It should look similar to the example below. Your API can have multiple tokens, if needed.

- -
return ,
-        ...
-    ]
-]
- -

Validation mechanism

- -

Behind the scenes, the API validates your configuration and lets you know if any config items prevent the submission of the error report. Below are the requirements for an application to be able to send error messages to Dotkernel API.

- - - -

Note: In src/App/src/Service/ErrorReportService.php, the method checkRequest() tries to validate the request by checking matches for domain_whitelist with isMatchingDomain() and for ip_whitelist with isMatchingIpAddress().
If both return false, a ForbiddenException is thrown and the error message does not get stored.

- - - -

Note:

- - - -

If your request passes all the checks, the message is saved in the log file specified in ErrorReportServiceInterface::class . path.

- -

Tips and tricks

- -

If there are multiple applications that report errors to your API, you can assign a different error reporting token for each. The tokens support key-value pairs where:

- - - -

Example:

- -
// ...
-return ,
-    ],
-];
- -

The log file will have entries similar to the below:

- -

Demo error message

-
- -

The inclusion of the token helps you identify the source of the error message. In our example, it's the application that uses the 0123456789abcdef0123456789abcdef01234567 token, which is assigned to the application frontend.

- -

How to use it on the Frontend side (Angular example)

- -

The API developer sends a generated token to the frontend developer who will save it in their environment.staging.ts and/or environment.prod.ts. From then on, it's the frontend developer's job to set up an error reporting function similar to the one below.

- -
postError(body: object): Promise<any> {
-     return new Promise((resolve, reject) => {
-      return this.http.post(API_ENDPOINT + 'error-report', body , {headers: new HttpHeaders({'Error-Reporting-Token': 'TOKEN', 'Origin': 'https://example.com'})})).subscribe({
-        next: (response: any) => {
-          resolve(response);
-        },
-        error: (e: HttpErrorResponse) => reject(e),
-        complete: () => console.info('Error on sending error'),
-      });
-    });
-  }
- -

Whenever an error is found, the frontend will call postError() with a relevant description under message.

- -
apiService.postError({message: 'ERROR MESSAGE'})
- -

Conclusion

- -

The error reporting feature in Dotkernel API is a secured and highly configurable tool for users of your API to report any unwanted behavior. More often than not, a detailed error report will help developers understand how to replicate the issue and fix it in due course.

- -

This article is also included in the full API documentation https://docs.dotkernel.org/api-documentation/v5/core-features/error-reporting.

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig deleted file mode 100644 index 817bda1f..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/how-to-implement-mailchimp-in-dotkernel-api.html.twig +++ /dev/null @@ -1,102 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.php and paste the following content inside of it: -
<?php
-
-declare(strict_types=1);
-
-return
-];
-
-
-  - -Step 3: Create factory src/App/src/MailChimp/Factory/MailChimpFactory.php which will return an instance of DrewM\MailChimp. Paste the following content inside this file: -
<?php
-
-declare(strict_types=1);
-
-namespace Api\App\MailChimp\Factory;
-
-use DrewM\MailChimp\MailChimp;
-use Psr\Container\ContainerInterface;
-
-/**
- * Class MailChimpFactory
- * @package Api\App\MailChimp\Factory
- */
-class MailChimpFactory
-{
-    /**
-     * @param ContainerInterface $container
-     * @return MailChimp
-     * @throws \Exception
-     */
-    public function __invoke(ContainerInterface $container) : MailChimp
-    {
-        $config = $container->get('config')['mailChimp'] ?? [];
-
-        return new MailChimp($config['apiKey'] ?? '');
-    }
-}
-
-
-  - -Step 4: Let your application use this factory by adding it to the main ConfigProvider: -To do this, open file src/App/src/ConfigProvider.php and locate the method called getDependencies(). -Inside this method, locate the key factories which points to an array. Inside this array add the following line: -
MailChimp::class => MailChimpFactory::class,
-
-Make sure you you add the corresponding uses: -
use Api\App\MailChimp\Factory\MailChimpFactory;
-use DrewM\MailChimp\MailChimp;
-
-  - -After this, you can start using the library by @Injecting MailChimp::class where it's needed. - -

Frequently Asked Questions

- -
-
- Which library does this tutorial use to add MailChimp to Dotkernel API? + -
-

The tutorial uses drewm/mailchimp-api, installed with the command composer require drewm/mailchimp-api.

-
-
-
- Where do you place the MailChimp configuration file? + -
-

In config/autoload/mailchimp.global.php, a new configuration file created as part of Step 2.

-
-
-
- What does the MailChimpFactory class do? + -
-

It's a factory, created at src/App/src/MailChimp/Factory/MailChimpFactory.php, that reads the config from the container and returns an instance of DrewM\MailChimp\MailChimp.

-
-
-
- Where do you register the MailChimp factory so the application can use it? + -
-

In src/App/src/ConfigProvider.php, inside the getDependencies() method's factories array, by mapping MailChimp::class to MailChimpFactory::class, plus adding the corresponding use statements for MailChimp and MailChimpFactory.

-
-
-
- How do you use MailChimp once it's wired up? + -
-

By injecting MailChimp::class wherever it's needed, using @Inject.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig deleted file mode 100644 index 351951aa..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel-api/openapi-implementation-in-dotkernel-api.html.twig +++ /dev/null @@ -1,146 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. API specifications typically use YAML or JSON to share and use the specification. They allow users of the API to quickly discover how it works by describing its elements, e.g. endpoints, request and response formats, security mechanisms and more.

- -

While not mutually exclusive, OpenAPI has several benefits over Postman:

- - - -

Other benefits from using OpenAPI:

- - - -

The importance of API documentation

- -

API documentation, in general, is crucial for several reasons. It serves multiple stakeholders that use the API for development, integration and maintenance.

- - - -

OpenAPI in Dotkernel API

- -

Dotkernel API has full support for OpenAPI, from describing the endpoints and generating the documentation, to rendering and testing the endpoints.

- -

Each module (Admin, App, User) in Dotkernel API contains a file named OpenAPI.php. In this file you must document all of the endpoints from RoutesDelegator.php. The entries in OpenAPI.php have several descriptive items, the most important being method, request and response. These are used to generate a documentation file from the command line. The static documentation file is rendered using Swagger UI or Redoc in a user-friendly way. You can read more about this starting here and its subsequent pages.

- -

Describing OpenAPI components

- -

All OpenAPI components require a handful of components that are universally valid for a given project. These are below:

- - - -

Read more details about the above here.

- -

Once you have your basic components defined, you can begin work on the endpoints. The endpoints already made available in Dotkernel API are documented, so you must do the same for the new endpoints you create in your project. This is done by defining these items:

- - - -

Wherever it's appropriate, schemas should be used to ensure consistency. The optional 'tags' item can be used to group operations together. Read more here.

- -

Generating the documentation

- -

The documentation is generated using zircote/swagger-php. It uses the descriptions you added in the OpenAPI.php files to build the documentation file. The documentation contents can be listed in the command line or saved to a file in yaml of json format. You can read more here.

- -

Alternatives for rendering the documentation

- -

Once you have the documentation generated, it can be rendered in two ways:

- - - -

Handling authentication for Swagger UI

- -

Most endpoints for your API should be protected, so to access them you are required to generate an authentication token (AuthToken). The token is related to the user type, so make sure to check the privileges required for the endpoint you are testing. After you submit the token, you can test the endpoints as an authenticated user. Clicking on the 'Try it out' button will activate the required parameter input fields and the textarea for the request body. The 'Execute' button will send the request and return the response. along with its HTTP status code. You can read more details here.

- -

- -

Frequently Asked Questions

- -
-
- What is the OpenAPI Specification? + -
-

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, typically described in YAML or JSON so users can quickly discover endpoints, request/response formats, security mechanisms and more.

-
-
-
- How does OpenAPI compare to Postman? + -
-

OpenAPI standardizes how endpoints and request/response models are described, automatically generates machine-readable documentation, lets you define the API specification before development starts, and improves collaboration across teams. Postman, by contrast, is used mainly to test an already-completed endpoint and has no real focus on standardized documentation or upfront design.

-
-
-
- Where do you document endpoints in Dotkernel API? + -
-

Each module (Admin, App, User) contains an OpenAPI.php file, where all endpoints from that module's RoutesDelegator.php must be documented, primarily describing the method, request and response.

-
-
-
- What core components does every OpenAPI description need? + -
-

OA\Info (basic project info like version and name), OA\Server (one or more target host URLs), OA\SecurityScheme (endpoint protection), OA\ExternalDocumentation (link and description for extended docs), and OA\Schema (describing an object or collection of objects).

-
-
-
- What generates the documentation file from the OpenAPI.php descriptions? + -
-

zircote/swagger-php, which uses the descriptions added in the OpenAPI.php files to build the documentation. The result can be listed in the command line or saved to a file in YAML or JSON format.

-
-
-
- How is the generated documentation rendered, and how do you test protected endpoints? + -
-

It can be rendered with Swagger UI, which lets you visualize and interact with the API's resources, or with Redoc, which lists the documentation in read-only mode. Because most endpoints are protected, testing them in Swagger UI requires generating an authentication token (AuthToken) matching the required privileges, then using the 'Try it out' button to fill in parameters/body and 'Execute' to send the request and see the response with its HTTP status code.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig deleted file mode 100644 index c79122f3..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/adding-a-cors-implementation-to-zend-expressive.html.twig +++ /dev/null @@ -1,130 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. No 'Access-Control-Allow-Origin header is present on the requested resource."

-

It means the server didn't sent the header that lets you access its data through a local client (eg.: browser).

-

This issue is most common when trying to get some data (usually json) that you want to process using JavaScript.

-

The error looks similar to the image below:

- -
- -
- -

The solution

-

A simple implementation would be using Tuupola's Cors Middleware package.

-

This article was inspired by: akrabat.com/implementing-tuupola-cors-in-expressive

- -

Adding the package to your project

-

Run the following command in your project:

-
composer require tuupola/cors-middleware
-

At the time writing this article the current package version is: 0.9.4.

-

Follow the next steps to get your Zend Expressive or Dotkernel3 project CORS friendly.

- -

Create the CORS config file

-

Create a cors.global.php file in the config/autoload directory.

-
return [
-    'cors' => [
-        "origin" => [],
-        "methods" => [],
-        "headers.allow" => [],
-        "headers.expose" => [],
-        "credentials" => false,
-        "cache" => 0,
-    ],
-    'dependencies' => [],
-];
-

We'll come back at this file to register the CORS middleware.

- -

Creating a factory for the middleware

-

The factory should look like the one below.

-

The code below extracts de config from the cors key if provided or initializes an empty array and instantiates the Tuupola CORS middleware.

-
<?php
-
-namespace App\Cors;
-
-use Tuupola\Middleware\CorsMiddleware;
-
-class CorsMiddlewareFactory
-{
-    public function __invoke($container)
-    {
-        $corsConfig = $container->get('config')['cors'] ?? [];
-        return new CorsMiddleware($corsConfig);
-    }
-}
- -

Registering the CORS middleware

-

Back at cors.global.php we will register the cors middleware so our custom implemented factory will be used to create the middleware. (basic config example below)

-
<?php
-
-use App\Cors\CorsMiddlewareFactory;
-use Tuupola\Middleware\CorsMiddleware;
-
-return [
-    'cors' => [
-        "origin" => [],
-        "methods" => [],
-        "headers.allow" => [],
-        "headers.expose" => [],
-        "credentials" => false,
-        "cache" => 0,
-    ],
-    'dependencies' => [
-        'factories' => [
-            CorsMiddleware::class => CorsMiddlewareFactory::class,
-        ]
-    ]
-];
- -

Final step: Adding (registering) the CorsMiddleware in the pipeline

-

In this last step we only need to add the CorsMiddleware in config/pipelines.php

-
// don't forget the use statement
-use Tuupola\Middleware\CorsMiddleware;
-
-return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
-    // ...
-    $app->pipe(CorsMiddleware::class);
-    // ...
-};
-

Add the cors middleware after the Error handler and before the middleware providing the data you want to access to make sure everything runs smoothly.

-

This should get your project working with CORS.

- -

Frequently Asked Questions

- -
-
- What causes the "No 'Access-Control-Allow-Origin' header" error? + -
-

It means the server didn't send the header that lets a local client, such as a browser, access its data. This is most common when trying to fetch data (usually JSON) that you want to process using JavaScript.

-
-
-
- What package does the article use to add CORS support? + -
-

Tuupola's Cors Middleware package, installed by running composer require tuupola/cors-middleware in the project.

-
-
-
- Where does the CORS configuration live? + -
-

In a cors.global.php file created in the config/autoload directory, containing a "cors" key with settings like origin, methods, headers.allow, headers.expose, credentials, and cache.

-
-
-
- How is the CorsMiddleware wired into the container? + -
-

A CorsMiddlewareFactory extracts the "cors" config array (or an empty array if it's not provided) and instantiates Tuupola's CorsMiddleware with it. That factory is then registered under the "dependencies" > "factories" section of cors.global.php.

-
-
-
- Where should the CORS middleware be added in the pipeline? + -
-

In config/pipelines.php via $app->pipe(CorsMiddleware::class), placed after the Error handler and before the middleware that provides the data you want to access.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.html.twig deleted file mode 100644 index bd6c63f0..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/adding-a-second-caching-layer-to-wurfl-in-dotkernel-using-apc.html.twig +++ /dev/null @@ -1,56 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. We realized that its default filesystem cache was too slow for our needs, especially with a relatively high traffic application. - -If you're not familiar with how WURFL's caching works, here's a brief introduction: -
    -
  1. The device data is stored at first in a large, zipped XML file, with one entry for each device.
  2. -
  3. When first called, WURFL unzips the file and reads each device entry.
  4. -
  5. It then serializes the data and writes it to the cache, using and MD5 signature for the file name (or key name if the cache is not on the filesystem)
  6. -
  7. When a user agent is looked up, its MD5 signature is computed and then searched in the cache.
  8. -
  9. For a number of reasons, the data in the data file is stored as a tree, each device being a node and inheriting all the properties of the nodes above it. So, for each look-up, a number of files have to be read and their capabilities merged to get all the capabilities of the requested device.
  10. -
-WURFL has cache providers for APC and memcache as well, which we've tried, but the results weren't impressive. - -We realized that we had the wrong approach, especially for use. The WURFL entry for a device has lots of fields that we didn't use. - -Our solution involved adding a second cache layer, on top of WURFL's, that only cached the fields we were interested in.  This second cache layer make use of APC, storing arrays of data in  User Cache Entries. - -This small change (under 10 lines of code), decreased our response time by an order of magnitude, to 20-30ms. - -

Frequently Asked Questions

- -
-
- Why was WURFL's default caching too slow for this project? + -
-

Profiling revealed that WURFL's default filesystem cache was taking up to a few hundred milliseconds of response time, which was too slow for a relatively high-traffic application.

-
-
-
- How does WURFL's caching work by default? + -
-

Device data is stored in a large zipped XML file. On first use, WURFL unzips the file, serializes each device's data, and writes it to cache using an MD5 signature of the user agent as the key. Because devices are stored as a tree inheriting properties from parent nodes, each lookup requires reading and merging several files.

-
-
-
- Did WURFL's built-in APC or memcache cache providers solve the problem? + -
-

No. The team tried WURFL's existing cache providers for APC and memcache, but the results weren't impressive.

-
-
-
- What was the actual solution? + -
-

Adding a second cache layer on top of WURFL's own cache, using APC and storing arrays of only the specific fields they actually needed in User Cache Entries. The change was under 10 lines of code.

-
-
-
- What performance improvement did this bring? + -
-

Response time decreased by an order of magnitude, down to about 20-30ms.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig deleted file mode 100644 index f62f0566..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/adding-composer-support-in-your-dotkernel-project.html.twig +++ /dev/null @@ -1,109 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Composer is an application-level package manager. Composer auto-loads the dependencies on demand and can also auto-load custom classes . - -This article will cover the needed steps to add composer support to your Dotkernel project or even "composify" it. - -Assuming that you know how to use composer (if not you should consider reading this article) we wil move on to your Dotkernel project "composification". - -  -

First things first

-The Dotkernel project must have a composer.json file so that composer can work. - -Our composer.json file should look like this: -
{
-  "require" : {
-    "zendframework/zendframework1" : "1.12.*",
-    "mobiledetect/mobiledetectlib" : "2.8.*"
-  },
-  "require-dev" : {
-    "php" : ">=5.4.0"
-  },
-}
-Note that zend/zendframework1 may not be necessarily if you already have ZendServer running or the Zend Framework folder within /usr/share/. - -The file above makes sure: - -  - -The dependencies provided in the require section are also loaded for development purpose if not provided in require-dev. - -  - -In order to have these components instaled you must run the following command in your Dotkernel root path -
composer update
-If the vendor folder is present composer will check for updates, and update the packages as needed. - -If the vendor folder does not exist composer will create a vendor folder. This folder will contain all the requested packages. - -Composer will create an autoload file, which will be use to load our dependencies/packages. - -  -

Adding Composer Support to Dotkernel

-The autoload file created by composer will be used to load our packages. -
$composerAutoLoaderPath = realpath(APPLICATION_PATH.'/vendor/autoload.php');
-require_once($composerAutoLoaderPath);
-So far so good, but what if the file does not exist or composer is not present? - -First we must make sure the composer autoload path exists and only load the dependencies if composer autoload file was found. -
$composerAutoLoaderPath = realpath('./vendor/autoload.php');
-
-$composerEnabled = file_exists($composerAutoLoaderPath);
-
-if($composerEnabled == true)
-{
-    require_once($composerAutoLoaderPath);
-}
-else
-{
-    // handle the error gracefully
-   // or load fallbacks - if exist
-}
-The variable $composerEnabled will be true only if the composer path exists so the application behavior can be controlled if composer is not present. - -Later on, the packages can be used like this: -
use VendorName\PackageName\ClassName as MyDependency;
-
-$myDependency = new MyDependency($neededArguments);
-$myDependency->doSomething();
- -

Frequently Asked Questions

- -
-
- What does Composer do? + -
-

Composer is an application-level package manager. It auto-loads dependencies on demand and can also auto-load custom classes.

-
-
-
- What must a Dotkernel project have before Composer can be used? + -
-

A composer.json file, for example requiring zendframework/zendframework1 at 1.12.* and mobiledetect/mobiledetectlib at 2.8.*, plus PHP >=5.4.0 listed under require-dev.

-
-
-
- What happens when you run composer update? + -
-

If the vendor folder already exists, composer checks for and applies updates to the packages. If it doesn't exist, composer creates the vendor folder containing all requested packages, along with an autoload file.

-
-
-
- How do you safely load the Composer autoloader in case Composer isn't present? + -
-

Check whether vendor/autoload.php exists using file_exists() before calling require_once() on it, and handle the case gracefully (for example by loading fallbacks) if the path is missing.

-
-
-
- Which Dotkernel versions does this apply to? + -
-

The article states it works for any Dotkernel 1.x version, as long as the server is running PHP greater than 5.4.0.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.html.twig deleted file mode 100644 index de2a0fe8..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/adding-windows-10-os-and-browser-detection-in-dotkernel-projects.html.twig +++ /dev/null @@ -1,61 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Recently we have added the Windows 8, 8.1 and 10 OS icon and Microsoft's Edge browser icon. - -In this article we will have the icon upgrade guide. - -Icons PatchThe new Icons listed in User and Admin Logins - - - -  -
    -
  1. Make sure your project is running on version 1.5.0 or newer
  2. -
  3. Download the patch
  4. -
  5. Extract the archive in a folder, let's name it icons_patch -
  6. -
  7. We recommend creating a backup of your project before you continue
  8. -
  9. Now copy all the files in the icons_patch folder in your Dotkernel
  10. -
  11. You will be prompted to replace 2 files, simply replace the files and agree to merge the folders content (files will be added, not replaced this time)
  12. -
  13. You need to clear the cache for changes to take effect, the os and browser xml's are cached. For more information read this article (look for Browser & OS)
  14. -
  15. You can now delete the icons_patch folder or use it to patch another project
  16. -
-List of affected files: -
M /configs/useragent/browser.xml
-M /configs/useragent/os.xml
-A /images/browsers/edge.png
-A /images/os/windows_metro.png
-M stands for modify - -A stands for add - -

Frequently Asked Questions

- -
-
- What Dotkernel version is required before applying this patch? + -
-

Your project must be running version 1.5.0 or newer.

-
-
-
- Which files does the patch modify or add? + -
-

It modifies configs/useragent/browser.xml and configs/useragent/os.xml, and adds images/browsers/edge.png and images/os/windows_metro.png.

-
-
-
- Why do you need to clear the cache after applying the patch? + -
-

Because the OS and browser XML files are cached, so the new icons won't show up until the cache is cleared.

-
-
-
- Will applying the patch overwrite existing files? + -
-

You'll be prompted to replace 2 files (browser.xml and os.xml) and should agree, and also agree to merge the folders' contents since the other files listed are added rather than replaced.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.html.twig deleted file mode 100644 index 7ef1b53e..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/autologin-using-cookie-remember-me-in-dotkernel.html.twig +++ /dev/null @@ -1,140 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.3.0.

- -

Add remember me button to user interface

- -

To add remember me button to user interface, navigate to src/User/templates/user/login.html.twig and under password element add the following code:

- -
{% verbatim %}<div class="checkbox remember-me-checkbox">
-     {% set rememberMe = form.get('rememberMe') %}
-     {{ formElement(rememberMe) }}
-     <p>Remember me</p>
-</div>{% endverbatim %}
- -

After you've added the button to your template, navigate to src/User/src/Form/LoginForm.php and add the following element to your form:

- -
$this->add([
-    'name' => 'rememberMe',
-    'type' => 'checkbox',
-    'attributes' => [
-        'class' => 'tooltips',
-        'data-toggle' => 'tooltip',
-        'title' => 'Remember me',
-    ],
-]);
-
- -

After you've added the new element navigate src/User/src/InputFilter/LoginInputFilter.php and add the following code, this will add a filter to the remember me element added previously.

- -
$this->add([
-            'name' => 'rememberMe',
-            'filters' => [
-                ['name' => 'StringTrim']
-            ],
-            'validators' => [
-                [
-                    'name' => 'NotEmpty',
-                    'break_chain_on_failure' => true,
-                ]
-            ]
-        ]);
- -

To add style to your remember me button navigate to src/App/assets/scss/components/_profile.scss and add the following css:

- -
.remember-me-checkbox {
-  input {
-    display: block;
-    float: left;
-    margin: 4px 6px 10px 0;
-    width: auto;
-    height: auto;
-  }
-}
- -

After you have made all the changes it's time to compile the css in order to implement the button in interface, to do that run the following command:

- -
npm run prod
- -

Add functionality to remember me button

- -

Now that you've added the button let's move on to its functionality, first you have to navigate to src/User/src/Entity, create a new entity named UserRememberMe.php and modifiy it as in UserRememberMe

- -

After you've created the entity, you have to create migration file for the new table using the following command:

- -
vendor/bin/phinx create --configuration=config/migrations.php  RememberUserSchema
< -

class="mb-3">After the migration file is created modify it as in user_remember_schema and run the following command to add it to you database:

-
vendor/bin/phinx migrate --configuration=config/migrations.php
- -

The table generated by migration is used to store data from coockie. The stored data will help to login the user utomatically.

- -

The next step is to create a new middleware, navigate to src/App/src/Middleware and create a new file named RememberMeMiddleware.php and modify it as in RememberMeMiddleware.

- -

After you've created the new middleware navigate to config/pipeline.php and add it as in pipeline.

- -

In order to generate the cookie in the next steps you have to add a new key to your local.php, to do that navigate to config/autoload/local.php and add the following code:

- -
'rememberMe' => [
-            'cookie' => [
-                'name' => 'rememberMe',
-                'lifetime' => 3600 * 24 * 30,
-                'samesite' => 'Lax',
-                'secure' => false,
-                'httponly' => true
-            ]
-        ],
- -

In the following step you have to edit your src/User/src/Service/UserService.php. First you have to add 2 new properties, $defaultSessionManager used to get config and $repository used to get repository. After you have added those properties add the following methods: getRepository(), addRememberMeToken(), deleteRememberMeCookie(). as in UserService.

- -

(don't forget to add all new methods to interface if needed)

- -

Now, let's move to src/User/src/Repository/UserRepository.php and add the following methods: saveRememberUser(), getRememberUser(), findRememberMeUser(), deleteExpiredCookies(), removeRememberUser() as in UserRepository.

- -

The last step is to go to src/User/src/Controller/UserController.php, add a new property called $config and edit your loginAction() and logoutAction() as in UserController.

- -

Frequently Asked Questions

- -
-
- Since which release is Remember Me implemented in Dotkernel? + -
-

It's implemented in Dotkernel Frontend starting from Release 3.3.0.

-
-
-
- How do you add the Remember Me checkbox to the login UI? + -
-

In src/User/templates/user/login.html.twig, under the password element, render the rememberMe element retrieved via form.get('rememberMe') with formElement().

-
-
-
- What form and validation changes are needed? + -
-

A 'rememberMe' checkbox element must be added to src/User/src/Form/LoginForm.php, and a StringTrim filter plus a NotEmpty validator must be added for it in src/User/src/InputFilter/LoginInputFilter.php.

-
-
-
- What backend pieces implement the actual remember-me functionality? + -
-

A new UserRememberMe entity, a database migration (created and run with phinx) for its table, a new RememberMeMiddleware registered in config/pipeline.php, a rememberMe cookie configuration block added to config/autoload/local.php, and new methods added to UserService, UserRepository, and UserController (including loginAction() and logoutAction()).

-
-
-
- What does the UserRememberMe table store? + -
-

It's used to store data from the cookie, which helps log the user in automatically.

-
-
-
- How do you apply the new remember-me button styles? + -
-

Add the CSS to src/App/assets/scss/components/_profile.scss, then run npm run prod to compile the CSS so the button styling takes effect.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.html.twig deleted file mode 100644 index c73c6d5e..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/avoid-routing-through-bootstrap-of-non-existent-files.html.twig +++ /dev/null @@ -1,34 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -In some cases you may encounter missing files: images, css or js files. -All those missing files are processed by the current bootstrap: index.php - -If the session is set to regenerate on each request, as a normal security measure, -the currently logged user is logged off, because the session ID is different now. - -To avoid this, below the following line -
RewriteEngine On
-add the line : -
RewriteCond %{REQUEST_FILENAME} (\.gif|\.jpg|\.png|\.css|\.js)$ [OR]
-Save and don't forget to test. - -Even though it's a well known fact that real men do not test... - -

Frequently Asked Questions

- -
-
- What problem does this fix address? + -
-

When missing static files (images, CSS, JS) are routed through the bootstrap (index.php) and the session is set to regenerate on each request, the currently logged-in user gets logged off because the session ID changes.

-
-
-
- What is the fix? + -
-

Below the "RewriteEngine On" line, add a RewriteCond matching file extensions like .gif, .jpg, .png, .css, and .js, so requests for those files are not routed through the bootstrap.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig deleted file mode 100644 index 489ac837..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/caching-in-dotkernel-using-zend-framework.html.twig +++ /dev/null @@ -1,81 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -Because of that , we implemented in upcoming 1.8 version of Dotkernel a cache layer where to store router, acl_role, menu, options(including seo_xml), browser_xml, os_xml, test between requests. More information about the variables which Dotkernel cache by default follow this link: Dotkernel Reserved Variable Names for Caching - - - -We are implementing 2 different cache factories to choose from: apc (or apcu for newest PHP installations) and file. -

1. Configuring the cache

-The configuration can be set from /configs/application.ini, you can choose if you use the caching system, how long your cache stays valid, the cache namespace, and the storage provider (File or APC). I would disable the cache in development mode if I were you. - -For more info about the configuration and help configuring the cache see: Configuring the Cache in Dotkernel. -

2. Using the Cache

-The cache is automatically loaded in the initialization and stored in the Registry. - -Loading the caching engine is not needed because it is already loaded on kernel initialization (see Dot_Kernel::initialize($startTime)), but if you would like to use caching for other purposes (where you are not initializing the kernel), the loading syntax is the following: -
Dot_Cache::loadCache();
-Below is a simple object caching sample, yes, you can also cache objects. -
-Note: The cache key must match the following RegEx pattern: [A-Za-z0-9_]* -
$id = 'MyCachedKey';
-$obj = new stdClass();
-$obj->text = 'I am a cached text';
-
-// saving an object
-Dot_Cache::save(obj, $id);
-
-// loading the object
-$value = Dot_Cache::load($id);
-
-// checking if we have the object in cache
-if($value !== false)
-{
-     // assuming we only need the text value from the object
-     echo $value->text;
-}
-else
-{
-     echo 'no value cached for '. $id ;
-}
- -

Frequently Asked Questions

- -
-
- What data does Dotkernel's cache layer store? + -
-

Router, acl_role, menu, options (including seo_xml), browser_xml, os_xml, and test data between requests.

-
-
-
- What storage providers are available for the cache? + -
-

Two cache factories to choose from: APC (or APCU for newer PHP installations) and File.

-
-
-
- Where is the cache configured? + -
-

In /configs/application.ini, where you can enable or disable caching, set how long the cache stays valid, choose the cache namespace, and pick the storage provider (File or APC). The article recommends disabling the cache in development mode.

-
-
-
- Do you need to manually load the cache engine? + -
-

No, it's automatically loaded during kernel initialization (Dot_Kernel::initialize()). Manually calling Dot_Cache::loadCache() is only needed if you want to use caching outside of that normal initialization.

-
-
-
- Can you cache PHP objects, not just simple values? + -
-

Yes, the article shows an example of saving and loading a stdClass object using Dot_Cache::save() and Dot_Cache::load().

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/camelcase-table-names-in-mysql-on-windows.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/camelcase-table-names-in-mysql-on-windows.html.twig deleted file mode 100644 index 6f3510de..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/camelcase-table-names-in-mysql-on-windows.html.twig +++ /dev/null @@ -1,27 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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.cnf file the line: -
lower_case_table_names=2
-and restart mysql. - -More on that here: http://dev.mysql.com/doc/refman/4.1/en/server-system-variables.html#sysvar_lower_case_table_names - -

Frequently Asked Questions

- -
-
- What happens when you create a camelCase table name on WAMP or XAMPP? + -
-

A table created with a camelCase name, for example adminLogin, ends up stored as all lowercase, e.g. adminlogin, instead.

-
-
-
- How do you fix it? + -
-

Add the line lower_case_table_names=2 to your my.cnf file and restart MySQL.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.html.twig deleted file mode 100644 index 388bb994..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/commitment-to-php-new-zend-certified-engineers-zce-in-our-team.html.twig +++ /dev/null @@ -1,22 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -Zend Yellow Pages - -

Frequently Asked Questions

- -
-
- How many Zend Certified Engineers does the team have? + -
-

According to the article, 2 more team members passed the ZCE exam, bringing the team's total to 5 Zend Certified Engineers.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/configuring-the-cache-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/configuring-the-cache-in-dotkernel.html.twig deleted file mode 100644 index 59044d18..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/configuring-the-cache-in-dotkernel.html.twig +++ /dev/null @@ -1,67 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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: - - -

Main cache settings (Cache Frontend)

-The main cache settings within the application.ini file should look like this: -
cache.enable = true
-cache.factory = "apc"
-cache.lifetime = "86400"
-cache.namespace = "dotkernel"
-The cache.enable option can be used to disable caching, mostly used in development stage. - -The cache.factory value will be the cache service we want to use: file or apc - -The cache.namespace will be the cache variables prefix and the cache.lifetime value will define how long the variables cached will be usable before they will need to be re-cached. - -  -

Individual cache settings (Cache Backend)

-The individual cache settings are optional but we highly recommend that you have theese values set, otherwise other projects might use the same cache -
; file caching settings
- cache.file.cache_dir = APPLICATION_PATH "/cache"
- cache.file.cache_file_perm = 0600
-  - -For more settings and caching alternatives see the Zend Framework Cache Links at the article beginning. - -The setting pattern and sample are below: -
cache.BACKEND_NAME.SETTING = "VALUE"
-; example:
-cache.file.file_name_prefix = "Dotkernel"
- -

Frequently Asked Questions

- -
-
- What is Dotkernel's caching layer based on? + -
-

It's based on Zend Framework Cache, configured through settings in application.ini, with more configuration options available at the Zend Framework Cache Frontends and Backends documentation links given in the article.

-
-
-
- What does the cache.enable setting do? + -
-

It can be used to disable caching, which is mostly useful during the development stage.

-
-
-
- What values can cache.factory take? + -
-

The cache.factory value is the cache service to use, and the article lists two options: file or apc.

-
-
-
- Why bother setting the individual/backend cache settings like cache.file.cache_dir? + -
-

These settings are optional, but the article highly recommends setting them, otherwise other projects might end up using the same cache.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig deleted file mode 100644 index 9d8fccef..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dependency-injection-made-easy-in-laminas-mezzio-applications.html.twig +++ /dev/null @@ -1,164 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

Note: The package requires Doctrine ORM. Still, it can be used in applications which do not integrate Doctrine.

-
- -

So, first thing first, the problem.

You have a Laminas / Mezzio application with a bunch of services that you need to use in a, let's say, controller class or in any other class, and you are tired of building, updating, and maintaining factories every time you add a new dependency to your class.

- -

Dotkernel has you covered. We built a tool to autowire those dependencies in your class. There is no need for factories for every class you make. Just use one “factory” class that you tie to your custom class in the config, and that's it.

- -

Sounds easy, right? Let’s finish with the chat and speak some code, first showing the problem and then the solution.

- -

The examples below are from the Dotkernel API framework, but the pattern applies to all laminas and mezzio applications and to all PSR-11 applications.

-
- -
class UserHandler implements RequestHandlerInterface
-{
-    public function __construct(
-        protected UserServiceInterface $userService,
-        protected array $config,
-    ) {
-    }
-}
- -

Above, we have a UserHandler (Controller), and we have the required dependencies: UserService and config. Normally, we would build a factory for this to get things from the container and put them in the config provider like this:

- -
class UserHandlerFactory
-{
-    /**
-     * @throws ContainerExceptionInterface
-     * @throws NotFoundExceptionInterface
-     */
-    public function __invoke(ContainerInterface $container)
-    {
-        $userService = $container->get(UserService::class);
-        assert($userService instanceof UserService);
-
-        $config = $container->get('config');
-
-        return new UserHandler($userService, $config);
-    }
-}
- -

And in the config provider, we would have the following:

- -
public function getDependencies(): array
-{
-    return
-    ];
-}
- -

In one more example, let's look at the real-world required dependencies for UserService, the dependency that is required for UserHandle.

- -
class UserService implements UserServiceInterface
-{
-    public function __construct(
-        protected UserRoleServiceInterface $userRoleService,
-        protected MailService $mailService,
-        protected TemplateRendererInterface $templateRenderer,
-        protected OAuthAccessTokenRepository $oAuthAccessTokenRepository,
-        protected OAuthRefreshTokenRepository $oAuthRefreshTokenRepository,
-        protected UserRepository $userRepository,
-        protected UserDetailRepository $userDetailRepository,
-        protected UserResetPasswordRepository $userResetPasswordRepository,
-        protected LoggerInterface $logger,
-        protected array $config = [],
-    ) {
-    }
-}
- -

Now consider that we need to build the factory for this and update it when we add a new dependency, and so on. Also to build the logic in the factory to handle any dependencies missing from the container. Painful right?

- -

Now let's use Dotkernel's dot-dependency-injection package to inject the required dependency into your class.

- -

After you install the package, your class needs to use Dot\DependencyInjection\Attribute\Inject , then you need to add the #[Inject(...)] attribute to the constructor definition to specify which dependencies should be injected.

- -
use Dot\DependencyInjection\Attribute\Inject;
-
-class UserHandler implements RequestHandlerInterface
-{
-    #[Inject(
-        UserServiceInterface::class,
-        "config",
-    )]
-    public function __construct(
-        protected UserServiceInterface $userService,
-        protected array $config,
-    ) {
-    }
-}
- -

Add the Dot\DependencyInjection\Factory\AttributedServiceFactory class to your ConfigProvider

- -
public function getDependencies(): array
-{
-    return
-    ];
-}
- -

That's right, the `AttributedServiceFactory `class is the only one you need to add to your config, so you are ready to go. This class will "build" the factory for you and will handle all the logic if any dependencies are not found in the container with appropriate exceptions and messages.

-

-

One more time, let's see how the UserService will look now.

-

-
class UserService implements UserServiceInterface
-{
-    use Dot\DependencyInjection\Attribute\Inject;
-
-    #
-    public function __construct(
-        protected UserRoleServiceInterface $userRoleService,
-        protected MailService $mailService,
-        protected TemplateRendererInterface $templateRenderer,
-        protected OAuthAccessTokenRepository $oAuthAccessTokenRepository,
-        protected OAuthRefreshTokenRepository $oAuthRefreshTokenRepository,
-        protected UserRepository $userRepository,
-        protected UserDetailRepository $userDetailRepository,
-        protected UserResetPasswordRepository $userResetPasswordRepository,
-        protected LoggerInterface $logger,
-        protected array $config = [],
-    ) {
-    }
-
-}
-

-

-

And, that's not all.

-

If you use doctrine and repository pattern and you don't want to get your repository from EntityManager and want to inject it into your service, this package covers that too. The principle is the same, and for more insight about this, you can check the package documentation at dot-dependency-injection.

-

- -

Frequently Asked Questions

- -
-
- What problem does dot-dependency-injection solve? + -
-

In Laminas/Mezzio applications, developers normally have to build, update, and maintain a factory class for every class that needs dependencies. dot-dependency-injection autowires those dependencies instead, so you don't need a factory for every class.

-
-
-
- Does dot-dependency-injection require Doctrine ORM? + -
-

The package requires Doctrine ORM, but the article notes it can still be used in applications that don't integrate Doctrine.

-
-
-
- How do you mark a class's constructor dependencies for injection? + -
-

Import Dot\DependencyInjection\Attribute\Inject in the class, then add the attribute to the constructor definition to specify which dependencies should be injected.

-
-
-
- What do you need to add to the ConfigProvider to use this package? + -
-

Only the Dot\DependencyInjection\Factory\AttributedServiceFactory class needs to be added to your config's dependencies. It builds the factory for you and handles the logic for dependencies missing from the container, with appropriate exceptions and messages.

-
-
-
- Can this package be used with the Doctrine repository pattern? + -
-

Yes. If you don't want to fetch a repository from the EntityManager and instead want to inject it directly into your service, the article says this package covers that too, following the same principle, with more details in the package documentation.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig deleted file mode 100644 index cd593876..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/detecting-mobile-devices-in-dotkernel-1-6-0.html.twig +++ /dev/null @@ -1,111 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -The new Dotkernel version 1.6.0 is comming with some changes how we are detecting mobile devices, this changes are because of the new Wurfl Cloud integration. - -This version of Dotkernel is not comming anymore with a working built in method for mobile detection, so first we have to configure it. - - - -  - -We choosed device_os and mobile_browser capabilities because with these two capabilities we can get some extra capabilities (isMobile, isSmartPhone, isIphone, isAndroid, isBlackberry, is Symbian and is WindowsMobile) using our built in methods. - -Choosing other capabilities from scientiamobile will result in wrong detection of these extra capabilities, but you can get only those capabilities using another method from Dot_UserAgent_WurflCloud class. - -Wurfl Cloud setting in application.ini -
resources.useragent.wurflcloud.active = TRUE
-resources.useragent.wurflcloud.redirect = TRUE
-resources.useragent.wurflcloud.cache = TRUE
-resources.useragent.wurflcloud.cache_lifetime = 3600
-resources.useragent.wurflcloud.cache_namespace = WURFLCLOUD
-resources.useragent.wurflcloud.api_key = 000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-resources.useragent.wurflcloud.lib_dir = APPLICATION_PATH "/library/WurflCloud/"
-active - used to turn on (TRUE) or off (FALSE) the wurfl cloud detection (default: TRUE) -redirect - if is TRUE your visitators from frontend will be redirected to mobile module (default: TRUE) -cache - cache every distinct result to optimize the number of requests to scientiamobile (default: TRUE) -cache_lifetime - time in seconds to keep the results in cache (default: 3600) -cache_namespace - the prefix used for cache keys (default: WURFLCLOUD) -api_key - API Key from WURFL Cloud account (change this with your key) -lib_dir - the wurfl cloud library location in Dotkernel (don't change this, just if you want to move the library) - -Because of these changes we removed the old Dot_UserAgent_Wurfl class and added the new one Dot_UserAgent_WurflCloud wich is using the Wurfl Cloud API adapter. - -

Example of Dot_UserAgent usage in Dotkernel:

- -Get Wurfl configuration -
$wurflConf = $registry->configuration->resources->useragent->wurflcloud;
-Note: You can have more Wurfl configurations if you have more libraries like Wurfl Package (GPL) - -If Wurfl is active then get device info -
if($wurflConf->active)
-{
-    $deviceInfo = Dot_UserAgent :: getDeviceInfo($_SERVER);
-    ...
-}
-If detected device is an mobile device we will save device info in database and redirect it to the mobile controller -
if( (0 < count((array)$deviceInfo)) && $deviceInfo->isMobile)
-{
-
-    if(!$registry->session->visitId)
-    {
-        $registry->session->visitId = Dot_Statistic::registerVisit();
-    }
-
-    // if the Statistic module is integrate, record the deviceInfo too, and record TRUE
-        //in $session->mobile
-    if(!$registry->session->mobile)
-    {
-        $registry->session->mobile =
-                Dot_Statistic::registerMobileDetails($registry->session->visitId, $deviceInfo);
-
-        //redirect to mobile controller , only if the session is not set.
-        //Otherwise will trap the user in mobile controller
-        if($wurflConf->redirect)
-        {
-            header('location: '.
-                        $registry->configuration->website->params->url.'/mobile');
-            exit;
-        }
-    }
-}
- -

Frequently Asked Questions

- -
-
- Why did mobile detection change in Dotkernel 1.6.0? + -
-

Because of the new Wurfl Cloud integration. This version no longer ships with a working built-in method for mobile detection, so it must be configured first.

-
-
-
- What are the steps to configure Wurfl Cloud detection? + -
-

Go to the scientiamobile website and register for a Wurfl Cloud account, choose the device_os and mobile_browser capabilities for the account and save, then go to API Keys and copy the key into application.ini.

-
-
-
- Why choose the device_os and mobile_browser capabilities specifically? + -
-

With these two capabilities, Dotkernel's built-in methods can also derive extra capabilities such as isMobile, isSmartPhone, isIphone, isAndroid, isBlackberry, isSymbian, and isWindowsMobile. Choosing other capabilities from scientiamobile results in wrong detection of these extra capabilities.

-
-
-
- What happened to the old Dot_UserAgent_Wurfl class? + -
-

It was removed and replaced with the new Dot_UserAgent_WurflCloud class, which uses the Wurfl Cloud API adapter.

-
-
-
- What does the redirect setting in application.ini control? + -
-

When resources.useragent.wurflcloud.redirect is TRUE (the default), visitors from the frontend are redirected to the mobile module the first time a mobile device is detected.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/disable-wurfl-redirect-for-mobile-browsers.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/disable-wurfl-redirect-for-mobile-browsers.html.twig deleted file mode 100644 index 6b8d447f..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/disable-wurfl-redirect-for-mobile-browsers.html.twig +++ /dev/null @@ -1,31 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Dotkernel has an example mobile site at http://v1.dotkernel.net/mobile that uses jQuery Mobile. - -Wurfl is also used to detect mobile browsers (as discussed in a previous blog post) and automatically redirect them to the mobile site the first time they view the homepage. Sometimes, this behavior isn't desired (for example when you don't have a mobile site, or you don't plan on using Wurfl at all) - -Starting with revision 408, there's an option in application.ini to disable the automatic redirect (by default the redirect is disabled): - -
resources.useragent.wurflapi.redirect = false
-The following condition is also added to Controllers/frontend/IndexController.php (at line 19) to check the configuration: -
//if automatic redirect is enabled in application.ini and the browser is mobile and session->mobileHit is not set, register it and redirect
-if($config->resources->useragent->wurflapi->redirect && 'mobile' == Dot_Kernel::getDevice()->getType() && !isset($session->mobileHit))
- -

Frequently Asked Questions

- -
-
- How do you disable the automatic mobile redirect in Dotkernel? + -
-

Starting with revision 408, set resources.useragent.wurflapi.redirect = false in application.ini. Per the article, this is also the default state of the redirect option.

-
-
-
- Where in the code is this configuration option checked? + -
-

In Controllers/frontend/IndexController.php (around line 19), a condition checks whether the redirect is enabled in application.ini, whether the visiting browser is mobile, and whether session->mobileHit isn't already set, before registering and redirecting the visit.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.html.twig deleted file mode 100644 index 12e78555..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/disambiguation-dotkernel-1-and-dotkernel-3.html.twig +++ /dev/null @@ -1,74 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

What is the meaning behind 'Dotkernel'?

- -

The name Dotkernel symbiotically combines the string Dot, as a representation of the Internet, and Kernel, the quintessential components of any IT application.

-

In other words, Dotkernel aims to become the starting point for development Internet applications and hence ensure increased development productivity and run-time performance.

- -

What was Dotkernel 1?

- -

Dotkernel 1 was a PHP Application Framework, built on top of Zend Framework 1 (ZF1).

- -

It had the first public release in July 2010. - It was tightly coupled with Zend Framework 1 and adds a set of custom or external features (such as Router, Template Engine, etc.). - It was composed of Zend Framework 1 and a set of custom or external features (such as Router, Template Engine, etc.). - Dotkernel 1 architecture was based on MVC.

- -

The latest version is 1.8 Long Term Support. - It will not be getting any new releases or bugfixes because Zend Framework 1 is also not supported. - If you are still using either Dotkernel 1 or Zend Framework 1, you need to refactor your code to the Dotkernel Headless Platform.

- -

What is Dotkernel?

- -

A collection of PSR-15 Middleware applications built on top of the Mezzio microframework. - It is composed of a set of custom and extended Laminas components.

-

Dotkernel architecture is based on Middleware. - Dotkernel implements the following PSR's, where applicable: PSR-7, PSR-11, PSR-15, PSR-3, PSR-4, PSR-6, PSR-13, PSR-14, PSR-17, PSR-18, PSR-20.

- -

Currently, there are three applications:

- -

- API
- - Admin
- - Queue

- -

Dotkernel = Dotkernel 1 or the new Dotkernel?

- -

In posts older than 2017 Dotkernel 1 was referred to as Dotkernel because it was the only Dotkernel version. - Since the release of newer versions, we have dropped the number at the end, so currently we refer to our platform as 'Dotkernel'.

- -

Frequently Asked Questions

- -
-
- What does the name "Dotkernel" mean? + -
-

It combines "Dot", as a representation of the Internet, with "kernel", the quintessence of any IT application, reflecting the aim of being a central part of Internet development.

-
-
-
- What is Dotkernel 1? + -
-

A PHP Application Framework built on top of Zend Framework 1, first publicly released in July 2010, with an architecture based on MVC. Its latest version is 1.8 Long Term Support, which per the article will not be followed by a new version, only bugfixes.

-
-
-
- What is Dotkernel? + -
-

A collection of PSR-15 Middleware applications built on top of the Mezzio microframework. It implements PSR-7, PSR-11, PSR-15, PSR-3, PSR-4, PSR-6, PSR-13, PSR-14, PSR-17, PSR-18, PSR-20.

-
-
-
- How many applications make up Dotkernel? + -
-

At the time of the article, there were three available applications, API, Admin and Queue.

-
-
-
- When someone writes just "Dotkernel", which version is meant? + -
-

In posts older than 2017, "Dotkernel" referred to Dotkernel 1, since it was the only version. Since the release of Dotkernel 3, "Dotkernel" refers to Dotkernel 3, and all future references to Dotkernel 1 are made explicitly.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig deleted file mode 100644 index 4da69f55..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-cache-using-symfony-cache.html.twig +++ /dev/null @@ -1,109 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

-

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.

-

-

When a user visits a website or interacts with a web application, various resources such as images, scripts, and database queries are requested from the server. Retrieving these resources can sometimes be time-consuming, especially if they require complex processing or querying a database.

-

-

To speed up this process and improve overall performance, developers implement caching mechanisms. When data is first requested, it's stored in a cache. Then, when subsequent requests for the same data are made, the application can retrieve it from the cache instead of fetching it from the original source. This reduces the time it takes to serve the content to the user because accessing data from the cache is typically much faster than retrieving it from the original source.

-
-

This article is a follow-up to the previous article where we tackled the caching topic.

-
-

-
-

- -

In this article our focus will be on enabling the dot-cache component and effectively implementing caching in Dotkernel Admin.

-

Installation

-

Run the following command in your project directory:

composer require dotkernel/dot-cache

After installing, add the DotCacheConfigProvider::class class to your configuration aggregate (config/config.php).

-

Before we continue with the configuration process we need to know a few things about how and where the data is stored.

-

The dotkernel/dot-cache component is a wrapper that sits on top of symfony/cache. It currently supports two adapters and can store data in two distinct locations:

-
    -
  1. array - stores data in-memory
  2. -
  3. filesystem - stores data on local disk files
  4. -
-

1. Storing data in-memory is the fastest and sometimes the cheapest caching mechanism, but it also comes with down-sides. Storing everything in the RAM memory is not the best idea when your application is running on a low memory system. In this case you should consider using the filesystem mechanism.

-

2. The second caching mechanism involves storing data into files on the local disk, known as the filesystem option. While this option may be slightly slower than the first one, it provides a more persistent storage solution.

-

Feel free to explore and use other adapters from symfony/cache by checking the official documentation.

-

Configuration

-

In configautoloaddoctrine.global.php, in the doctrine.configuration.orm_default key add the following entry:

-
'result_cache'       => 'filesystem',
-'metadata_cache'     => 'filesystem',
-'query_cache'        => 'filesystem',
-'hydration_cache'    => 'array',
-'second_level_cache' => ,
-],
-

Next, under the doctrine key add the following items:

-
'cache' => ,
-    'filesystem' => ,
-],
-

The result is that the metadata and query cache will be stored in the data/cache/doctrine folder and the hydration cache will be stored in-memory.

-

Each system is unique, requiring customized configurations. Make sure to identify the specific configuration requirements for your application.

-

Doctrine cache is divided into 4 different types: 

- -

Result cache

-

The result cache can be used to store the results of your queries, enabling Doctrine to avoid querying the database or hydrating the data again after the initial retrieval.

-

 

-

Metadata cache

-

Parsing your class metadata on every request is inefficient. Instead, it's advisable to cache this information using one of the available cache adapters.

-

 

-

Query cache

-

In a production environment, it's strongly recommended to cache the resulted DQL query into its SQL equivalent. Since the query doesn't change unless the DQL query itself changes, it's unnecessary to parse it multiple times.

-

Hydration cache

-Doctrine hydration cache is a feature that stores the results of data hydration, which is the process of converting raw database data into usable objects or arrays. By caching these results, it avoids repeating the hydration process for repeated queries, improving performance. -

How to use

-

To enable caching for entities, need to add the  # attribute like in the following example:

-
#
#
#
class Admin extends AbstractEntity implements AdminInterface
{
}
-

For further details about the cache mode please refer to the official documentation.

-

When querying data, you can have Doctrine cache your results. You do this by calling the setCacheable method on the query builder.

-
$this->getQueryBuilder()
->select('admin')
->from(Admin::class, 'admin')
->setCacheable(true)
->getQuery()
->getResult();
-

Caching is not limited to entities alone. Objects can be cached too. Check the basic cache usage for this purpose.

-

In conclusion, cache plays a vital role in optimizing system performance and improving user experience by storing frequently accessed data. As technology continues to evolve, caching mechanisms will remain an integral part of modern computing architectures, driving faster access to data and smoother user interactions across various digital platforms.

- -

Frequently Asked Questions

- -
-
- What component does this article use for caching Doctrine data? + -
-

The dotkernel/dot-cache component, a wrapper that sits on top of symfony/cache. It's installed with composer require dotkernel/dot-cache and registered by adding DotCacheConfigProvider::class to the configuration aggregate.

-
-
-
- What storage adapters does dot-cache currently support? + -
-

Two: array, which stores data in-memory and is the fastest option but uses more RAM, and filesystem, which stores data in local disk files and is slightly slower but more persistent.

-
-
-
- What are the four types of Doctrine cache covered? + -
-

result_cache, metadata_cache, query_cache, and hydration_cache, each configured under the doctrine.configuration.orm_default key.

-
-
-
- What does the result cache do? + -
-

It stores the results of queries, letting Doctrine avoid querying the database or hydrating the data again after the initial retrieval.

-
-
-
- Why cache class metadata? + -
-

Because parsing class metadata on every request is inefficient, so it's advisable to cache it using one of the available cache adapters.

-
-
-
- How do you mark an entity or a query as cacheable? + -
-

To enable caching for entities you add a caching attribute to the entity class; to cache an individual query, call setCacheable(true) on the query builder before getResult().

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig deleted file mode 100644 index be7ce995..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/doctrine-enum-implementation-in-dotkernel.html.twig +++ /dev/null @@ -1,267 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

The update of doctrine/orm to version 3.2.0 saw the introduction of EnumType columns. The enum type was introduced in PHP 8.1. This new data type is now implemented in Dotkernel, on both the PHP side and the database side.

- -

Below we will discuss some technical aspects behind this update. You can review the full update in this Dotkernel API Pull Request.

- -

Doctrine's approach

- -

The update introduces the detection of enumType and options.values from a property with type: Types::ENUM. This PR discusses the update and links to several older relevant issues.

- -

Old setup

- -
#[Entity]
-class Card
-{
-    #[Id]
-    #[GeneratedValue]
-    #[Column]
-    public int $id;
-
-    #[Column(
-        type: Types::ENUM,
-        enumType: Suit::class,
-        options: ['values' => ['H', 'D', 'C', 'S']],
-    )]
-    public Suit $suit;
-}
- -

New setup

- -
#[Entity]
-class Card
-{
-    #[Id]
-    #[GeneratedValue]
-    #[Column]
-    public int $id;
-
-    #[Column(type: Types::ENUM)]
-    public Suit $suit;
-}
- -

Note that the type Types::ENUM part is still required if we want to have an actual enum column in MariaDB. We still default to Types::STRING or TYPES::INTEGER for columns types with a PHP enum as this is the more portable solution and the safer default.

-
- -

Dotkernel's approach

- -

Old setup

- -

Dotkernel uses flags for columns like User->Status, but we resorted to the simpler string type. The obvious disadvantage is that you can't definitively enforce a set of values for a given column. Sure, the PHP can be set up to only use the agrred upon set of values, but the database is independent from it. If you edit a value manually in the database, any string is accepted.

- -

The issue is the same on the side of the PHP code. If the developer adds a value with a typo, it's supported, but will not work as intended.

- -

The only advantage this setup has is the ability to easily add more values in the value set. This may be seen as a feature, but it invites bugs in the execution.

- -

Our old implementation defined the values like below, for the User entity.

- -
public const STATUS_PENDING = 'pending';
-public const STATUS_ACTIVE  = 'active';
-public const STATUSES       = [
-    self::STATUS_PENDING,
-    self::STATUS_ACTIVE,
-];
- -

The column for the ORM was defined like this, as a simple string, with pending as its default value:

- -
#[ORM\Column(name: "status", type: "string", length: 20)]
-protected string $status = self::STATUS_PENDING;
- -

Obviously, the getStatus and setStatus also work with strings:

- -
public function getStatus(): string
-{
-    return $this->status;
-}
-
-public function setStatus(string $status): self
-{
-    $this->status = $status;
-}
- -

New setup

- -

Thanks to the update of doctrine/orm to version 3.2.0, Dotkernel can now have a proper link between the PHP code and database values. Now the link between the PHP code and the database is explicit and enforced.

- -

Any update to the value set must be on both the PHP code and the database.

-
- -

Let's review how the update affects the User entity.

- -

In the next example, we show how to implement a value set using a custom enum.

-
- -

First, we define our custom value set in src/User/src/Enum/UserStatusEnum.php.

- -
namespace Api\User\Enum;
-
-enum UserStatusEnum: string
-{
-    case Active  = 'active';
-    case Pending = 'pending';
-}
- -

We need to create src/User/src/DBAL/Types/UserStatusEnumType.php to process the new values for the status column.

- -

AbstractEnumType must be extended by any future custom enum type.

-
- -
namespace Api\User\DBAL\Types;
-
-use Api\App\DBAL\Types\AbstractEnumType;
-use Api\User\Enum\UserStatusEnum;
-
-class UserStatusEnumType extends AbstractEnumType
-{
-    public const NAME = 'user_status_enum';
-
-    protected function getEnumClass(): string
-    {
-        return UserStatusEnum::class;
-    }
-
-    public function getName(): string
-    {
-        return self::NAME;
-    }
-}
- -

If you create your own enum types, make sure to update the NAME constant and the value returned by getEnumClass.

-
- -

Let's register the custom type in config/autoload/doctrine.global.php under the types key:

- -
'types'         => [
-[...]
-    UserStatusEnumType::NAME => UserStatusEnumType::class,
-[...]
-],
- -

The filtering is updated in src/User/src/InputFilter/Input/StatusInput.php:

- -
$this->getFilterChain()
-    ->attachByName(StringTrim::class)
-    ->attachByName(StripTags::class)
-    ->attach(fn($value) => $value === null ? UserStatusEnum::Active : UserStatusEnum::from($value));
-
-$this->getValidatorChain()
-    ->attachByName(InArray::class, [
-        'haystack' => UserStatusEnum::cases(),
-        'message'  => sprintf(Message::INVALID_VALUE, 'status'),
-    ], true);
- -

The above ensures that the new UserStatusEnum class is used for the status column updates.

- -

The User entity uses the new UserStatusEnum class.

- -
#[ORM\Column(type: 'user_status_enum', options: ['default' => UserStatusEnum::Pending])]
-protected UserStatusEnum $status = UserStatusEnum::Pending;
- -

The status getter and setter are also updated:

- -
public function getStatus(): UserStatusEnum
-{
-    return $this->status;
-}
-
-public function setStatus(UserStatusEnum $status): self
-{
-    $this->status = $status;
-}
- -

Dotkernel checks the user status during login in src/User/src/Repository/UserRepository.php. If the user is not activated, the login is rejected.

- -
if ($clientEntity->getName() === 'frontend' && $result['status'] !== UserStatusEnum::Active) {
-    throw new OAuthServerException(Message::USER_NOT_ACTIVATED, 6, 'inactive_user', 401);
-}
- -

A new user is created using the enum type and pending as the default.

- -
$user = (new User())
-    ->setDetail($detail)
-    ->setIdentity($data['identity'])
-    ->usePassword($data['password'])
-    ->setStatus($data['status'] ?? UserStatusEnum::Pending);
- -

Note the status column in the migration query which now looks like this:

- -
$this->addSql('
-CREATE TABLE user (
-    uuid BINARY(16) NOT NULL,
-    identity VARCHAR(191) NOT NULL,
-    password VARCHAR(191) NOT NULL,
-    status ENUM(\'active\', \'pending\') DEFAULT \'pending\' NOT NULL,
-    isDeleted TINYINT(1) NOT NULL,
-    hash VARCHAR(64) NOT NULL,
-    created DATETIME NOT NULL,
-    updated DATETIME DEFAULT NULL,
-    UNIQUE INDEX UNIQ_8D93D6496A95E9C4 (identity), UNIQUE INDEX UNIQ_8D93D649D1B862B8 (hash),
-    PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4');
- -

The difference for the migration query is for the status column, highlighted below:

- -
old setup: status VARCHAR(20) NOT NULL
-new setup: status ENUM(\'active\', \'pending\') DEFAULT \'pending\' NOT NULL
- -

Conclusions

- -

The old setup used in the Dotkernel applications worked fine, but the limitations were clear as day. There was:

- - - -

The new setup solves both issues, ensuring more consistent flag management for your classes.

- -

Frequently Asked Questions

- -
-
- What update triggered this change to Dotkernel's enum handling? + -
-

The update of doctrine/orm to version 3.2.0 introduced EnumType columns, building on the enum type introduced in PHP 8.1. Dotkernel implemented this new data type on both the PHP side and the database side.

-
-
-
- What was the limitation of Dotkernel's old approach to columns like User->Status? + -
-

The old setup used a simple string type, so the value set couldn't be definitively enforced. A typo in a PHP value would still be accepted, and the database was independent of any values the PHP code allowed, so manually editing a value in the database would accept any string.

-
-
-
- What was the one advantage of the old string-based setup? + -
-

It made it easy to add more values to the value set, though the article notes this ease also invites bugs in the execution.

-
-
-
- What do you need to create to add a new custom enum type? + -
-

A PHP enum class (like UserStatusEnum) plus a DBAL type class extending AbstractEnumType, which must define a NAME constant and a getEnumClass() method; the new type is then registered under the types key in config/autoload/doctrine.global.php.

-
-
-
- Does Types::ENUM still fall back to a string or integer database column? + -
-

The article notes that Doctrine still defaults to Types::STRING or Types::INTEGER for columns backed by a PHP enum, as this is considered the more portable and safer default; Types::ENUM is required if you want an actual enum column in MariaDB.

-
-
-
- What must happen when the value set of an enum changes under the new setup? + -
-

Any update to the value set must be made on both the PHP code and the database, since the new setup creates an explicit, enforced link between them.

-
-
-
- -

Relevant links

- - -{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotboost-technologies-products-and-services-north-american-relaunch.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotboost-technologies-products-and-services-north-american-relaunch.html.twig deleted file mode 100644 index d18f8f29..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotboost-technologies-products-and-services-north-american-relaunch.html.twig +++ /dev/null @@ -1,31 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -At the heart of our relaunch is the source release for our exclusive inhouse developed Dotkernel framework. We have also added business IT integration and increased the clarity to our existing consulting services. - - -We're not your average IT organization; we view our customers as strategic partners. This paradigm allows us to take a comprehensive approach towards creating solutions and gain the competitive advantage. - -Founded in 2005, the Dotboost process can incorporate anywhere into your project's life-cycle including concept development, architecture and design, development and integration, and implementation and support. We use time and distance to our advantage, pushing competitive boundaries and staking our place as a globally efficient organization. - -

Frequently Asked Questions

- -
-
- What is at the heart of Dotboost's North American relaunch? + -
-

The source release of Dotboost's exclusive, in-house developed Dotkernel framework, along with added business IT integration and increased clarity around existing consulting services.

-
-
-
- When was Dotboost founded, and at what stages can it join a project? + -
-

Dotboost was founded in 2005. Per the article, its process can incorporate anywhere into a project's life-cycle, including concept development, architecture and design, development and integration, and implementation and support.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-2-0-release.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-2-0-release.html.twig deleted file mode 100644 index 1229a536..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-2-0-release.html.twig +++ /dev/null @@ -1,62 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Finally we reached Dotkernel 1.2.0 milestone. - -Since the previous released 1.1.2, some changes have been made. - - - - -To be easier to start an application from Dotkernel, in admin module, there are now the following dots:  admin, user and system. - -New library classes have been implemented: Dot_Geoip and Dot_Seo, and some of the existing ones have been updated: Dot_Curl and Dot_Session (each module has his own session). - -In Dotkernel, all SQL queries are written as prepared statements.  We strongly encourage this  practice: http://www.dotkernel.com/php-development/protection-against-sql-injection-using-pdo-and-zend-framework/ - -For more details, see  ChangeLog 1.2.0 - - - - -

Frequently Asked Questions

- -
-
- What is a "dots" in Dotkernel, a term introduced in this release? + -
-

A term for a submodule and all its component files. For example, "user" is a dots of the frontend module, and one dots can belong to multiple modules, such as "user" belonging to both frontend and admin.

-
-
-
- Where are dots configuration values stored, compared to earlier versions? + -
-

They're stored in XML files inside the configs/dots folder. In previous versions, these values were hard-coded in the PHP files.

-
-
-
- What dots does the admin module include by default? + -
-

admin, user, and system, to make it easier to start an application from Dotkernel.

-
-
-
- What library classes were added or updated in 1.2.0? + -
-

Dot_Geoip and Dot_Seo were newly implemented, while Dot_Curl and Dot_Session were updated, with each module now having its own session.

-
-
-
- How are SQL queries written in Dotkernel? + -
-

All SQL queries are written as prepared statements, a practice the article strongly encourages.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-2-2-release.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-2-2-release.html.twig deleted file mode 100644 index 5b65538b..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-2-2-release.html.twig +++ /dev/null @@ -1,41 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Yesterday, we released Dotkernel 1.2.2. It contains some bug fixes: - - -For more details see ChangeLog 1.2.2. To get only the changed files from 1.2.1 to 1.2.2, download the upgrade file - -Note*: because of the Bug 35, all php files have changed. To see what else has changed, check the Dotkernel Tracker or the Dotkernel WebSVN . - -P.S. On July 22, 2010 we released Dotkernel 1.2.1. You can check the ChangeLog 1.2.1 or download the upgrade 1.2.1 zip file. - -

Frequently Asked Questions

- -
-
- What does the Dotkernel 1.2.2 release include? + -
-

Dotkernel 1.2.2 is a bug-fix release that closes five issues: captcha error handling (try/catch), a pagination issue, a wrong admin link, notices and a fatal error found by an Acunetix scan, and an update to the copyright line in files.

-
-
-
- Why did all PHP files change in the 1.2.2 release? + -
-

Because of the fix for bug 35, which updated the copyright line, every PHP file in the codebase was touched, which is why the note in the post warns that all PHP files have changed.

-
-
-
- How can I upgrade from a previous version to 1.2.2? + -
-

You can download just the changed files from 1.2.1 to 1.2.2 using the upgrade package linked in the post, or check the ChangeLog 1.2.2 for full details of what changed.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-3-0-release.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-3-0-release.html.twig deleted file mode 100644 index 6fa33619..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-3-0-release.html.twig +++ /dev/null @@ -1,70 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} - Dotkernel 1.3.0 is released at last. It contains important changes and new features. - -- 64: [Feature] Skin switcher in admin - closed. - -The admin skin can be customized. There are several readymade skins like: blue, brown, gray and green. To set the admin skin, change the value of settings.admin.skin from application (e.g. settings.admin.skin = green). - - -- 76: [Bugs] Want-Url in frontend - closed. - -To protect a link that is accessible by members only, add this line in the controller file to protect what is below it: Dot_Auth::checkIdentity(); - -- 77: [Bugs] Dot_Sessions / rename - closed. -- 70: [Bugs] Menu issue in Admin and frontend - closed. -- 73: [Bugs] Naming consistency Upper-lower case in url - closed. -- 71: [Bugs] XSS forgot password - closed. -- 72: [Bugs] scan result Oct 12th - closed. -- 63: [Bugs] Scan results Oct 1, 2010 on 1.3.0 RC - closed. -- 69: [Bugs] reorganization of XMl files - closed. - -Some xml files from configs folder have been changed to encompass the current needs of Dotkernel. resource.xml has been deleted and its content split between route.xml and dots.xml. Check the manual to find more about route.xml and dots.xml - -- 67: [Bugs] Security Issue / test controller - closed. -- 68: [Bugs] The canonical URL isn't escaped - closed. -- 66: [Bugs] Blank line at the beginning of every file in the frontend - closed. -- 60: [Bugs] 1.3.0 as Release Candidate Friday Oct 1st - closed. -- 62: [Bugs] Admin text box class dojo - closed. -- 55: [Bugs] geoIP extension: record by name + list in dashboard admin GEOIP version and build - closed. -- 59: [Bugs] Admin listings # - closed. -- 61: [Bugs] Admin Add Transporter not working - closed. -- 58: [Bugs] Admin : list stuff, div float - closed. -- 57: [Bugs] OS name on admin/on mouse over - closed. -- 53: [Bugs] drop-down list in admin/user logins - closed. -- 52: [Bugs] Admin hide debug bar in Login page - closed. - -For more details see ChangeLog 1.3.0. - -Note*: because of the bug 69, this release is not compatible with the previous versions. To see what else has changed, check Dotkernel Tracker or Dotkernel WebSVN. - -

Frequently Asked Questions

- -
-
- What is new in the admin interface in Dotkernel 1.3.0? + -
-

1.3.0 adds a skin switcher for the admin, with several ready-made skins (blue, brown, gray, green) that can be set via the settings.admin.skin configuration value.

-
-
-
- How do I protect a page so only logged-in members can access it? + -
-

Add the line Dot_Auth::checkIdentity(); in the controller file above the code you want to protect - everything below that line requires the visitor to be logged in.

-
-
-
- What happened to resource.xml in this release? + -
-

resource.xml was deleted and its content split between two new files, route.xml and dots.xml.

-
-
-
- Is Dotkernel 1.3.0 backward compatible with earlier versions? + -
-

No. Because of the XML file reorganization (bug 69), 1.3.0 is not compatible with previous versions.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-3-2-release.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-3-2-release.html.twig deleted file mode 100644 index 328bba6a..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-3-2-release.html.twig +++ /dev/null @@ -1,51 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Before the winter holiday we came with a new release: Dotkernel 1.3.2 -It contains many bug fixes, some refactoring and a few minor features. - -Bugs fixes: - -Minor features: - -Refactoring: - - -

Frequently Asked Questions

- -
-
- What kind of release is Dotkernel 1.3.2? + -
-

It's mainly a maintenance release, containing many bug fixes, some refactoring, and a few minor features.

-
-
-
- What bugs were fixed in 1.3.2? + -
-

Fixes include a CSS issue on the admin phpinfo page, a warning in the admin dashboard, a WURFL cache issue and WURFL version issue in admin, a Dot_Paginator bug, a Zend Paginator double-query issue, and a database naming convention issue.

-
-
-
- What minor features and refactoring were included? + -
-

Minor features include a refactor of validIP in Dot_Kernel and showing the WURFL date and API version in admin. Refactoring covered Zend_Paginator and added a dojo dijit theme to Dotkernel.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig deleted file mode 100644 index 717e041a..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-5-0-released.html.twig +++ /dev/null @@ -1,62 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -After a longer wait than usual, Dotkernel 1.5.0 was just released. Due to the large amount of changes and the long time spent in development, we chose to skip 1.4 and go straight to 1.5.0. - -Here are a few of the many changes to Dotkernel in the latest release: -

-

Highlights of 1.5.0

-

Switched from Dojo to jQuery

-Starting with 1.5.0 we've switched from using Dojo to jQuery. This doesn't mean you can't still use Dojo in your own projects, but only jQuery will be used and maintained in the Dotkernel distribution. -

New designs

-We've redesigned the admin site, with new themes, and a dropdown menu, as well as a new and simpler design for the front-end. -

Model inheritance

-Up until now, there was a lot of code duplication in models. For example, in the user model, you might have a getUserById function in the admin as well as the frontend. When you've got more models and more modules, your project can start having a lot of copy-pasted code. - -To prevent this, we've introduced a Dot_Model class, and a way to define global models that are inherited in the admin and frontend. This way, you can have User class in the admin that only has methods specific to the admin module, a User class in the frontend that only has code specific for the frontend, and they both inherit the Dot_Model_User class which will have all the common code. -

Dashed controllers

-We've changed the way the controller name is parsed, so that you can have controller with multiple words, split with dashes, without breaking the coding standard (for example, www.example.com/search-article will call SearchArticleController.php) -

Zend Registry reorganization

-We've changed the structure of the registry, for more about this, please check this blog post. - -  - -There have been about 250 commits in our SVN repository since the latest release, so we can't cover all changes in this blog post. Please download Dotkernel 1.5.0 try it out yourself and tell us what you think. - -  - -

Frequently Asked Questions

- -
-
- Why did Dotkernel jump from 1.3 straight to 1.5.0? + -
-

Because of the large amount of changes and the long time spent in development, the team chose to skip version 1.4 and go straight to 1.5.0.

-
-
-
- Did Dotkernel switch from Dojo to jQuery in 1.5.0? + -
-

Yes. Starting with 1.5.0, Dotkernel switched from Dojo to jQuery for its own distribution, though Dojo can still be used in your own projects.

-
-
-
- What is Dot_Model and why was it introduced? + -
-

Dot_Model is a base class introduced to reduce code duplication between admin and frontend models. Both admin- and frontend-specific model classes (such as User) inherit from a shared Dot_Model_User class that holds the common code.

-
-
-
- How does the "dashed controllers" feature work? + -
-

The controller name parsing was changed so a URL like www.example.com/search-article correctly calls SearchArticleController.php, allowing multi-word controller names split with dashes without breaking the coding standard.

-
-
-
- How much changed in the 1.5.0 release? + -
-

About 250 commits went into the SVN repository since the previous release, so the blog post only covers the highlights - the full Dotkernel 1.5.0 download is available to try out.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig deleted file mode 100644 index 18987679..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-0-lts-released.html.twig +++ /dev/null @@ -1,90 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Dotkernel 1.8.0 (LTS) was just released. -

What is LTS? -

-Long-term support (LTS) is a type of special versions or editions of software designed to be supported for a longer than normal period. It is particularly applicable to open-source software projects. It contains many bug fixes, some refactoring and a few minor features. Find more details read this article. - -  - -Here are a few of the many changes to Dotkernel in the latest release: -

-

Highlights of 1.8.0 (LTS)

-  -

Plugin Architecture

-Starting with 1.8.0 we will start using Plugins to make the Dotkernel extending easier. We'll keep you up to date about how you create and use a plugin. -

New design

-We've redesigned the admin module and the frontend module is now mobile-friendly, but you can still use the mobile module. -

Loads Faster

-The Dotkernel framework just got a big boost because it supports APC & File Caching within the framework, all the XML's and config files are cached in order to maximize response speed for more information about caching and how to cache your data see this article. -

Easier Request Handling

-We've added a new class, Dot_Request, which lets you have control over the request data before you use it, for example the variables $_SERVER, $_GET and $_POST are only used within controllers. -

Features added

- -

Other changes

- -

Bug Fixes

- -  - -  - -There have been a lot of commits in our SVN repository since the latest release, so we can't cover all changes in this blog post. Please download Dotkernel 1.8.0 (LTS) try it out yourself and tell us what you think. - -  - -

Frequently Asked Questions

- -
-
- What does LTS mean for Dotkernel 1.8.0? + -
-

LTS stands for Long-term support, a type of special version designed to be supported for longer than normal, which is particularly common for open-source software projects.

-
-
-
- What is the Plugin Architecture introduced in 1.8.0? + -
-

Starting with 1.8.0, Dotkernel uses Plugins to make extending the framework easier.

-
-
-
- How does 1.8.0 load faster than previous versions? + -
-

It supports APC and File Caching within the framework, so XML files and config files are cached to maximize response speed.

-
-
-
- What is Dot_Request? + -
-

Dot_Request is a new class that gives you control over the request data before you use it, so that the variables $_SERVER, $_GET and $_POST are only accessed within controllers.

-
-
-
- What was removed from Dotkernel in 1.8.0? + -
-

WURFL integration was removed (mobile device detection is now handled separately, and WURFL can be added as a plugin), and support for multiple SMTP transporters was removed (it can also be added as a plugin).

-
-
-
- What security-related additions does 1.8.0 include? + -
-

A security scan in the Admin Dashboard shows recommended settings, admin failed-login notifications are sent to all developers listed in devEmails (not just the first admin), and a new Alert System can notify developers if something goes wrong.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.html.twig deleted file mode 100644 index 06d78dfa..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-1-8-1-upgrade-from-1-8-0-released.html.twig +++ /dev/null @@ -1,33 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Dotkernel 1.8.1 was just released. - -Changes to Dotkernel in the latest release: -- Enhanced Cache Support, which means you can use tags in your cache system if the host suports it - -Here are some useful download links: - -Dotkernel 1.8.1 - -Upgrade from Dotkernel 1.8.0 - -Dotkernel 1.8.0 (LTS) - -

Frequently Asked Questions

- -
-
- What's new in Dotkernel 1.8.1? + -
-

The main change is Enhanced Cache Support, which means you can use tags in your cache system if the host supports it.

-
-
-
- How do I upgrade from 1.8.0 to 1.8.1? + -
-

The post provides a dedicated "Upgrade from Dotkernel 1.8.0" download link, separate from the full Dotkernel 1.8.1 package and the original Dotkernel 1.8.0 (LTS) download.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-coding-standard.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-coding-standard.html.twig deleted file mode 100644 index e8d957e8..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-coding-standard.html.twig +++ /dev/null @@ -1,70 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Dotkernel will be a "skeleton"of Zend Framework. -Dotkernel borrowed the coding standard from Zend Framework: ZF Coding Standard with some exceptions. - -In what follows, we will make remarks only on those features that are slightly different in the coding standards of Dotkernel. - - -B.2. PHP File Formatting - -B.3. Naming Conventions - -Camel naming convention - -B.4.6. Control Statements -every starting curly brace } after a statement starts on a new line, end it's closing curly brace } will be on a new line too. The start and end braces must be on the same column (for better indentation of the code) -e.g: -
if ($a != 2)
-{
-   $a = 2;
-}
-
if ($a != 2)
-{
-    $a = 2;
-    if($a == 2)
-    {
-       $c = 3;
-    }
-}
- -

Frequently Asked Questions

- -
-
- What coding standard does Dotkernel follow? + -
-

Dotkernel borrows its coding standard from the Zend Framework Coding Standard, with some exceptions described in this article.

-
-
-
- Tabs or spaces for indentation? + -
-

Dotkernel indents with tabs, not spaces.

-
-
-
- How should classes, interfaces, and filenames be named? + -
-

Classes start with the prefix Dot_ (e.g. Dot_Templates), interfaces end with the string "Interface" (e.g. Dot_Db_Interface), and all PHP files use the ".php" extension, with no fancy extensions like ".inc".

-
-
-
- How should curly braces be placed for control statements? + -
-

Every opening curly brace starts on its own new line after the statement, and its matching closing brace also goes on a new line, aligned in the same column as the opening brace, for better indentation of the code.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-database-naming-conventions-for-mysql.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-database-naming-conventions-for-mysql.html.twig deleted file mode 100644 index e7270b04..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-database-naming-conventions-for-mysql.html.twig +++ /dev/null @@ -1,79 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -Database naming conventions for tables and columns: - -Example of proper SQL file formatting and naming: -
CREATE TABLE IF NOT EXISTS `user`
- (
-   `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
-   `username` VARCHAR(255) NOT NULL,
-   `password` VARCHAR(25) NOT NULL,
-   `email` VARCHAR(100) NOT NULL,
-   `firstName` VARCHAR(255) NOT NULL,
-   `lastName` VARCHAR(255) NOT NULL,
-   `dateCreated` TIMESTAMP NOT  NULL DEFAULT  CURRENT_TIMESTAMP,
-   `userType` INT(11) NOT NULL AUTO_INCREMENT
-   `isActive` ENUM('0','1') NOT NULL DEFAULT '1',
-   PRIMARY KEY  (`id`),
-   UNIQUE KEY `username` (`username`),
-   UNIQUE KEY `email` (`email`)
-   CONSTRAINT `FK_user_userType` FOREIGN KEY(`userTypeId`) REFERENCES `userType`(`id`)
-        ON UPDATE CASCADE
-        ON DELETE CASCADE
- )
- ENGINE=InnoDB
- DEFAULT CHARSET=latin1
- AUTO_INCREMENT=1 ;
-
-Conclusion: -The names of database tables and columns must follow camelLetter as naming conventions.s - -

Frequently Asked Questions

- -
-
- Where do Dotkernel's database naming conventions come from? + -
-

They are borrowed from FaZend's "Rules of naming of database tables and columns," an open-source PHP framework based on Zend Framework.

-
-
-
- Should table names be singular or plural? + -
-

Singular table names only, for example user, category, product, order, orderProduct.

-
-
-
- How should foreign key columns be named? + -
-

A foreign key column takes the name of the referenced table plus the name of the referenced column. For example, referencing table admin's Id column produces a column named adminId.

-
-
-
- What naming pattern is used for CONSTRAINT names? + -
-

The pattern is FK_referencedTableName_tableName, for example CONSTRAINT `FK_admin_adminLogin`.

-
-
-
- What casing convention applies to table/column names and to SQL keywords? + -
-

Table and column names must follow camelLetter naming conventions, while SQL keywords such as SELECT and INT are capitalized.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.html.twig deleted file mode 100644 index 5d3ea89d..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-starting-with-mezzio-microframework-and-laminas-components.html.twig +++ /dev/null @@ -1,149 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

Dotkernel Light is the smallest complete Mezzio application that includes only the bare-bones essentials. Though simpler than Frontend, it's perfect for:

- - - -

Goal

- -

Dotkernel Light is the smallest complete Mezzio application, designed at the same time to be a fast-start example of using Mezzio microframework, as well as of using an entry-level version of Dotkernel Frontend. Primarily, its purpose is to present the novice developer with as few moving parts as possible. It's also a perfect starting point for the more advanced developer who wants full control of the platform's functionality.

- -

Light retains the modern architecture of Mezzio microframework and several Laminas components used in Dotkernel Frontend. The low number of out-of-box components encourages the active exploration of functionality required by your application. Basically, you add only the packages your application needs.

- -

Components and functionality

- -

We designed Dotkernel Light to be the smallest complete Mezzio application, a stripped-down version of Dotkernel Frontend. Just like Dotkernel Frontend, Dotkernel Light is built on top of the Mezzio microframework using Laminas components. The big difference is the limited features and thus the lower number of packages. This makes the learning curve of working with the repo considerably gentler.

- -

Currently, Light contains this functionality:

- - - -

Compared to Frontend, Light had these items removed:

- - - -

As expected, several packages used by Frontend are not required:

- - - -

Frequently Asked Questions

- -
-
- What is Dotkernel Light? + -
-

Dotkernel Light is the smallest complete Mezzio application, a PSR-15 pipeline, routing and templating, with nothing to strip out. It's a good base for a presentation site, an introduction to the Mezzio microframework architecture, or a starting point for a more complex project where you want full control over functionality.

-
-
-
- What is the goal of Dotkernel Light? + -
-

It's designed to be the smallest complete Mezzio application. It's a fast-start example of using the Mezzio microframework as well as an entry-level version of Dotkernel Frontend. It presents the novice developer with as few moving parts as possible, while still allowing the more advanced developer to have full control of the platform's functionality.

-
-
-
- What functionality does Dotkernel Light retain? + -
-

It keeps routing, templating, error handling, and tests and code quality checks.

-
-
-
- What was removed compared to Dotkernel Frontend? + -
-

Items removed include Doctrine and all database related stuff, sessions/cookies/flash messages, authentication/authorization, dependency injection, mail related stuff, navigation, CORS, forms/validators/input filters, the User module, the Contact module, the Plugin module, unused CSS/JS code, and outdated README instructions.

-
-
-
- Which packages are no longer required in Dotkernel Light? + -
-

Packages such as dotkernel/dot-authorization, dotkernel/dot-mail, dotkernel/dot-session, dotkernel/dot-navigation, dotkernel/dot-flashmessenger, laminas/laminas-form, mezzio/mezzio-cors, and several others used by Frontend are not required.

-
-
-
- -

Useful links

- - - -

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.html.twig deleted file mode 100644 index 3d6568b0..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-light-the-best-choice-for-your-presentation-site.html.twig +++ /dev/null @@ -1,215 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

- -

Dotkernel Light is the smallest complete Mezzio application and a good starting point for a project if you want to have full control over the functionality it contains. It can be expanded into something more complex with the integration of packages based on your requirements.

- -

Its out-of-box functionality is suitable for a presentation site:

- - - -

Presentation sites don't require features that are present in Dotkernel Frontend. The goal of Dotkernel Light is to have no clutter, so these features are removed:

- - - -

The goal of this article

- -

In this article we explore how to use Dotkernel Light for a simple presentation site. We will mention what files to focus on to teach you how to add more pages of content to your site and how to manage their assets.

- -

Adding new pages

- -

The first step is to add the new pages in src/Page/src/Controller/PageController.php. This means adding an Action function for each page, as seen below.

- -
    public function examplePageAction(): ResponseInterface
-    {
-        return new HtmlResponse(
-            $this->template->render('page::example-template')
-        );
-    }
- -

The url for the new page in this example is /page/example-page.

-
- -

Each page has its own template, so the next step is to create the template files in the src/Page/templates/page/ folder. For the example above, the src/Page/templates/page/example-template.html.twig file was created. We won't include the entire code here, just the basic building blocks. The content block is where your page copy goes.

- -
-{% verbatim %}
-{% extends '@layout/default.html.twig' %}
-
-{% block title %}Page Title{% endblock %}
-
-{% block page_title %}{% endblock %}
-
-{% block content %}
-    <div class="page-intro">
-        <div class="container">
-            <h2>Add title here!</h2>
-        </div>
-    </div>
-
-    <div>
-    Add cool content here!
-    </div>
-{% endblock %}
-{% endverbatim %}
-
- -

Make sure to check the header for any fonts your content requires.

-
- -

If you haven't already done so, make sure the npm is installed and running during your updates with npm run watch or run this command after the edits are completed npm run prod.

- -

The assets should be copied under the src/App/assets/ folder.
These are the default asset folders:

- - - -

Optional items

- -

Twitter and OpenGraph cards

- -

If you want to promote the pages on other platforms, a helpful item is the header section in the src/App/templates/layout/default.html.twig file. This is where the Twitter (X) and OpenGraph cards should be placed.

- -

Make sure to update all items based on your page content.

- -

In the example:

- - -
- -
-{% verbatim %}
-<!-- Twitter card -->
-<meta name="twitter:card" content="summary_large_image">
-<meta name="twitter:site" content="@example">
-<meta name="twitter:title" content="Page title">
-<meta name="twitter:description" content="Basic description">
-<meta name="twitter:image" content="{{ url('home') }}images/app/My-image.png">
-<meta name="twitter:image:alt" content="Image alt">
-
-<!-- OpenGraph card -->
-<meta property="og:title" content="Page title"/>
-<meta property="og:type" content="website"/>
-<meta property="og:url" content="{{ url('home') }}"/>
-<meta property="og:image" content="{{ url('home') }}images/app/My-image.png"/>
-<meta property="og:description" content="Basic description"/>
-{% endverbatim %}
- -

Top menu

- -

This menu is displayed on all of the pages, in the header. To edit it, go to src/App/templates/layout/default.html.twig and update the items under id="navbarHeader". You can use the below as an example.

- -
<div class="menu" id="navbarHeader">
-    <ul class="navbar-nav mr-auto">
-    <li class="nav-item">
-        <a class="nav-link" target="_blank" href="https://first.example.com/">First Link</a>
-    </li>
-    <li class="nav-item">
-        <a class="nav-link" target="_blank" href="https://second.example.com/">Second Link</a>
-    </li>
-    </ul>
-</div>
- -

You can also replace the nav-item class for the li elements with button-border for a link that looks more like a button.

-
- -

Footer

- -

To edit the footer on all of the pages, search for <footer class="app-footer"> in the src/App/templates/layout/default.html.twig template. We won't include an example here, since the content is usually basic HTML and CSS with twig elements already covered in this article.

- -

The result of your hard work

- -

Whew, well done! That's all there is to it.

- -

Now you should have a basic idea on how to work on a presentation site. You know how to expand the site with more pages, where to place the assets and how to promote the site.

- -

Frequently Asked Questions

- -
-
- What is Dotkernel Light designed for? + -
-

It's a good starting point for a project when you want full control over its functionality, and it easily grows into something more complex by integrating packages based on your requirements.

-
-
-
- What functionality does Dotkernel Light include out of the box? + -
-

Routing, Templating, Error handling, and Tests and code quality checks.

-
-
-
- What features are removed from Dotkernel Light compared to Dotkernel Frontend? + -
-

Everything related to the database, Sessions/Cookies/Flash messages, Authentication/Authorization, Dependency Injection, Mail related stuff, Navigation, CORS, Forms/Validators/InputFilters, the User module, the Contact module, and the Plugin module.

-
-
-
- How do you add a new page to a Dotkernel Light site? + -
-

Add an Action function for the page (e.g. examplePageAction()) in src/Page/src/Controller/PageController.php that renders a template, then create the matching template file in src/Page/templates/page/ (e.g. example-template.html.twig), with the page copy placed in its content block.

-
-
-
- Where should new page assets like fonts, images, JS, and CSS be placed? + -
-

Under src/App/assets/, in its default folders: src/App/assets/fonts, src/App/assets/images, src/App/assets/js, and src/App/assets/scss. Run npm run watch during edits, or npm run prod once the edits are completed.

-
-
-
- Where do you edit the top menu and footer that appear on every page? + -
-

Both live in src/App/templates/layout/default.html.twig: the top menu items are under id="navbarHeader", and the footer content is inside the footer element with class "app-footer".

-
-
-
- -

Useful links

- - -{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-on-nginx.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-on-nginx.html.twig deleted file mode 100644 index 95b2ee9c..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-on-nginx.html.twig +++ /dev/null @@ -1,79 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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 :-) - - -
    -
  1. Installed on a clean Centos 6.3 VPS : Nginx 1.4.1 , PHP 5.4.16 , PHP-FPM
  2. -
  3. Installed PHP modules: APC, GeoIP and such.
  4. -
  5. Installed PEAR and Zend Framework from http://code.google.com/p/zend/
  6. -
  7. Fine tune php.ini file: date, default charset, include path, etc
  8. -
  9. Create on a remote server a MySql database, allow permissions from Nginx server  IP to connect to it.
  10. -
  11. Now the fine part: editing the Nginx config ( in our case /etc/nginx/conf.d/default.conf) -
      -
    • Set server name: -
      server_name  nginx.dotkernel.net;
      -
    • -
    • Set document root , in the location / area -
      root   /var/www/html;
      -
    • -
    • Equivalent of the main .htaccess file is -
      try_files    $uri $uri/ /index.php;
      -This directive will send all requests to index.php file
    • -
    • Set the location for php directive -
          location ~ \.php$ {
      -           root           /var/www/html;
      -           fastcgi_pass   127.0.0.1:9000;
      -           fastcgi_index  index.php;
      -           fastcgi_param  SCRIPT_FILENAME  /var/www/html$fastcgi_script_name;
      -           fastcgi_param APPLICATION_ENV staging;
      -           include        fastcgi_params;
      -            }
      -In the lines above , you can change the value for APPLICATION_ENV into production or development
    • -
    • Protect configs folder from web access -
      #protect folders
      -location ~ ^/configs/ {
      -        deny all;
      - }
      -
    • -
    -
  12. -
  13.   Export a Dotkernel copy from SVN -
    svn export --force  http://v1.dotkernel.net/svn/trunk/ /var/www/html
    -
  14. -
  15. Edit application.ini to reflect the current settings and check if is  protected -http://nginx.dotkernel.net/configs/application.ini
  16. -
  17. And walla, site is running on Nginx       http://nginx.dotkernel.net/ -Admin login is disabled due to security reason.
  18. -
- -

Frequently Asked Questions

- -
-
- What server stack was used to test Dotkernel on Nginx? + -
-

A clean CentOS 6.3 VPS running Nginx 1.4.1, PHP 5.4.16, and PHP-FPM, with the APC and GeoIP PHP modules installed.

-
-
-
- What is the Nginx equivalent of the main .htaccess file? + -
-

The directive try_files $uri $uri/ /index.php;, which sends all requests to the index.php file.

-
-
-
- How is the configs folder protected from web access in Nginx? + -
-

By adding a location block matching ^/configs/ that returns deny all;.

-
-
-
- How was the Dotkernel codebase deployed onto the server? + -
-

It was exported directly from SVN using svn export --force http://v1.dotkernel.net/svn/trunk/ /var/www/html.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-reserved-variable-names-for-caching.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-reserved-variable-names-for-caching.html.twig deleted file mode 100644 index 2d0e4f1a..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-reserved-variable-names-for-caching.html.twig +++ /dev/null @@ -1,71 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. More info about router: http://www.dotkernel.com/docs/router-xml/ - -The router is cached as router. -

Auth / ACL Role -

-The object which authorizes user methods (aka Dot_Auth), used in all Dotkernel Applications, Dot_Auth uses /configs/acl/role.xml to define the users rights. - -The role.xml file is cached as acl_role. -

Menu

-The menu.xml from current module(what is a module?) -More about menu.xml : http://www.dotkernel.com/docs/menu-xml/ - -The menu is cached as admin_menu, frontend_menu. -

Options

-The options are the ones found in /configs/dots/ and have the following naming format: option_MODULE_CONTROLLER. - -MODULE is the current module and CONTROLLER is the current controller or "seo", so the options cache entries will look like: - -

Browser & OS

-The browser.xml and os.xml are used to identify the Browser and OS name, icon, and type. Theese XML files are located in /configs/useragent/. - -Browser & OS are cached as browser_xml and os_xml. - -Note: Be careful when changing the xml files / values to be cached as they remain cached. If there is no effect that means you must clear the cache or rewrite that value in cache. - -

Frequently Asked Questions

- -
-
- What cache key is used for the router? + -
-

The router, which loads routes settings from router.xml, is cached as router.

-
-
-
- What cache key stores the ACL role definitions? + -
-

The role.xml file (used by Dot_Auth, from /configs/acl/role.xml, to define user rights) is cached as acl_role.

-
-
-
- How is the menu cached? + -
-

The menu.xml from the current module is cached as admin_menu and frontend_menu.

-
-
-
- What naming pattern do cached options use? + -
-

option_MODULE_CONTROLLER, where MODULE is the current module and CONTROLLER is the current controller or "seo" — for example option_admin_Admin, option_frontend_Page, and option_admin_seo (seo is lowercase).

-
-
-
- What happens if you change browser.xml, os.xml, or other cached config files? + -
-

The change won't take effect on its own, since values like browser_xml and os_xml remain cached — you must clear the cache or rewrite that value in cache to see the change take effect.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-template-engine.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-template-engine.html.twig deleted file mode 100644 index d61b04b2..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-template-engine.html.twig +++ /dev/null @@ -1,25 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

- -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. Separates the manipulation of data (in the database as well as in PHP) from its final format, whether that format is HTML, XML or a formatted e-mail. The big advantage is that is allowing us to change the look and feel of a site quickly without having to delve immediately into a lot of PHP variable assignments and print statements. - -

Frequently Asked Questions

- -
-
- What is the Dotkernel Template Engine based on? + -
-

It's an implementation of the PHPLib Template engine for PHP5.

-
-
-
- What is the main benefit of using it? + -
-

It separates application code from the presentation layer, and separates data manipulation from its final format (HTML, XML, or a formatted e-mail), letting you change a site's look and feel quickly without delving into a lot of PHP variable assignments and print statements.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-version-1-0-in-action.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-version-1-0-in-action.html.twig deleted file mode 100644 index 4ec56353..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/dotkernel-version-1-0-in-action.html.twig +++ /dev/null @@ -1,56 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -
- -Dotkernel is the DotBoost's in-house developed framework, based on Zend Framework. -Dotkernel is at version 1.0 , released under Open Software License (OSL 3.0) , and is built on top of Zend Framework.
-
Dotkernel is using a simplified MVC (Model-View-Controller) architecture, easy to learn by beginner and intermediate level programmers. -It has eliminated the complexity of Zend Framework by using a different approach of how the web request are handled. - -
-

From Zend Framework, Dotkernel is using only the necessary classes:

- -
- -

Frequently Asked Questions

- -
-
- Who developed Dotkernel and what is it built on? + -
-

Dotkernel is DotBoost's in-house developed framework, built on top of Zend Framework. At the time of this article it was at version 1.0, released under the Open Software License (OSL 3.0).

-
-
-
- What architecture does Dotkernel use? + -
-

A simplified MVC (Model-View-Controller) architecture that's easy to learn for beginner and intermediate level programmers, achieved by eliminating much of Zend Framework's complexity through a different approach to handling web requests.

-
-
-
- What does Zend_Config provide in Dotkernel? + -
-

It provides multiple backends for configuration storage and a simple, intuitive, object-oriented interface for accessing it. Dotkernel stores configuration as simple PHP arrays, which are then wrapped by Zend_Config.

-
-
-
- What role do Zend_Db and Zend_Db_Table play? + -
-

Zend_Db_Table is a classic implementation of the Table Data Gateway and Row Data Gateway design patterns, allowing easy and intuitive access to database tables and rows, as well as an entry point for custom business logic surrounding the data.

-
-
-
- What are Zend_Mail and Zend_Registry used for? + -
-

Zend_Mail composes and sends both text and MIME-compliant multipart e-mail, via the default Zend_Mail_Transport_Sendmail transport or via Zend_Mail_Transport_Smtp. Zend_Registry is a container for storing objects and values in the application space, so the same object is always available throughout the application, as an alternative to using global storage.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.html.twig deleted file mode 100644 index 1fd28238..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/forcing-utf8-connections-and-character-set-in-mysql.html.twig +++ /dev/null @@ -1,38 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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.cnf file -and add the below lines: -
character_set_server=utf8
-skip-character-set-client-handshake
-If you are interested in better performance, add the below line: -
collation_server=utf8_general_ci
-If you are interested in better sorting order, add the below line instead: -
collation_server=utf8_unicode_ci
- -

Frequently Asked Questions

- -
-
- Why force UTF8 at the MySQL server level instead of relying on each client? + -
-

Because you can't control all the scripts that connect to your database (for instance the mysql command line or mysqldump), so forcing the server's collation and character set to UTF8 in my.cnf guarantees it regardless of the connecting client.

-
-
-
- What two lines enable UTF8 for all connections in my.cnf? + -
-

character_set_server=utf8 and skip-character-set-client-handshake.

-
-
-
- What's the difference between the two suggested collation settings? + -
-

collation_server=utf8_general_ci is recommended for better performance, while collation_server=utf8_unicode_ci is recommended instead if better sorting order matters more.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/geoip-city-removed-from-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/geoip-city-removed-from-dotkernel.html.twig deleted file mode 100644 index ab2c5e68..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/geoip-city-removed-from-dotkernel.html.twig +++ /dev/null @@ -1,83 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -In the newest version we have removed the GeoIP City integration. The City database on GeoIP 1.1.0 extension was causing a segmentation fault, which resulted in crashing or outputting an error instead of executing the PHP Code. - - - -This error occurs due to the extension incompatibility with the database(dat) file. If you have an older Dotkernel version and version >=1.1.0 of GeoIP you might get this error. - -On some operating systems Apache will respond your request with HTTP Status Code 200 (OK) and a web page containing the text below. - -Error result: -
The server encountered an internal error or
- misconfiguration and was unable to complete
- your request.
-The error might not be logged as the error is generated the GeoIP extension and not by your PHP code. - -If you don't want to use GeoIP City and just want to get rid of the error go to - -
if(geoip_db_avail(GEOIP_CITY_EDITION_REV0))
-{
-    $info = explode(" ",geoip_database_info(GEOIP_CITY_EDITION_REV0));
-    $return = $info.' '.Dot_Kernel::TimeFormat($info);
-}
- -
if(function_exists('geoip_db_avail') && geoip_db_avail(GEOIP_CITY_EDITION_REV0)
-&& 'unknown' == $country)
-{
-    //if GeoIPCity.dat file exists
-    $record = geoip_record_by_name($ip);
-    if(!empty($record))
-    {
-        $countryCode = $record['country_code'];
-        $countryName = $record['country_name'];
-        $country = $countryCode != false  ? $countryCode : 'unknown';
-        $country = $countryName != false  ? $countryName : 'NA';
-    }
-}
-Theese changes were also made in the latest version of Dotkernel. You should use this fix only if you don't intend to use GeoIP City. - -We recommend special coding for using GeoIP City and in the GeoIP extension in general. - -Note that GeoIP is an obsolete extension, Maxmind recommends using GeoIP2. - -

Frequently Asked Questions

- -
-
- Why was GeoIP City removed from Dotkernel? + -
-

The City database on GeoIP extension version 1.1.0 and above was causing a segmentation fault, which resulted in crashing or outputting an error instead of executing the PHP code.

-
-
-
- Who is affected by this error? + -
-

Users running an older Dotkernel version together with GeoIP extension version 1.1.0 or newer, since the error comes from the extension's incompatibility with the database (dat) file.

-
-
-
- How does the error show up to visitors? + -
-

On some operating systems, Apache still responds with HTTP Status Code 200 (OK) but with a page containing a generic "internal error or misconfiguration" message, and the error might not be logged since it's generated by the GeoIP extension rather than by the PHP code.

-
-
-
- How can you get rid of the error if you don't need GeoIP City? + -
-

Comment out or remove the relevant lines in Dotkernel/admin/System.php (the Admin/System model) and in library/Dot/GeoIp.php (the Dot_GeoIp class). These changes were also made in the latest version of Dotkernel, but this fix should only be used if you don't intend to use GeoIP City.

-
-
-
- What does the article recommend going forward? + -
-

It recommends special coding for GeoIP City and for the GeoIP extension in general, and notes that GeoIP is an obsolete extension, with Maxmind recommending GeoIP2 instead.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/geoip-ip-address-location-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/geoip-ip-address-location-in-dotkernel.html.twig deleted file mode 100644 index 1b451f9e..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/geoip-ip-address-location-in-dotkernel.html.twig +++ /dev/null @@ -1,127 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. When a person visits your website, GeoIP can determine which country, region, city, postal code, area code the visitor is coming from. - -Dotkernel uses GeoIP to get user statistics by country. The main file for GeoIP is library/Dot/Geoip.php - -Let’s explain what function getCountryByIp does: - -Next there are 4 if/else statements: - -1. Line 43 – 57: mod_geoip PECL extension is not installed - -2. Line 58 – 63: mod_geoip is installed and GeoIP.dat file exists on the server: geoip_db_avail(GEOIP_COUNTRY_EDITION) - -3. Line 64 – 73 : mod_geoip is installed, GeoIP.dat file does not exist, but GeoIpCity.dat exists - -4. Line 74 – 88 : mod_geoip is installed, but neither GeoIP.dat or GeoIPCity.dat exist - -
 line="32">/**
- * Get the country by IP
- * Return an array with : short name, like 'us' and long name, like 'United States'
- * @access public
- * @param string $ip
- * @return array
- */
-public function getCountryByIp($ip)
-{
-    $session = Zend_Registry::get('session');
-    $country = array(0 => 'unknown',1 => 'NA');
-    if(extension_loaded('geoip') == FALSE)
-    {
-        // GeoIp extension is not active
-        $api = new Dot_Geoip_Country();
-        $geoipPath = 'externals/geoip/GeoIP.dat';
-        if(file_exists($geoipPath))
-        {
-            $country = $api->getCountryByAddr($geoipPath, $ip);
-        }
-        else
-        {
-            $session->message = $this->option->warningMessage->modGeoIp;
-            $session->message = 'warning';
-        }
-    }
-    elseif(geoip_db_avail(GEOIP_COUNTRY_EDITION))
-    {
-        //if GeoIP.dat file exists
-        $country = geoip_country_code_by_name ($ip);
-        $country = geoip_country_name_by_name($ip);
-    }
-    elseif(geoip_db_avail(GEOIP_CITY_EDITION_REV0))
-    {
-        //if GeoIPCity.dat file exists
-        $record = geoip_record_by_name($ip);
-        if(!empty($record))
-        {
-            $country = $record;
-            $country = $record;
-        }
-    }
-    else
-    {
-        // GeoIp extension is not active
-        $api = new Dot_Geoip_Country();
-        $geoipPath = 'externals/geoip/GeoIP.dat';
-        if(file_exists($geoipPath))
-        {
-            $country = $api->getCountryByAddr($geoipPath, $ip);
-        }
-        else
-        {
-            $session->message['txt'] = $this->option->warningMessage->modGeoIp;;
-            $session->message = 'warning';
-        }
-    }
-    return $country;
-}
-}
-
- -

Frequently Asked Questions

- -
-
- What is GeoIP and what does it let Dotkernel do? + -
-

GeoIP is MaxMind's proprietary technology for IP geolocation data. Dotkernel uses it to get user statistics by country, determining a visitor's country, region, city, postal code, or area code in real time.

-
-
-
- Where does the GeoIP logic live in Dotkernel? + -
-

In library/Dot/Geoip.php, inside the getCountryByIp function.

-
-
-
- What does getCountryByIp do when the mod_geoip PECL extension isn't installed? + -
-

It falls back to the existing externals/geoip/GeoIP.dat file bundled with Dotkernel (downloadable from MaxMind if not present).

-
-
-
- What happens when mod_geoip is installed and GeoIP.dat exists? + -
-

It uses the built-in PHP functions geoip_country_code_by_name and geoip_country_name_by_name to get the country code and name.

-
-
-
- What if GeoIP.dat is missing but GeoIPCity.dat exists, or neither file exists? + -
-

If GeoIPCity.dat exists, it uses the PHP function geoip_record_by_name to get the country code and name. If neither .dat file exists, it behaves the same as when mod_geoip isn't installed, falling back to externals/geoip/GeoIP.dat.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.html.twig deleted file mode 100644 index a394ab25..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/handling-and-logging-errors-with-dot-errorhandler-and-dot-log.html.twig +++ /dev/null @@ -1,130 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -This can be considered as a guide on how the dot-errorhandler was made and how it's meant to be used. - - - -  - -As a first note Dot Error Handler provides two kinds of error handlers: - -Both error handlers have factories for an easier usage with the Container. - -  - -To use dot-error handler in your project run the following command: composer require dotkernel/dot-errorhandler. - -  -

The Config Provider

-When the dot-errorhandler config provider is invoked the following configuration is returned. -
,
-        'factories' =>
-    ],
-];
-Both the error handlers have the factories registered, and an alias to switch between them is added. - -As a fallback case the plain error handler is selected by default and can be overwritten through the config file. - -  -

Configuration

-IMPORTANT NOTES: - -The steps to configuring are the following: - -  - -To use the logging error handler the following config must be used. - -  - -config/autoload/dot-errorhandler.global.php -
use Dot\ErrorHandler\ErrorHandlerInterface;
-use Dot\ErrorHandler\LogErrorHandler;
-
-return
-    ],
-    'dot-errorhandler' =>
-];
-The logger key in dot-error handler should reflect your logger configuration in config/autoload/log.global.php - -To use the default logger an out-of-the-box config was provided within the error handler's config directory. -

Usage / Triggering errors

-The tests we have made were the following: - -  - -

Frequently Asked Questions

- -
-
- What two kinds of error handlers does dot-errorhandler provide? + -
-

A plain ErrorHandler, which is a copy of Zend Expressive's Zend\Stratigility\Middleware\ErrorHandler (copied because that class is final), and a logging LogErrorHandler, which is the same but with added logging support via the container. Both have factories registered for easier use with the Container.

-
-
-
- How do you install dot-errorhandler? + -
-

Run composer require dotkernel/dot-errorhandler.

-
-
-
- Which error handler is used by default? + -
-

The plain error handler is selected by default as a fallback, and this can be overwritten through the config file, which also registers factories for both handlers plus an alias to switch between them.

-
-
-
- What must already be in place before configuring the logging error handler? + -
-

A configured logger as described in the "Logging with dot-log in Zend Expressive and Dotkernel" article, with the logger named default_logger as used in the package's config example.

-
-
-
- Why might whoops interfere when testing dot-errorhandler? + -
-

dot-errorhandler is meant to be a silent logger for staging and production environments. To actually test it, development mode should be disabled, otherwise whoops will catch the errors and show them to the developer instead.

-
-
-
- How were errors triggered to test the error handlers? + -
-

By throwing Exceptions, the most common case, and by raising warnings/errors such as dividing a number by zero (e.g. 16/0) or casting an array to a string.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/highcharts-integration-in-dotkernel-1-6-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/highcharts-integration-in-dotkernel-1-6-0.html.twig deleted file mode 100644 index e95a4704..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/highcharts-integration-in-dotkernel-1-6-0.html.twig +++ /dev/null @@ -1,77 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Integrating a new charting library in the latest version of Dotkernel (1.6.0) we offer a new experience with this new intuitive and interactive charts. - - - -Also in admin we made some samples using highcharts. These samples includes an pie chart (with a small custom feature), an column chart and the last one is an line chart. -

-You can find highcharts library in the externals directory. - -Take a quick view on the code to see how highcharts are working. -
chart = new Highcharts.Chart({
-        chart: {
-            renderTo: elementId,
-            type: 'line',
-            plotBackgroundColor: null,
-            plotBorderWidth: 0,
-        },
-        credits: {
-            enabled: false
-        },
-        title: {
-            text: ''
-        },
-        colors: colors,
-        tooltip: {
-            formatter: function() {
-                    return '' + this.series.name + ' ' + this.x + '
-' + 'Total logins: ' + this.y;
-            }
-        },
-        yAxis: {
-            title: {
-                text: 'Logins count'
-            },
-            min: 0
-        },
-        xAxis: {
-            categories: ['1','2','3','4','5','6','7','8','9','10','11','12',
-                                     '13','14','15','16','17','18','19','20','21','22',
-                                     '23','24','25','26','27','28','29','30','31'],
-            labels: {
-                rotation: -45,
-                align: 'right',
-                style: {
-                    font: 'normal 10px Verdana, sans-serif'
-                }
-            }
-        },
-        series: timeActivity
-    });
-  - -

Frequently Asked Questions

- -
-
- What charting library was integrated in Dotkernel 1.6.0? + -
-

Highcharts was integrated in Dotkernel 1.6.0, offering a new, intuitive and interactive charting experience.

-
-
-
- What sample charts were added to the admin? + -
-

The admin includes samples made with Highcharts: a pie chart (with a small custom feature), a column chart, and a line chart.

-
-
-
- Where can the Highcharts library be found in a Dotkernel project? + -
-

The Highcharts library is located in the externals directory of the project.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/how-to-build-this-website-starting-from-dotkernel-light.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/how-to-build-this-website-starting-from-dotkernel-light.html.twig deleted file mode 100644 index 0c4fedf6..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/how-to-build-this-website-starting-from-dotkernel-light.html.twig +++ /dev/null @@ -1,45 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

This blog didn't start as a blog. It started as a bare Dotkernel Light installation — a minimal starter project built on Mezzio, with Twig for templating, FastRoute for routing, PSR-7 via Laminas Diactoros, and a PSR-11 container using Laminas Service Manager. No database, no posts, just the skeleton for a simple website.

- -

Starting from Dotkernel Light

-

The setup itself is a single clone away:

-
git clone https://github.com/dotkernel/light.git dotkernel-light
-

At that point you have routing, templating and a working request lifecycle, but nothing to persist. Dotkernel Light is deliberately unopinionated about storage — it's up to the project to add a persistence layer if it needs one.

- -

Adding Doctrine, following Tutorial 101

-

This project needed one: categories, authors, tags, and posts, all queryable and paginated. Rather than wiring Doctrine ORM in from scratch, the official Tutorial 101 was followed, which walks through exactly this on top of a fresh Dotkernel Light install:

-
    -
  1. Installing Doctrine ORM and wiring it into the container.
  2. -
  3. Defining entities and generating migrations to create the schema.
  4. -
  5. Loading fixture data and building the repositories/queries needed to list and display it.
  6. -
-

Following that path is what turned this project's Category, Author, Tag and Post entities, their migrations, and the repositories behind every listing and category page on this site into what they are now.

- -

Where the data comes from

-

The actual content — every article, its author, its category and tags — is authored as data in src/App/src/Fixture/articles_cleaned.json and loaded into the database with php bin/doctrine-fixtures. That file wasn't written from scratch: it was built from what already existed in dotkernel.com's own database, cleaned up and reshaped into fixture data so it could be loaded into this project's schema. Doctrine takes care of turning that into rows; the entities and repositories added while following Tutorial 101 take care of turning those rows back into the pages you're browsing right now.

- -

Frequently Asked Questions

- -
-
- What is Dotkernel Light? + -
-

A minimal Mezzio-based starter project for building a simple website — routing (FastRoute), templating (Twig) and PSR-7 (Laminas Diactoros) out of the box, without a database layer.

-
-
-
- Why was Doctrine added on top? + -
-

Dotkernel Light doesn't include persistence by default. This blog needed categories, authors, tags and posts stored and queried from a database, so Doctrine ORM was added following the official Tutorial 101.

-
-
-
- Where does the article content come from? + -
-

From src/App/src/Fixture/articles_cleaned.json, loaded into the database via php bin/doctrine-fixtures.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/how-to-group-log-files-by-date-using-dot-log.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/how-to-group-log-files-by-date-using-dot-log.html.twig deleted file mode 100644 index 0fb1834f..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/how-to-group-log-files-by-date-using-dot-log.html.twig +++ /dev/null @@ -1,193 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- - - -

With a little help from PHP's date function, Version 3.1.1 takes this one step further, though. It adds the ability to use datetime formatter strings right in the stream option of your log writer. It also fixes an issue where caching dot-log configs caused logs to be written to the same file, instead of being grouped by date.

- -

Prerequisites

- -

You will need dot-log installed and configured inside your application. If it's not installed, you can install it by following the steps described here. Else, make sure you're using the latest version of dot-log by running composer update dotkernel/dot-log.

- -

As always, we strongly suggest you to keep your packages updated. Allthough, if your application logs messages in a single file with a static name (eg: log/dk.log), you can skip the rest of this article - logging will work as before.

- -

Configuring the logger with Dotkernel

- -

Your application should already have a config/autoload/error-handling.global.php file, similar to this:

- -
<?php
-
-return [
-    'dot-errorhandler' => [
-        'loggerEnabled' => true,
-        'logger' => 'dot-log.default_logger'
-    ],
-    'dot_log' => [
-        'loggers' => [
-            'default_logger' => [
-                'writers' => [
-                    'FileWriter' => [
-                        'name' => 'stream',
-                        'priority' => \Laminas\Log\Logger::ALERT,
-                        'options' => [
-                            'stream' => sprintf('%s/../../log/error-log-%s.log', __DIR__, date('Y-m-d')),
-                            // explicitly log all messages
-                            'filters' => [
-                                'allMessages' => [
-                                    'name' => 'priority',
-                                    'options' => [
-                                        'operator' => '>=',
-                                        'priority' => \Laminas\Log\Logger::EMERG,
-                                    ],
-                                ],
-                            ],
-                            'formatter' => [
-                                'name' => \Laminas\Log\Formatter\Json::class,
-                            ],
-                        ],
-                    ],
-                ],
-            ],
-        ],
-    ],
-];
-
- -

Inside that file, locate every instance of your log writers by navigating to: dot_log->loggers->default_logger->writers. For each writer, you'll find a stream option containing the path to your log file. If your stream config looks like this:

- -

'stream' => sprintf('%s/../../log/error-log-%s.log', __DIR__, date('Y-m-d'))

- -

replace it with:

- -

'stream' => __DIR__ . '/../../log/error-log-{Y}-{m}-{d}.log'

- -

The last step is to clear config cache using the command:

- -

php bin/clear-config-cache.php

- -

If the path to your log files contains other date format specifiers, make sure you adapt it accordingly. A complete list of the specifiers can be found here.

- -

Configuring the logger without Dotkernel

- -

Locate dot-log configs in your application (probably /config/autoload/log.global.php). They should look similar to this:

- -
<?php
-
-return [
-    'dot_log' => [
-        'loggers' => [
-            'my_logger' => [
-                'writers' => [
-                    'FileWriter' => [
-                        'name' => 'FileWriter',
-                        'priority' => \Laminas\Log\Logger::ALERT,
-                        'options' => [
-                            'stream' => __DIR__ . '/../../log/dk.log',
-                            'filters' => [
-                                'allMessages' => [
-                                    'name' => 'priority',
-                                    'options' => [
-                                        'operator' => '>=',
-                                        'priority' => \Laminas\Log\Logger::EMERG,
-                                    ]
-                                ],
-                            ],
-                        ],
-                    ],
-                    // Only warnings
-                    'OnlyWarningsWriter' => [
-                        'name' => 'stream',
-                        'priority' => \Laminas\Log\Logger::ALERT,
-                        'options' => [
-                            'stream' => __DIR__ . '/../../log/warnings_only.log',
-                            'filters' => [
-                                'warningOnly' => [
-                                    'name' => 'priority',
-                                    'options' => [
-                                        'operator' => '==',
-                                        'priority' => \Laminas\Log\Logger::WARN,
-                                    ],
-                                ],
-                            ],
-                        ],
-                    ],
-                    // Warnings and more important messages
-                    'WarningOrHigherWriter' => [
-                        'name' => 'stream',
-                        'priority' => \Laminas\Log\Logger::ALERT,
-                        'options' => [
-                            'stream' => __DIR__ . '/../../log/important_messages.log',
-                            'filters' => [
-                                'importantMessages' => [
-                                    'name' => 'priority',
-                                    'options' => [
-                                        // note, the smaller the priority, the more important is the message
-                                        // 0 - emergency, 1 - alert, 2- error, 3 - warn. .etc
-                                        'operator' => '<=',
-                                        'priority' => \Laminas\Log\Logger::WARN,
-                                    ],
-                                ],
-                            ],
-                        ],
-                    ],
-                ],
-            ],
-        ],
-    ],
-];
- -

Inside that file, locate every instance of your log writers by navigating to: dot_log->loggers->my_logger->writers. For each writer, you'll find a stream option containing the path to your log file. If your stream config looks like this:

- -

'stream' => sprintf('%s/../../log/dk-%s.log', __DIR__, date('Y-m-d'))

- -

replace it with:

- -
'stream' => __DIR__ . '/../../log/error-log-{Y}-{m}-{d}.log',
- -

If the path to your log files contains other date format specifiers, make sure you adapt it accordingly. A complete list of the specifiers can be found here.

- -

Make sure you clear your application's config before usage.

- -

Frequently Asked Questions

- -
-
- What does dot-log Version 3.1.1 add? + -
-

Version 3.1.1 adds the ability to use datetime formatter strings directly in the stream option of your log writer, and it fixes an issue where caching dot-log configs caused logs to be written to the same file instead of being grouped by date.

-
-
-
- How do I make sure I'm using the fix in Version 3.1.1? + -
-

Make sure you're using the latest version of dot-log by running composer update dotkernel/dot-log.

-
-
-
- Do I need to change anything if my logs are already written to a single static file? + -
-

No. If your application logs messages in a single file with a static name (e.g. log/dk.log), you can skip the rest of the article - logging will work as before.

-
-
-
- How do I group log files by date when using Dotkernel? + -
-

In config/autoload/error-handling.global.php, locate every log writer by navigating to dot_log->loggers->default_logger->writers and replace a stream value like sprintf('%s/../../log/error-log-%s.log', __DIR__, date('Y-m-d')) with __DIR__ . '/../../log/error-log-{Y}-{m}-{d}.log'.

-
-
-
- How do I group log files by date without Dotkernel? + -
-

In your dot-log config (e.g. /config/autoload/log.global.php), locate every writer under dot_log->loggers->my_logger->writers and replace a dynamic stream value such as sprintf('%s/../../log/dk-%s.log', __DIR__, date('Y-m-d')) with a formatter string like __DIR__ . '/../../log/error-log-{Y}-{m}-{d}.log'.

-
-
-
- What must I do after changing the stream configuration? + -
-

Clear the config cache. In a Dotkernel application, run php bin/clear-config-cache.php; in a non-Dotkernel setup, make sure you clear your application's config before usage.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.html.twig deleted file mode 100644 index 86c66b39..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/how-to-set-a-persistent-connection-to-database-with-zend-framework-zend-db-adapter.html.twig +++ /dev/null @@ -1,37 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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.ini ( Dotkernel approach as an example) - -Do not try something like below, will not work: -
database.params.options.PDO::ATTR_PERSISTENT = TRUE
-  - -Instead , use the below line -
database.params.persistent = TRUE
- -

Frequently Asked Questions

- -
-
- Where do you configure a persistent database connection in a Dotkernel project? + -
-

The option should be added in application.ini, as shown in the Dotkernel approach used as an example in the article.

-
-
-
- What Zend_Db configuration line should NOT be used for a persistent connection? + -
-

database.params.options.PDO::ATTR_PERSISTENT = TRUE should not be used - the article states that it will not work.

-
-
-
- What's the correct line to enable a persistent connection? + -
-

Use database.params.persistent = TRUE instead.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/how-to-use-alerts-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/how-to-use-alerts-in-dotkernel.html.twig deleted file mode 100644 index 9c6f719d..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/how-to-use-alerts-in-dotkernel.html.twig +++ /dev/null @@ -1,105 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -The Dot_Alert class resembles with Dot_Email, the alerts, like a mail message, have at least the sender, a subject, a destination and a message and it can also be sent. - - - -In Dotkernel we use the Alerts for notifying the developer that an email was not sent successfully. - -In this case we kept the message in the dots.xml file -
-
-    
-        
-             SMTP Error on {SITE_NAME} 
-            
-SMTP Error on {SITE_NAME}
-We were unable to send SMTP email
----------------------------------
-Caught exception: {E_CLASS}
-Message:  {E_MESSAGE}
-To Email: {TO_EMAIL}
-From Email: {FROM_EMAIL}
-Date: {DATE_NOW}
----------------------------------
-            
-        
-    
-
-
-If you're not familiar with the dots.xml, you should see this article. - -First the message will be fetched from the xml file, we already have it in $this->option -
-$subject = $this->option->alertMessages->email->subject;
-$message = $this->option->alertMessages->email->message;
-Second, we get the destination recipient (in this case the developers e-mail addresses) -
-$devEmails = explode(',', $this->settings->devEmails);
-
-
-As you can see the Alert messages contains {VARIABLES} called details in the Alert system. Now we will prepare the details: -
$details = array(
-    'e_class' => get_class($e),
-    'site_name' => $this->seoOption->siteName,
-    'site_url' => $registry->configuration->website->params->url,
-    'e_message' => $e->getMessage(),
-    'to_email' => implode(',', $this->_to),
-    'from_email' => $this->getFrom(),
-    'date_now' => date('F dS, Y h:i:s A'),
-);
-Note that $e is a caught exception, this exception is thrown when the e-mail send process fails. -Now that we have it all, let's create an alert: -
$alert = new Dot_Alert();
-This is just an empty alert, we will now set the sender, the subject and the message, the sender is set on sending -
$alert->addHeader( "From: " . $this->settings->siteEmail);
-$alert->addHeader( "Reply-To:" . $this->settings->siteEmail );
-$alert->addHeader( "X-Mailer: PHP/" . phpversion() ) ;
-
$alert->setTo($devEmails);
-$alert->setSubject($subject);
-$alert->setContent($message);
-Our message doesn't look that good, the setDetails method will replace our {VARIABLES} within subject and message with actual data -
$alert->setDetails($details);
-Everything is great, we can now send our alert: -
$alert->send();
- -

Frequently Asked Questions

- -
-
- What is a Dot_Alert used for? + -
-

Alerts (Dot_Alert's) are e-mails usually sent to the site developers using PHP's mail() function. They shouldn't be used to send regular mail - they only notify the developer that something is wrong.

-
-
-
- What does the Dot_Alert class resemble, and what does an alert contain? + -
-

Dot_Alert resembles Dot_Email. Like a mail message, an alert has at least a sender, a subject, a destination and a message, and it can be sent.

-
-
-
- What's an example use of Alerts in Dotkernel? + -
-

Dotkernel uses Alerts to notify the developer when an email was not sent successfully, with the message kept in the dots.xml file under the alertMessages section.

-
-
-
- How are the {VARIABLES} in an alert message replaced with real data? + -
-

A $details array is prepared (e.g. e_class, site_name, site_url, e_message, to_email, from_email, date_now), and the alert's setDetails() method replaces the {VARIABLES} placeholders in the subject and message with that data.

-
-
-
- What are the steps to build and send an alert? + -
-

Create a new Dot_Alert(), add headers such as From, Reply-To and X-Mailer, then call setTo(), setSubject() and setContent(), then setDetails() to fill in the placeholders, and finally call send().

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.html.twig deleted file mode 100644 index 9b61c3cf..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/implementing-the-new-password-hashing-api-from-php-5-5-in-dotkernel.html.twig +++ /dev/null @@ -1,72 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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.8.0 , starting from revision 799. - -See more on that matter here - - - -Since those 4 new functions are available only since PHP 5.5 , we used the Password Compatibility library. - -The minimum PHP version in order to run Dotkernel was raised to PHP 5.3.8 . - -  - -How to apply this refactor to older Dotkernel systems - -
ALTER TABLE `admin` CHANGE `password` `password` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL;
-
ALTER TABLE `user` CHANGE `password` `password` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL;
- -  - -  - -  - -

Frequently Asked Questions

- -
-
- Why was Dotkernel refactored to use the new PHP 5.5 Password Hashing API? + -
-

To use the new Password Hashing functions introduced in PHP 5.5 and unify all password related functions used for both admin and users, Dotkernel's codebase was refactored in version 1.8.0, starting from revision 799.

-
-
-
- How can PHP versions older than 5.5 use these new hashing functions? + -
-

Since the 4 new functions are only available since PHP 5.5, Dotkernel used the Password Compatibility library (ircmaxell/password_compat).

-
-
-
- What is the minimum PHP version required after this change? + -
-

The minimum PHP version required to run Dotkernel was raised to PHP 5.3.8.

-
-
-
- What steps are needed to apply this refactor to an older Dotkernel system? + -
-

Change the password column definition in the admin and user tables via ALTER TABLE, remove the settings.admin.salt entry from application.ini, apply the provided patch, and run the conversion script described in Console/Controller.php at line 47.

-
-
-
- What happens to existing admin passwords during the upgrade? + -
-

Admin passwords cannot be converted automatically, so they need to be recreated manually.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.html.twig deleted file mode 100644 index 87eee31d..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-5-6-on-windows.html.twig +++ /dev/null @@ -1,91 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. -

-If your output is false than you have to download an php_geoip.dll file correctly compiled for your zend server version.
-
-For Zend Server 5.6 you can find geoip extension here:
-
-http://windows.php.net/downloads/pecl/releases/geoip/1.0.8/
-
-As you can see there are several files for different architectures and compilers
-
-** NOTE: you can find on what architecture your php is built and the compiler used with phpinfo();
-
- - - - - - - - - - -
CompilerMSVC9 (Visual C++ 2008)
Architecturex86
-After you have downloaded the php_geoip.dll you have to copy the file in: - -C:\YOUR_LOCATION\Zend\ZendServer\lib\phpext\ - -Now you have to go in your Zend Server interface, restart php, turn on geoip extension from Server Setup -> Extension and restart php again. - -If you get an error loading geoip extension than you picked the wrong dll and you have to try the other ones. - -Otherwise your geoip extension is loaded and your test script from the beginning will output "true". - -The next step is to download some geoip databases from maxmind. - -Copy those .dat files in - -C:\YOUR_LOCATION\Zend\ZendServer\bin\ - -And now you can use geoip functions on your Zend Server 5.6 -
-

Frequently Asked Questions

- -
-
- How do you test if the php_geoip extension is available on Zend Server? + -
-

Create a PHP file with var_dump(function_exists('geoip_database_info'));. It outputs true if the extension is available, or false if it isn't.

-
-
-
- Where can you download the php_geoip extension for Zend Server 5.6? + -
-

From http://windows.php.net/downloads/pecl/releases/geoip/1.0.8/, where several php_geoip.dll files are available for different architectures and compilers.

-
-
-
- How do you know which php_geoip.dll file to download? + -
-

You can find your PHP's architecture and the compiler it was built with by running phpinfo().

-
-
-
- Where does the php_geoip.dll file need to be copied? + -
-

Into C:\YOUR_LOCATION\Zend\ZendServer\lib\phpext\.

-
-
-
- How do you enable the geoip extension after copying the dll? + -
-

Go into the Zend Server interface, restart PHP, turn on the geoip extension from Server Setup -> Extension, and restart PHP again. If you get an error loading the extension, you picked the wrong dll and should try another one.

-
-
-
- What else is needed besides the extension itself? + -
-

You also need to download geoip databases from MaxMind, such as geoIP.dat, geoIPCity.dat, and geoIPOrganization.dat, and copy those .dat files into C:\YOUR_LOCATION\Zend\ZendServer\bin\.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.html.twig deleted file mode 100644 index 4725a0e1..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/installing-geoip-extension-in-zend-server-6-on-windows.html.twig +++ /dev/null @@ -1,42 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -As an update to the post Installing GeoIP extension in Zend Server 5.6 on Windows , for Zend Server 6.1 , you can do the following: -
    -
  1. Download the php_geoip-1.0.8-5.4-nts-vc9-x86.zip file from http://windows.php.net/downloads/pecl/releases/geoip/1.0.8/
  2. -
  3. From the archived file, copy the file php_geoip.dll to ZEND_PATH\Zend\ZendServer\lib\phpext\
  4. -
  5.  Open php.ini file, from the location: ZEND_PATH\Zend\ZendServer\etc\php.ini
  6. -
  7.  Add the below line at the end of the file extension=php_geoip.dll
  8. -
  9. Save the file and "Restart PHP" :-))  from Zend Server GUI
  10. -
  11. follow the step from the previous article in order to test GeoIP integration , and download the Geoip*.dat files
  12. -
- -

Frequently Asked Questions

- -
-
- Which php_geoip file should be downloaded for Zend Server 6.1? + -
-

Download the php_geoip-1.0.8-5.4-nts-vc9-x86.zip file from http://windows.php.net/downloads/pecl/releases/geoip/1.0.8/.

-
-
-
- Where does the php_geoip.dll go on Zend Server 6.1? + -
-

Copy the php_geoip.dll file from the archive to ZEND_PATH\Zend\ZendServer\lib\phpext\.

-
-
-
- How is the extension enabled on Zend Server 6.1? + -
-

Open the php.ini file at ZEND_PATH\Zend\ZendServer\etc\php.ini, add the line extension=php_geoip.dll at the end of the file, save it, and click "Restart PHP" from the Zend Server GUI.

-
-
-
- How do you verify GeoIP is working after this setup? + -
-

Follow the same steps described in the previous article (Installing GeoIP extension in Zend Server 5.6 on Windows) to test the GeoIP integration and download the Geoip*.dat files.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig deleted file mode 100644 index f3118c9d..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/logging-with-dot-log-in-zend-expressive-and-dotkernel.html.twig +++ /dev/null @@ -1,276 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - - - -For a more detailed documentation about the zend-log visit the zend-log documentation. -

Adding The Config Provider

- -

Configuring the logger

-For compatibility between components and for the better understanding of this tutorial the name for the created logger will be my_logger. - -The logger name is the developer's choice and should reflect its purpose (eg.: db_error_logger - a logger that only writes the error messages in db). - -Create a key-value pair, the key should be dot-log (or log if using zend-log) and the value should be an empty array. - -In the newly created value add a key loggers, and the value should be an array with the key my_logger and an empty array as the value. - -  - -At this point your log.global.php should look like this: -
return
-        ],
-    ],
-];
-  - -For this logger to actually log somewhere a writer is required, otherwise the log command will be received, but the logger will have no place to write the message in. - -The next step will show you how to configure writing to a specific file. -

Configuring the writer(s)

-Loggers must have at least one writer. - -A writer is an object that inherits from Zend\Log\Writer\AbstractWriter. A writer's responsibility is to record log data to a storage backend. (from zend-log's writer documentation) - -  -

Writing to a file (stream)

-It is possible separate logs into multiple files using writers and filters. For example warnings.log, errors.log, all_messages.log. - -In this example all the log messages will be written in one file. - -
return ,
-                    ],
-                ],
-            ]
-        ],
-    ],
-];
-The FileWriter key is optional, otherwise the writers array would be enumerative instead of associative. - -The writer name key is a developer-provided name for that writer, the writer name key is mandatory. - -The writer priority key is not affecting the errors that are written, it is a way to organize writers, for example: - -It is the most important to write in the file, the sql or e-mail are more probably fail because the servers can be external and offline, the file is on the same server. - -The writer priority key is optional. - -To write into a file the key stream must be present in the writer options array. This is required only if writing into streams/files. - -  - -For more examples see the zend-log streams: - -

(Optional) Configuring the Filters

-A filter prevents a message from being written to the log. (from zend-log filters documentation) - -As per PSR-3 document. - -The log levels are: emergency (0), alert (1), critical (2), error (3), warn (4), notice (5), info (6), debug (7) (in order of priority/importance) - -Although the plain Logger in Zend Log is not fully compatible with PSR-3, it provides a way to log all of these message types. - -  - -Starting from the basic writer configuration, several configurations will be added for extra functionality. - -  -
<?php
-
-return
-                                ],
-                            ],
-                        ],
-                    ],
-                    // Only warnings
-                    'OnlyWarningsWriter' => ,
-                                ],
-                            ],
-                        ],
-                    ],
-                    // Warnings and more important messages
-                    'WarningOrHigherWriter' => ,
-                                ],
-                            ],
-                        ],
-                    ],
-                ],
-            ],
-        ],
-    ],
-];
-As in the writer configuration, the developer can optionally use keys for associating the filters with a name. - -IMPORTANT NOTE: the operator for more important messages is <=, this is because the number representation is smaller for a more important message type. - -The filter added on the first writer is equal to not setting a filter, but it was been added to illustrate how to explicitly allow all messages. - -It was added opposite to the others just to demonstrate the other operator is also an option. - -  - -More examples on filters: https://zendframework.github.io/zend-log/filters/ -

(Optional) Configuring the Formatter

-When using dot-log or zend-log, the logged value is not limited to a string. Arrays can be logged as well. - -For a better readability, these arrays can be serialized. - -Zend Log provides String formatting, XML, JSON and FirePHP formatting. - -  - -The formatter accepts following parameters: - -  - -The following formats the message as JSON data: -
'formatter' => [
-    'name' => \Zend\Log\Formatter\Json::class,
-],
-  - -  -

Full example

-Below an example which: - -The key elements are bold. -
<?php
-
-return ,
-                                ],
-                            ],
-                            'formatter' => ,
-                        ],
-                    ],
-                ],
-            ],
-        ],
-    ],
-];
-  -

Usage

-Basic usage of the logger is illustraded below. - -The messages are written to see which logs are written and which are not written. -
use Zend\Log\Logger;
-... -
$logger = $container->get('dot-log.my_logger');
-
-/** @var Logger $logger */
-$logger->emerg('0 EMERG');
-$logger->alert('1 ALERT');
-$logger->crit('2 CRITICAL');
-$logger->err('3 ERR');
-$logger->warn('4 WARN');
-$logger->notice('5 NOTICE');
-$logger->info('6 INF');
-$logger->debug('7 debug');
-$logger->log(Logger::NOTICE, 'NOTICE from log()');
- -

Frequently Asked Questions

- -
-
- How do I register dot-log's config provider? + -
-

In config/config.php, add \Dot\Log\ConfigProvider::class if it's not already there, making sure it is added before application-specific components such as Frontend\App\ConfigProvider or Admin\App\ConfigProvider.

-
-
-
- How is a logger retrieved from the container instead of using the plain class name? + -
-

Dot\Log\ConfigProvider registers an abstract factory, LoggerAbstractServiceFactory, that responds to "selectors" instead of class names. Instead of requesting Zend\Log\Logger::class from the container, you request dot-log.my_logger (or just my_logger if using zend-log).

-
-
-
- What is a writer, and how many does a logger need? + -
-

A writer is an object that inherits from Zend\Log\Writer\AbstractWriter and is responsible for recording log data to a storage backend. Loggers must have at least one writer, and the writer's "name" key is mandatory while its "priority" key is optional and only used to organize writers, not to affect which errors get written.

-
-
-
- What does a filter do, and how are log levels ordered? + -
-

A filter prevents a message from being written to the log. Per PSR-3, the log levels in order of priority/importance are emergency (0), alert (1), critical (2), error (3), warn (4), notice (5), info (6), and debug (7) - the operator for "more important" messages is <= because a smaller number represents a more important message.

-
-
-
- What does the formatter configuration control? + -
-

The formatter accepts a "name" (a class implementing Zend\Log\Formatter\FormatterInterface) and "options" to pass to that formatter's constructor. Zend Log provides String, XML, JSON and FirePHP formatting, and arrays can be serialized this way for better readability.

-
-
-
- How do you actually write log messages once the logger is configured? + -
-

Fetch the logger from the container, e.g. $logger = $container->get('dot-log.my_logger');, then call methods such as emerg(), alert(), crit(), err(), warn(), notice(), info(), debug(), or the generic log(Logger::NOTICE, 'message').

-
-
-
- -

Sources:

- -This article will be continued in a different article that treats the error handling in middleware applications. - -  -{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/manual-upgrade-of-wurfl-xml-file-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/manual-upgrade-of-wurfl-xml-file-in-dotkernel.html.twig deleted file mode 100644 index 1f1008cf..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/manual-upgrade-of-wurfl-xml-file-in-dotkernel.html.twig +++ /dev/null @@ -1,48 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -You need to manually upgrade the xml file, if you still want to use it , and of course, please pay attention to license changes. -
    -
  1.  Download the wurfl-2.3.xml.zip file.
  2. -
  3. Rename it to wurfl.zip
  4. -
  5. Rename the file inside the archive to wurfl.xml
  6. -
  7. Replace the file /externals/wurfl/wurfl.zip with this new wurfl.zip file
  8. -
  9. Download the web_browsers_patch.xml file
  10. -
  11. Replace the /externals/wurfl/web_browsers_patch.xml file with this new one
  12. -
  13. Go to Admin panel, click on empty wurfl cache  link, then rebuild the cache
  14. -
-Again, please pay attention to WURFL license changes. - -  - -

Frequently Asked Questions

- -
-
- Why does the WURFL xml file need to be manually upgraded in Dotkernel? + -
-

Dotkernel bundles an old WURFL XML file (the latest GPL version, from June 2011). Because of a license change to that WURFL file, the bundled file will no longer be upgraded by Dotkernel, so it must be upgraded manually if you still want to use it.

-
-
-
- What are the main steps to manually upgrade the WURFL file? + -
-

Download the wurfl-2.3.xml.zip file, rename it to wurfl.zip, rename the file inside the archive to wurfl.xml, and replace /externals/wurfl/wurfl.zip with this new wurfl.zip file.

-
-
-
- Is there anything else to replace besides wurfl.zip? + -
-

Yes. Download the web_browsers_patch.xml file and replace the /externals/wurfl/web_browsers_patch.xml file with the new one.

-
-
-
- What's the last step after replacing the files? + -
-

Go to the Admin panel, click on the "empty" wurfl cache link, then rebuild the cache.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/migration-of-zend-framework-1-pear-channel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/migration-of-zend-framework-1-pear-channel.html.twig deleted file mode 100644 index dbf41ae2..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/migration-of-zend-framework-1-pear-channel.html.twig +++ /dev/null @@ -1,54 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -We were unable to migrate the project on github.com since the size of the repository is more then 1 GB. We resorted to building a special server that will host only the PEAR channel for Zend Framework 1 and we are commited to keep it live and running for the long term. - -Instructions on how to use the new channel can be found below . -

PEAR Channel Migration Guide

-Use the PEAR installer: - -1. Remove the installed pear package -
pear uninstall zend/zend
-2. Remove the old googlecode channel -
pear channel-delete zend.googlecode.com/svn
-3. Discover the new channel -
pear channel-discover pear.dotkernel.com/zf1/svn
-4. Install the package -
pear install zend/zend
-Follow @dotkernel  in order to be up to date related to new releases. - -  - -

Frequently Asked Questions

- -
-
- Why did the PEAR channel for Zend Framework 1 need to move? + -
-

The unofficial PEAR channel for Zend Framework 1 was hosted on Google Code, and once Google Code was closed the channel was forced to move to a new location.

-
-
-
- Why wasn't the project moved to GitHub instead? + -
-

The team was unable to migrate the project to github.com because the repository is more than 1 GB in size, so they built a special server dedicated to hosting only the PEAR channel for Zend Framework 1.

-
-
-
- Is Zend Framework 1 still worth using? + -
-

Zend Framework 1 is still used by a lot of projects in production, is still a viable library collection, and runs on PHP7, even though it is only in maintenance/security-patch mode.

-
-
-
- How do I switch to the new PEAR channel? + -
-

Using the PEAR installer: remove the installed package with pear uninstall zend/zend, remove the old Google Code channel with pear channel-delete zend.googlecode.com/svn, discover the new channel with pear channel-discover pear.dotkernel.com/zf1/svn, and then reinstall the package with pear install zend/zend.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/new-features-in-zend-framework-1-12.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/new-features-in-zend-framework-1-12.html.twig deleted file mode 100644 index 1a8537c5..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/new-features-in-zend-framework-1-12.html.twig +++ /dev/null @@ -1,84 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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.12.0RC1 -

 New Features

- - -
A number of autoloaders and autoloader facilities were back ported from ZF2 to provide performant alternatives to those already available in the 1.X releases.  These include: Zend_Loader_StandardAutoloader, which improves on Zend_Loader_Autoloader by allowing the ability to specify a specific path to associate with a vendor prefix or namespace; Zend_Loader_ClassMapAutoloader, which provides the ability to use lookup tables for autoloading (which are typically the fastest possible way to autoload); and Zend_Loader_AutoloaderFactory, which can both create and update autoloaders for you, as well as register them with spl_autoload_register(). - -The Zend_Loader changes were back ported from ZF2 by Matthew Weier O'Phinney
- -
Zend_EventManager is a component that allows you to attach and detach listeners to named events, both on a per-instance basis as well as via shared collections; trigger events; and interrupt execution of listeners. - -Zend_EventManager was back ported from ZF2 by Matthew Weier O'Phinney
- -
This class provides a features adapter that calls get_browser() in order to discover mobile device capabilities to inject into UserAgent device instances. - -Browscap (http://browsers.garykeith.com/) is an open project dedicated to collecting an disseminating a "database" of browser capabilities. PHP has built-in support for using these files via the get_browser() function. This function requires that your php.ini provides a browscap entry pointing to the PHP-specific php_browscap.ini file which is available at http://browsers.garykeith.com/stream.asp?PHP_BrowsCapINI. - -Zend_Http_UserAgent_Features_Adapter_Browscap was created by Matthew Weier O'Phinney
- -
Zend_Mobile_Push is a component for implementing push notifications for the 3 major push notification platforms (Apple (Apns), Google (C2dm) and Microsoft (Mpns). - -Zend_Mobile_Push was contributed by Mike Willbanks.
- -
Zend_Gdata_Analytics is an extension to Zend_Gdata to allow interaction with Google's Analytics Data Export API. This extension does not encompass any major changes in the overall operation of Zend_Gdata components. - -Zend_Gdata_Analytics was contributed by Daniel Hartmann.
-

Removed features

- -
Due to the changes in licensing of WURFL, we have removed the WurflApi adapter. We will be providing the WurflApi adapter to ScientiaMobile so that users of WURFL will still have that option.
-

Bug Fixes

-
In addition over 200 reported issues in the tracker have been fixed. We'd like to particularly thank Adam Lundrigan, Frank Brückner and Martin Hujer for their efforts in making this happen. Thanks also to the many people who ran the ZF1 unit tests and reported their results! - -For a complete list go here: http://framework.zend.com/issues/secure/IssueNavigator.jspa?requestId=12877
-  - -

Frequently Asked Questions

- -
-
- What was announced for Zend Framework 1.12? + -
-

Matthew Weier O'Phinney announced the immediate availability of the first release candidate of the Zend Framework 1.12 series, 1.12.0RC1.

-
-
-
- What changed in Zend_Loader? + -
-

A number of autoloaders and autoloader facilities were back ported from ZF2, including Zend_Loader_StandardAutoloader, Zend_Loader_ClassMapAutoloader, and Zend_Loader_AutoloaderFactory, providing performant alternatives to the autoloading facilities already in the 1.X releases.

-
-
-
- What is Zend_EventManager? + -
-

Zend_EventManager is a component, also back ported from ZF2, that lets you attach and detach listeners to named events (per-instance or via shared collections), trigger events, and interrupt execution of listeners.

-
-
-
- Why was the WurflApi adapter removed? + -
-

Zend_Http_UserAgent_Features_Adapter_WurflApi was removed due to changes in the licensing of WURFL. The team planned to provide the WurflApi adapter to ScientiaMobile so WURFL users would still have that option.

-
-
-
- How many bugs were fixed in this release? + -
-

Over 200 reported issues in the tracker were fixed, with particular thanks credited to Adam Lundrigan, Frank Brückner and Martin Hujer, as well as everyone who ran the ZF1 unit tests and reported results.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.html.twig deleted file mode 100644 index 027cd142..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/php-formatter-and-templates-for-zend-studio-10-1.html.twig +++ /dev/null @@ -1,34 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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.zend.php" to "org.eclipse.php". - -See  the updated file  Dotkernel PHP Formatter for Zend Studio 10.1 - -About PHP Formatter , see http://files.zend.com/help/Zend-Studio-10/zend-studio.htm#formatter.htm - -  - -Also, we created the templates file for Zend Studio 10.1 , according to Dotkernel coding standard, see the file: Templates for Zend Studio - -About Templates in Zend Studio and how to import/export: http://files.zend.com/help/Zend-Studio-10/zend-studio.htm#templates.htm - -  - -

Frequently Asked Questions

- -
-
- Why does the PHP Formatter plugin for Zend Studio 10.1 need to be changed? + -
-

Because Zend donated the Zend Studio Formatter upstream to the PDT project, the Formatter plugin needs to be changed by replacing "com.zend.php" with "org.eclipse.php".

-
-
-
- Where can I get Dotkernel's updated Formatter and Templates files for Zend Studio 10.1? + -
-

The updated PHP Formatter file for Zend Studio 10.1 and a templates file built according to the Dotkernel coding standard are both available as downloads linked in the article.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.html.twig deleted file mode 100644 index 0fd28571..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/protecting-admin-folder-with-htaccess-in-plesk.html.twig +++ /dev/null @@ -1,39 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -In /var/www/vhosts/exampledomain.com/conf/vhost.conf file add this code: -
-< Location /admin>
-   AuthType Basic
-   AuthName "My Site Admin"
-   AuthUserFile /var/www/vhosts/exampledomain.com/conf/.htpasswd
-  Require valid-user
-< /Location >
-  - -Notice the server that vhost.conf has been added/changed with command: -
/usr/local/psa/admin/sbin/websrvmng -a
-  - -Then create the .htpasswd file and upload it to /var/www/vhosts/exampledomain.com/conf/ - - - - -

Frequently Asked Questions

- -
-
- How do I protect the /admin folder with .htaccess in Plesk? + -
-

Add a Location block for /admin to the vhost.conf file (e.g. /var/www/vhosts/exampledomain.com/conf/vhost.conf) using AuthType Basic, an AuthName, an AuthUserFile pointing to a .htpasswd file, and Require valid-user.

-
-
-
- After editing vhost.conf, how do I apply the change and finish setup in Plesk? + -
-

Notify the server that vhost.conf has changed by running /usr/local/psa/admin/sbin/websrvmng -a, then create the .htpasswd file and upload it to the same conf directory referenced in the AuthUserFile directive.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.html.twig deleted file mode 100644 index 9060c7de..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/replacing-laminas-mail-with-symfony-mailer-in-dot-mail.html.twig +++ /dev/null @@ -1,136 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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:

- - - -

How Dotkernel handles the issue

- -

The Dotkernel team has also opted to replace the laminas/laminas-mail package in the dotkernel/dot-mail package. This meant revising the code, including the configuraton files. The optimal way to handle this was to try to have minimal impact on existing projects that already use dotkernel/dot-mail. This means that the calls to send the mail should remain the same, even if some lesser-used functionality like mime and imap are lost.

- -

Technical approach

- -

In this article we will list the edits that enabled the switch from laminas/laminas-mail to symfony/mailer in the dotkernel/dot-mail package. You can follow all the changes in this list of PRs:

- - - -

Function definition changes will not be covered in this article.

-
- -

The configuration file mail.global.php was revised to remove features that are no longer available and to make it easier to configure.

- -
<?php
-
-declare(strict_types=1);
-
-return ,
-                //copy destination addresses
-                'cc' => [],
-                //hidden copy destination addresses
-                'bcc' => [],
-                //email subject
-                'subject' => '',
-                //body options - content can be plain text, HTML
-                'body' => ,
-                //attachments config
-                'attachments' => ,
-                    'dir'   => ,
-                ],
-            ],
-            /**
-             * the mail transport to use can be any class implementing
-             * Symfony\Component\Mailer\Transport\TransportInterface
-             *
-             * for standard mail transports, you can use these aliases:
-             * - sendmail  => Symfony\Component\Mailer\Transport\SendmailTransport
-             * - esmtp     => Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport
-             *
-             * defaults to sendmail
-             **/
-            'transport' => 'sendmail',
-            //options that will be used only if esmtp adapter is used
-            'smtp_options' => ,
-            ],
-        ],
-        // option to log the SENT emails
-        'log' => ,
-    ],
-];
- -

Make sure to use ONE of the below transporters, based on your server configuration.

- -
'transport' => 'sendmail',
- -

OR

- -
'transport' => 'esmtp',
- -

We set Sendmail to be the default mail transport.

- -

How to update dotkernel/dot-mail from version 3 or version 4 to version 5 in your projects

- - - -

At this moment, mime and imap related functionality is removed.

- -

Frequently Asked Questions

- -
-
- Why was laminas/laminas-mail replaced? + -
-

The Laminas Technical Steering Committee decided on 2023-12-04 that laminas/laminas-mail would be abandoned because there was nobody to maintain it, and several alternatives were already available in the ecosystem.

-
-
-
- What alternatives were identified for the abandoned laminas-mail package? + -
-

The alternatives mentioned are ddeboer/imap for interacting with IMAP, zbateson/mail-mime-parser for parsing MIME messages, and symfony/mailer for sending mail.

-
-
-
- How did Dotkernel handle the removal of laminas-mail in dot-mail? + -
-

The Dotkernel team replaced laminas/laminas-mail with symfony/mailer inside the dotkernel/dot-mail package, revising the code and configuration files while aiming for minimal impact on existing projects, so the calls used to send mail stay the same, even though some lesser-used functionality like mime and imap is lost.

-
-
-
- Which mail transport does dot-mail use by default? + -
-

The revised mail.global.php configuration defaults the transport to sendmail. You should use ONE of the transporters, either 'transport' => 'sendmail' or 'transport' => 'esmtp', based on your server configuration; Sendmail was set as the default.

-
-
-
- How do I update dotkernel/dot-mail from version 3 or 4 to version 5? + -
-

Download the new mail.global.php.dist configuration file, add the values you configured for your project (focusing on transport, message_options and smtp_options) to replace your old configuration file, then update "dotkernel/dot-mail" to "^5.0.0" in composer.json and run composer update.

-
-
-
- What functionality is lost after switching to symfony/mailer? + -
-

At the time of the article, mime and imap related functionality is removed from dot-mail as a result of the switch.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.html.twig deleted file mode 100644 index c03414bc..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/scientia-mobile-licensed-its-wurfl-cloud-php-library-to-dotkernel-1-6.html.twig +++ /dev/null @@ -1,52 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -In the Dotkernel version 1.6.0 , released on May 16th, 2012 we have removed the GPL'ed WURFL PHP library , as the code it was obsolete anyway and also the structure of the XML file was changed. - -Instead , we implemented the Scientia Mobile's WURFL Cloud PHP  library . - - - -Instructions about how to signup for a free account , and how to setup your API key can be found here . - -In the near future, we will create a template in Wurfl Cloud API control panel,  at Scientia Mobile,  ready to be used in Dotkernel implementations. - -Also, we got a special license, please read the file: - -dotkernel/library/WurflCloud/LICENSE.txt and notice Dotkernel mention -
License is non-transferable, conditioned, limited to the version -received and to the use and distribution of this software only in connection -with the Dotkernel software framework, and does not allow making derivative works -even when the source code is provided or the language is an interpreted one -and regardless of whether the code is not obfuscated.
-  - -

Frequently Asked Questions

- -
-
- Why was the WURFL PHP library removed from Dotkernel 1.6.0? + -
-

In Dotkernel 1.6.0, released on May 16th, 2012, the GPL'ed WURFL PHP library was removed because the code was obsolete anyway and the structure of the XML file had changed.

-
-
-
- What replaced the old WURFL PHP library? + -
-

Dotkernel implemented Scientia Mobile's WURFL Cloud PHP library instead.

-
-
-
- Where can I find instructions to set up the WURFL Cloud API key? + -
-

Instructions on how to sign up for a free account and set up your API key are linked in the article, pointing to the "Detecting Mobile Devices in Dotkernel 1.6" article.

-
-
-
- What are the terms of Dotkernel's special WURFL Cloud license? + -
-

The license is non-transferable, conditioned, limited to the version received, and restricted to use and distribution only in connection with the Dotkernel software framework. It does not allow making derivative works, even when source code is provided or the code is not obfuscated.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/sending-emails-using-dot-email-component-and-zend-email.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/sending-emails-using-dot-email-component-and-zend-email.html.twig deleted file mode 100644 index bd9f5c43..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/sending-emails-using-dot-email-component-and-zend-email.html.twig +++ /dev/null @@ -1,81 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -1.   setContent() , as the name is suggesting, set the body of the email by calling setBodyText() (if format is "text/plain") or setBodyHtml() (if format is "text/html"). - -Note the declaration of this method: setContent ($content, $format = 'text/plain') - -2.  send(), set the transporter and is calling parent::send() method for sending the email - -For sending the email you MUST use this methods in the code above: -
$dotEmail = new Dot_Email();
-$dotEmail->addTo($email);
-$dotEmail->setSubject('Forgot Password');
-$dotEmail->setBodyText('Your password is '.$password);
-$succeed = $dotEmail->send();
-if($succeed)
-{
-    echo "Message sent!";
-}
-else
-{
-    echo "Message failed, not sent!";
-}
-Only if you want to overwrite the default Email and Name, add the below lines: -
$dotEmail->setFrom($fromEmail, $fromName);
-$dotEmail->setReplyTo($fromEmail, $fromName);
-NOTE*: you always have to use  addTo()setSubject(), setBodyText() or setBodyHtml() or setContent(), and finally send() method. - -Methods inherited  from Zend_Mail and that can be used in your code are: -
    -
  1. setBodyText($txt,$charset=null,$encoding=Zend_Mime::ENCODING_QUOTEDPRINTABLE)
  2. -
  3. setBodyHtml($html,$charset=null,$encoding=Zend_Mime::ENCODING_QUOTEDPRINTABLE)
  4. -
  5. addTo($email, $name='')
  6. -
  7. addCc($email, $name='')
  8. -
  9. addBcc($email)
  10. -
  11. setFrom($email, $name = null)
  12. -
  13. setReplyTo($email, $name = null)
  14. -
  15. setReturnPath($email)
  16. -
  17. setSubject($subject)
  18. -
  19. addHeader($name, $value, $append = false)
  20. -
- -

Frequently Asked Questions

- -
-
- What is Dot_Email? + -
-

Dot_Email is a class that extends Zend_Mail, so all methods from Zend_Mail are available in it. Aside from its constructor, Dot_Email itself is composed of only two methods, with everything else inherited from Zend_Mail.

-
-
-
- What are the two methods that Dot_Email adds on top of Zend_Mail? + -
-

setContent($content, $format = 'text/plain') sets the body of the email by calling setBodyText() when the format is "text/plain" or setBodyHtml() when the format is "text/html". send() sets the transporter and calls the parent::send() method to send the email.

-
-
-
- What is the minimal sequence of calls needed to send an email with Dot_Email? + -
-

You must always use addTo(), setSubject(), and setBodyText() or setBodyHtml() or setContent(), and finally call send(), as shown in the article's example that creates a new Dot_Email, adds a recipient, sets the subject and body text, then sends it and checks the returned success value.

-
-
-
- How do I override the default From and Reply-To addresses? + -
-

Only if you want to overwrite the default Email and Name, add calls to setFrom($fromEmail, $fromName) and setReplyTo($fromEmail, $fromName).

-
-
-
- Which Zend_Mail methods are inherited and usable through Dot_Email? + -
-

Inherited methods listed in the article include setBodyText(), setBodyHtml(), addTo(), addCc(), addBcc(), setFrom(), setReplyTo(), setReturnPath(), setSubject(), and addHeader().

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/templating-in-dotkernel3.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/templating-in-dotkernel3.html.twig deleted file mode 100644 index d228d044..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/templating-in-dotkernel3.html.twig +++ /dev/null @@ -1,46 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -In Dotkernel 3, we made the move to the popular Twig Templating Engine, which will allow us to take our templates to a whole new level. It can now be written as HTML code, with some extra Twig tags; It is incredibly powerful and includes features such as: - - - -The templating engine in Dotkernel3 gives you the familiarity of HTML with the overview and convenience of PHP. - -

Frequently Asked Questions

- -
-
- Which templating engine does Dotkernel3 use? + -
-

Dotkernel3 moved to the popular Twig Templating Engine.

-
-
-
- Why did Dotkernel move away from the previous templating engine? + -
-

The previous templating engine, although solid and robust, was also 10 years old and used techniques that had become slightly outdated, and it was one of the pain points in the previous version of Dotkernel.

-
-
-
- What features does Twig bring to Dotkernel3 templates? + -
-

Templates can now be written as HTML code with extra Twig tags, and Twig includes features such as layouts, loops, variables, and escaping, giving the familiarity of HTML with the overview and convenience of PHP.

-
-
-
- Where can I read the official Twig templating documentation for Dotkernel? + -
-

The official documentation is available at twig.symfony.com.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/test-article.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/test-article.html.twig deleted file mode 100644 index bac62a68..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/test-article.html.twig +++ /dev/null @@ -1,18 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} - - -

This is a test article used for fixture and testing purposes. This article is now updated.

- -

Frequently Asked Questions

- -
-
- What is this article for? + -
-

This is a test article added to the Dotkernel category for fixture and testing purposes.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig deleted file mode 100644 index ab5c931d..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/using-dotkernel-with-composer-dependencies.html.twig +++ /dev/null @@ -1,114 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -Composer automatically loads our dependencies so there is no need to include/require them. - -For this example we will render a Barcode using Zend Framework 1 as the Non-Namespaced dependency and Zend Framework 2 as the Namespaced dependency. -

The Composer Dependencies

-ZendFramework 1 Barcode module can only be loaded with the ZF1 itself: -
composer require 'zendframework/zendframework1'
-The ZendFramework 2 Barcode module can be separately loaded: -
composer require 'zendframework/zend-barcode'
-  -

Important note

-
These dependencies can be used anywhere after the Dot_Kernel::initialize() function was called
-  -

Using Non-Namespaced Dependencies (Zend Framework 1)

-The class is loaded PSR-0 style, meaning the class name looks like VendorName_PackageName_ClassName -
// Only the text to draw is required
-$barcodeOptions = array('text' =>; 'ZEND-FRAMEWORK');
- 
-// No required options
-$rendererOptions = array();
- 
-// Draw the barcode in a new image,
-// send the headers and the image
-Zend_Barcode::factory(
-'code39', 'image', $barcodeOptions, $rendererOptions
-)->render();
-  -

Using Namespaced Dependencies (Zend Framework 2)

-The class is loaded PSR-4 style, meaning the class name looks like \VendorName\PackageName\ClassName -
use Zend\Barcode\Barcode;
-
-// Only the text to draw is required
-$barcodeOptions = array('text' => 'ZEND-FRAMEWORK');
-
-// No required options
-$rendererOptions = array();
-
-// Draw the barcode in a new image,
-// send the headers and the image
-Barcode::factory(
-    'code39', 'image', $barcodeOptions, $rendererOptions
-)->render();
-

The result

-Both the examples will render the following barcode if nothing goes wrong: - -Resulting barcode. Source: http://framework.zend.com/images/manual/zend.barcode.introduction.example-1.png Resulting barcode.
Source: bit.ly/1XbZZ81 -

Tip

-In this case the first example will work for both namespaced an non-namespaced dependencies if we add the the following as the first line at the first example: -
use Zend\Barcode\Barcode as Zend_Barcode;
-Now we can use any of the following to access ZF2's Barcode Module: - -  - -  - -The full examples can be found here: - -Zend Framework 1 - Rendering a barcode - -Zend Framework 2 - Rendering a barcode - -This article works for any Dotkernel 1.x version if your server is running PHP >5.4.0. - -

Frequently Asked Questions

- -
-
- Do I need to manually include or require Composer dependencies in Dotkernel? + -
-

No. Composer automatically loads dependencies, so there is no need to include or require them yourself.

-
-
-
- What example does the article use to demonstrate Composer dependencies? + -
-

The article renders a Barcode using Zend Framework 1 as the Non-Namespaced dependency (installed with composer require 'zendframework/zendframework1') and Zend Framework 2 as the Namespaced dependency (installed with composer require 'zendframework/zend-barcode').

-
-
-
- When can these Composer dependencies be used in the application? + -
-

These dependencies can be used anywhere after the Dot_Kernel::initialize() function has been called.

-
-
-
- How are non-namespaced (Zend Framework 1) classes loaded compared to namespaced (Zend Framework 2) classes? + -
-

Non-namespaced ZF1 classes are loaded PSR-0 style, meaning the class name looks like VendorName_PackageName_ClassName (e.g. Zend_Barcode). Namespaced ZF2 classes are loaded PSR-4 style, meaning the class name looks like \VendorName\PackageName\ClassName (e.g. Zend\Barcode\Barcode).

-
-
-
- Can the same code work for both namespaced and non-namespaced barcode dependencies? + -
-

Yes. If you add "use Zend\Barcode\Barcode as Zend_Barcode;" as the first line, the ZF1-style example will work for both, and you can then reference the module as Zend\Barcode\Barcode, Barcode, or Zend_Barcode.

-
-
-
- What Dotkernel and PHP versions does this article apply to? + -
-

This article works for any Dotkernel 1.x version if your server is running PHP greater than 5.4.0.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig deleted file mode 100644 index 7c75e909..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/using-utf8-charset-in-dotkernel.html.twig +++ /dev/null @@ -1,53 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. -
Those changes are commited into Dotkernel 1.6.0 dev codebase, which will be released in next days.
- -

Database

-set for all tables and columns proper collation , either utf8_general_ci or utf8_bin -

Application.ini

-add the line -
database.params.charset = utf8
-to your application.ini file, in the [production] area. -

Differences between utf8_general_ci and utf8_bin

-utf8_bin: compare strings by the binary value of each character in the string -utf8_general_ci: compare strings using general language rules and using case-insensitive comparisons - -For example, the following will evaluate at true with  utf8_general_ci collation, but not with the utf8_bin collation: - -Ä = A -Ö = O -Ü = U - -Those differences happens only on MySQL level ( for instance in queries using LIKE operator) and not at PHP level ( for instance, in str_replace() calls ) - -

Frequently Asked Questions

- -
-
- What needs to change to use UTF8 encoding in a Dotkernel-based system? + -
-

Both the database structure and the application.ini file need changes: all tables and columns must have a proper UTF8 collation (utf8_general_ci or utf8_bin), and application.ini must load the utf8 charset.

-
-
-
- What line should I add to application.ini to enable UTF8? + -
-

Add the line database.params.charset = utf8 to your application.ini file.

-
-
-
- What is the difference between utf8_general_ci and utf8_bin collation? + -
-

utf8_bin compares strings by the binary value of each character, while utf8_general_ci compares strings using general language rules with case-insensitive comparisons. For example, Ä = A, Ö = O, and Ü = U evaluate as true under utf8_general_ci but not under utf8_bin.

-
-
-
- Does the collation choice affect PHP string functions too? + -
-

No. These collation differences only happen at the MySQL level, for instance in queries using the LIKE operator, and not at the PHP level, for instance in str_replace() calls.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.html.twig deleted file mode 100644 index fc22bbfa..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-cloud-integration-in-dotkernel-1-6-0.html.twig +++ /dev/null @@ -1,53 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Another new feature in version 1.6.0 of Dotkernel is the integration of Wurfl Cloud. - -Wurfl or Wireless Universal Resource FiLe is coming with a new way to deliver their services of device detection and they named it Wurfl Cloud. - -Because of the need to detect mobile devices in Dotkernel we have integrated Wurfl Cloud as the default detection method. - - - -If you want to see the Wurfl Cloud library you can find it in the library folder of Dotkernel. - -Quick steps to have a functional device detection in Dotkernel. - -
resources.useragent.wurflcloud.api_key = 000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-Now you can test it by changing the user agent in your browser to a mobile device and access your project. Now you can see that you are redirected to the mobile module. - -To see how mobile detection is working in Dotkernel read this post. - -

Frequently Asked Questions

- -
-
- What is WURFL Cloud? + -
-

WURFL Cloud is a new way that WURFL (Wireless Universal Resource FiLe) delivers its device detection services. Dotkernel integrated it as the default detection method for mobile devices starting with version 1.6.0.

-
-
-
- How do I get a functional device detection setup with WURFL Cloud? + -
-

Register for a Wurfl Cloud account with Scientia Mobile, choose device_os and mobile_browser as your capabilities, then copy your API key into application.ini in Dotkernel.

-
-
-
- What line do I add to application.ini for the API key? + -
-

Add a line such as resources.useragent.wurflcloud.api_key = 000000:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX, using your own API key.

-
-
-
- How can I test that mobile detection is working? + -
-

Change the user agent in your browser to a mobile device and access your project; you should be redirected to the mobile module.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.html.twig deleted file mode 100644 index 34cad7a3..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-php-api-license-incompatible-with-dotkernel.html.twig +++ /dev/null @@ -1,45 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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.0). Everybody was happy, users was able to make web applications for them or for their customers without worry about licensing costs at all. - -On June 6th, 2011, the WURFL PHP API library was updated to version 1.3.0. So we updated too the latest Dotkernel 1.5.0 ( now in release candidate stage, soon to be released as stable) , without noticing the change on its license, from GNU/GPL to AGPL which suddenly transform it into a 'trial only' library, for "Product evaluation" only. - -See the official announcement here. - -We respect the work of Luca Passani and Steve Kamerman ( thank you and keep up the good work :-) ). -But we must keep Dotkernel Application Framework free, without such license limitations. Only thing to do in this stage is : - -  - -later edit: the correct link to announcement is here - -

Frequently Asked Questions

- -
-
- Why did the WURFL PHP API license become incompatible with Dotkernel? + -
-

The WURFL PHP API used to be licensed under GNU/GPL, compatible with both Zend Framework's new BSD license and Dotkernel's OSL 3.0 license. On June 6th, 2011, version 1.3.0 of the library changed its license to AGPL, turning it into a "trial only" library for product evaluation, which is incompatible with keeping Dotkernel free.

-
-
-
- Which version introduced the license change, and when? + -
-

Version 1.3.0 of the WURFL PHP API, released June 6th, 2011, changed the license from GNU/GPL to AGPL. Dotkernel had updated to this version in its 1.5.0 release candidate without noticing the license change.

-
-
-
- What did Dotkernel do in response to the license change? + -
-

Dotkernel planned to remove WURFL PHP API version 1.3.0 from its codebase and reintegrate version 1.2.1, which was still GNU/GPL. It also intended to keep contributing to the free WURFL XML data, and considered forking the 1.2.1 library and renaming it to avoid trademark issues.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-zend-framework-integration-into-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/wurfl-zend-framework-integration-into-dotkernel.html.twig deleted file mode 100644 index 3fb7ceab..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/wurfl-zend-framework-integration-into-dotkernel.html.twig +++ /dev/null @@ -1,78 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -WURFL is integrated into Dotkernel, using the Zend_Http_UserAgent class from the latest release ZF 1.11.0rc1 ( Beta release at the date of this post). - -The integration of WURFL into Dotkernel is described below. - -Download WURFL PHP API and unzip it into folder wurfl-php-1.1 - - - - -
resources.useragent.wurflapi.wurfl_api_version = "1.1"
-resources.useragent.wurflapi.wurfl_lib_dir = APPLICATION_PATH "/library/Wurfl/"
-resources.useragent.wurflapi.wurfl_config_file = APPLICATION_PATH "/configs/wurfl.xml"
- -WURFL is integrated into Dotkernel in the mobile module, but to access WURFL configuration, use -
$userAgent = new Zend_Http_UserAgent($config->resources->useragent);
-$device = $userAgent->getDevice();
-$userAgent->getDevice() returns all the relevant information about the current user agent ($_SERVER['HTTP_USER_AGENT']) - -

Frequently Asked Questions

- -
-
- Which Zend Framework class is used to integrate WURFL into Dotkernel? + -
-

WURFL is integrated using the Zend_Http_UserAgent class from Zend Framework 1.11.0rc1 (the beta release at the time of the post).

-
-
-
- What is the first step to integrate WURFL into Dotkernel? + -
-

Download the WURFL PHP API and unzip it into a folder named wurfl-php-1.1.

-
-
-
- Which folders need to be created and made writable? + -
-

Two folders must be created and made writable by the web server: cache/wurfl/FILE_CACHE_PROVIDER and cache/wurfl/FILE_PERSISTENCE_PROVIDER.

-
-
-
- What needs to be added to application.ini? + -
-

Three lines need to be added: resources.useragent.wurflapi.wurfl_api_version = "1.1", resources.useragent.wurflapi.wurfl_lib_dir pointing to APPLICATION_PATH "/library/Wurfl/", and resources.useragent.wurflapi.wurfl_config_file pointing to APPLICATION_PATH "/configs/wurfl.xml".

-
-
-
- How do you access WURFL configuration in code? + -
-

Instantiate a Zend_Http_UserAgent with the useragent config, then call getDevice() on it, for example $userAgent = new Zend_Http_UserAgent($config->resources->useragent); $device = $userAgent->getDevice();. getDevice() returns all the relevant information about the current user agent.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.html.twig deleted file mode 100644 index 925ce132..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-auth-and-zend-acl-integrated-in-dotkernel.html.twig +++ /dev/null @@ -1,84 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Zend_Auth and Zend_Acl have been integrated into the Dotkernel, starting with version 1.5.0 - -With the integration of these two major components for user authorization and access we completely refactored the User and Admin models by using Dot_Auth and Dot_Acl classes. - - - -The Dot_Auth class authenticates the user by checking the database – Zend_Auth_Adapter_DbTable. -
private function _getAuthAdapter($who)
-    {
-        $dbAdapter = Zend_Registry::get('database');
-        $authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
-        $authAdapter->setTableName($who)
-            ->setIdentityColumn('username')
-            ->setCredentialColumn('password');
-        return $authAdapter;
-    }
-ACL roles (user types) and permissions are configured in the configs/acl/role.xml file. -In Dotkernel there are 3 roles: guest, user and admin. -The resources for ACL are taken from configs/router.xml – see controllers tag. - -The Dot_Acl class - -Dot_Auth calls the isAllowed method from Dot_Acl which authenticates the user. -
// instantiate Zend_Acl
-$this->acl = new Zend_Acl();
-
public function isAllowed($role)
-    {
-        $resource = $this->requestControllerProcessed;
-        $privillege = $this->requestAction;
-        if(!$this->acl->has($resource))
-        {
-            return FALSE;
-        }
-        else
-        {
-            return $this->acl->isAllowed($role, $resource, $privillege);
-        }
-    }
-IndexController.php is calling Dot_Auth -
$dotAuth = Dot_Auth::getInstance();
-$dotAuth->checkIdentity('user');
- -

Frequently Asked Questions

- -
-
- When were Zend_Auth and Zend_Acl integrated into Dotkernel? + -
-

Zend_Auth and Zend_Acl were integrated starting with Dotkernel version 1.5.0, as part of a refactor of the User and Admin models using the new Dot_Auth and Dot_Acl classes.

-
-
-
- How does Dot_Auth authenticate a user? + -
-

Dot_Auth authenticates the user by checking the database using Zend_Auth_Adapter_DbTable, setting the table name, the identity column to "username", and the credential column to "password".

-
-
-
- What roles exist for ACL in Dotkernel? + -
-

Dotkernel defines 3 ACL roles (user types): guest, user, and admin. These roles and their permissions are configured in the configs/acl/role.xml file.

-
-
-
- Where do the ACL resources come from? + -
-

The resources for ACL are taken from configs/router.xml, specifically the controllers tag.

-
-
-
- What does the Dot_Acl class do? + -
-

Dot_Acl controls user access (Access Controller Layer), is used for setting and checking a user's permissions, and uses Zend_Acl to check whether a role (user) has access to a resource (controller). Dot_Auth calls the isAllowed method from Dot_Acl to authenticate the user.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig deleted file mode 100644 index 57f8ee6a..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-console-implementation-in-dotkernel.html.twig +++ /dev/null @@ -1,56 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Starting with 1.5, Dotkernel has a Console bootstrap to easily run PHP scripts from the command line. The most common use for this is to be able to run cron jobs without using wget and going through Apache. - - - -The bootstrap can be found in Console/index.php and it has one controller in Controller.php with two example actions. -

Usage

-Currently there are two command line arguments: - -Any other arguments set when calling the script will be available in the controller in the $registry->arguments array. - -The controller bundled with Dotkernel has two example actions, "count-users" which demonstrates how to interact with models, and "send-newsletter" which reads the newsletter from the command line. -

Examples

-
/var/www/vhosts/example.com/httpdocs/Cron/index.php -e staging -a count-users
-
/var/www/vhosts/example.com/httpdocs/Cron/index.php -e staging -a send-newsletter "test newsletter"
- -

Frequently Asked Questions

- -
-
- What is the purpose of the Console bootstrap in Dotkernel? + -
-

Starting with version 1.5, Dotkernel has a Console bootstrap to easily run PHP scripts from the command line, most commonly used to run cron jobs without using wget or going through Apache.

-
-
-
- Where is the Console bootstrap located? + -
-

The bootstrap is found in Console/index.php, and it has one controller in Controller.php with two example actions.

-
-
-
- What command line arguments does the Console bootstrap support? + -
-

There are two arguments: -a, the name of the action to execute, and -e, the environment as defined in application.ini (development, staging, or production), which is optional and defaults to production.

-
-
-
- What happens to extra arguments passed to the script? + -
-

Any other arguments set when calling the script will be available in the controller in the $registry->arguments array.

-
-
-
- What example actions are bundled with the Console controller? + -
-

The bundled controller has two example actions: "count-users", which demonstrates how to interact with models, and "send-newsletter", which reads the newsletter from the command line.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.html.twig deleted file mode 100644 index 62dc33e4..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-framework-dropped-integration-of-wurfl-adapter.html.twig +++ /dev/null @@ -1,36 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -According to Matthew Weier O'Phinney, Zend Framework Project Leader, in the next release of ZF, 1.12.0 branch, WURFL adapter will be dropped -
due to a change in licensing by the WURFL project -- -the free version is now AGPL -- we're dropping the WURFL adapter from -Zend_Http_UserAgent. In discussions with Zend and the CR Team, we feel -the AGPL raises too many concerns for end users with regards to how -their products must be licensed in order to comply. (This is a similar -rationale as to why we did not consider ExtJS when looking at JS -toolkits to partner with.) - -As such, if you relied on the WURFL adapter in the past, you _will_ need -to change code when upgrading to ZF 1.12.0, or grab the WURFL adapter -from a previous version.
-So its a good moment to check the old or current projects, and proceed. - -Dotkernel framework is using its own WURFL adapter, so this will not affect projects built with it . - -

Frequently Asked Questions

- -
-
- Why did Zend Framework drop the WURFL adapter? + -
-

According to Matthew Weier O'Phinney, Zend Framework Project Leader, the WURFL adapter was dropped from Zend_Http_UserAgent in the upcoming 1.12.0 branch due to a change in licensing by the WURFL project: the free version became AGPL, which raised too many concerns for end users regarding how their products must be licensed to comply.

-
-
-
- Does this change affect projects built with Dotkernel? + -
-

No. Dotkernel uses its own WURFL adapter, so the removal of the WURFL adapter from Zend_Http_UserAgent in ZF 1.12.0 will not affect projects built with Dotkernel.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-registry-usage-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-registry-usage-in-dotkernel.html.twig deleted file mode 100644 index 2681bdc6..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-registry-usage-in-dotkernel.html.twig +++ /dev/null @@ -1,56 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -In Dotkernel, Zend_Registry will contain the following variables: - -To use the variables in the registry, you must first get an instance of the registry object: -
$registry = Zend_Registry::getInstance();
-//...
-echo $registry->startTime;
-//...
-echo $registry->requestAction;
-Or if you only need one variable from the registry, you can get it directly using: -
$action = Zend_Registry::get('requestAction');
-You can find more information about Zend_Registry, in the Zend Framework Documentation. - -

Frequently Asked Questions

- -
-
- What variables does Zend_Registry contain in Dotkernel? + -
-

It contains startTime (the result of microtime() at the beginning of the request), configuration (loaded from configs/application.ini), router (loaded from configs/router.xml), database (the database adapter), settings (loaded from the database), requestModule/requestController/requestAction, request (additional request variables), seo (loaded from configs/dots/seo.xml), option (loaded from configs/dots/<moduleName>.xml), and session (the session object).

-
-
-
- How do you get an instance of the registry? + -
-

Use $registry = Zend_Registry::getInstance(); and then access variables such as $registry->startTime or $registry->requestAction.

-
-
-
- How do you retrieve just one variable from the registry? + -
-

If you only need one variable, you can get it directly using $action = Zend_Registry::get('requestAction');.

-
-
-
- What does the "seo" entry in the registry contain? + -
-

It contains seo information loaded from configs/dots/seo.xml, such as site name, default description, and keywords.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.html.twig deleted file mode 100644 index 5887edbf..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-session-usage-in-dotkernel-refactor-of-dot-session-class.html.twig +++ /dev/null @@ -1,57 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -We found a strange behaviour of sessions in one of our project, running Dotkernel version 1.5.0 - -Similar to this one described here - -In unknowns circumstances, and only in IE 8 and IE9 , the session cookie is not saved on client machine, and the user need to login over and over again. I was able to reproduce that behaviour once, on our staging server , and the only way to fix that was to open a new tab with the same page (?!?!?) - - - -Anyway, is still an enygma. So i started to investigate Dot_Session class, and notice that the session cookie is generated 3 times !! See this bug report. - -I noticed that we use both regenerateID()  and rememberMe()  methods of Zend_Session, , which is   not necessary -Quote from ZF documentation: -
If you call the rememberMe() function, then don't use regenerateId(), since the former calls the latter.  If a user has successfully logged into your website, use rememberMe() instead of regenerateId().
-So i removed regenerateID() call,   also added 2 new settings in application.ini  related to session : use_only_cookies, which must be ON all the time in order to avoid session fixation , and remember_me_seconds. - -Those small bug fixes will be included in the new Dotkernel version 1.5.1 which will be released  next days. - -TIP - -if you encounter the same issue in IE8 and IE9 , then with all regret, you need to de-activate the rememberMe() and regenerateId() methods calls. - -  - -  - -

Frequently Asked Questions

- -
-
- What session bug was found in Dotkernel 1.5.0? + -
-

In unknown circumstances, and only in IE8 and IE9, the session cookie was not saved on the client machine, forcing the user to log in over and over again. The only workaround found was to open a new tab with the same page.

-
-
-
- What caused the session cookie to be generated multiple times? + -
-

Investigation of the Dot_Session class showed the session cookie was generated 3 times, because the code called both regenerateID() and rememberMe() methods of Zend_Session. According to the Zend Framework documentation, this is unnecessary: if you call rememberMe(), you should not also call regenerateId(), since rememberMe() already calls it internally.

-
-
-
- What was the fix, and where was it released? + -
-

The regenerateID() call was removed, and two new application.ini settings were added: use_only_cookies, which must be ON at all times to avoid session fixation, and remember_me_seconds. These fixes were included in Dotkernel version 1.5.1.

-
-
-
- What should I do if I still see this issue in IE8/IE9? + -
-

If you encounter the same issue in IE8 and IE9, the tip given is to deactivate the rememberMe() and regenerateId() method calls.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.html.twig deleted file mode 100644 index ce8ffc1a..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel/zend-studio-php-formatter-file-for-dotkernel-coding-standard.html.twig +++ /dev/null @@ -1,28 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Here is  uploaded the XML file , ready to be imported in your Zend Studio, version 9.x - -This file follow Dotkernel’s Coding standard. - -Unzip the file. -Go to Zend Studio -> Preferences -> PHP -> Code Style -> Formatter -Import the xml file. - -

Frequently Asked Questions

- -
-
- What is this file for? + -
-

It is an XML file, downloadable from the linked page, that can be imported into Zend Studio (version 9.x) to set up the PHP code formatter so it follows Dotkernel's coding standard.

-
-
-
- How do I import the formatter file into Zend Studio? + -
-

Unzip the downloaded file, then in Zend Studio go to Preferences -> PHP -> Code Style -> Formatter and import the XML file.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/development-report-december-11-2017.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/development-report-december-11-2017.html.twig deleted file mode 100644 index 867fcfa0..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel3/development-report-december-11-2017.html.twig +++ /dev/null @@ -1,42 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -Webpack tutorial has been added in the Prerequisites section. - -The Api Endpoint Documentation Guidelines were updated as well, a slightly different layout and some corrections were applied. - -  - -Links: - -  - -Thanks to our contributors: - -JapSeyz - -

Frequently Asked Questions

- -
-
- What documentation updates does this report cover? + -
-

Release notes were added for Dotkernel3 frontend and admin, a Webpack tutorial was added to the Prerequisites section, and the Api Endpoint Documentation Guidelines were updated with a slightly different layout and some corrections.

-
-
-
- Who was credited as a contributor in this report? + -
-

JapSeyz was thanked as a contributor.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig deleted file mode 100644 index 9b5c4dce..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-v4.html.twig +++ /dev/null @@ -1,237 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. Dotkernel Admin V4 comes with various interesting features and overall improvement of the core framework.

- -

- -

- -

Demo

- -

Ready to try it out?

- -

There should be an admin account with the following credentials:

- - - -

Head over to https://admin7.dotkernel.net/ and see for yourself :)

- -

Dotkernel Admin V4 Features

- -

PHP 8.1

- -

Dotkernel Admin V4 fully supports PHP 8.1 with a minimum requirement of PHP 7.4.

- -

Configurability

- -

From managing middleware order to simply adding an API key to you application the config directory is the way to go.

- -

Want to register a new module? In config.php you will find the right place for it registering its ConfigProvider.php.
Got a shiny new Middleware? You can put it with the rest of them in pipeline.php where you can even edit in which order they should run.

- -

You can further customize your app within the autoload directory by changing the application name and url into the app.global.php, adding the freshly created routes into navigation.global.php and much more.

- -

Example adding a simple route to navigation bar:

- -
[
-    'options' => [
-        'label' => 'Dashboard',
-        'route' => [
-            'route_name' => 'dashboard',
-        ],
-        'icon' => 'fas fa-tachometer-alt',
-    ]
-]
- -

or a group of 2 or more routes

- -
[
-    'options' => [
-        'label' => 'Manage admins',
-        'route' => '',
-        'icon' => 'fas fa-user-circle',
-    ],
-    'pages' => [
-        [
-            'options' => [
-                'label' => 'Admins',
-                'uri' => '/admin/manage',
-                'icon' => 'fas fa-user-circle',
-            ],
-        ],
-        [
-            'options' => [
-                'label' => 'Logins',
-                'uri' => '/admin/logins',
-                'icon' => 'fas fa-sign-in-alt',
-            ],
-        ]
-    ]
-]
- -

Authorization Guards

- -

The packages responsible for restricting access to certain parts of the application are dot-rbac-guard and dot-rbac. These packages work together to create an infrastructure that is customizable and diversified to manage user access to the platform by specifying the type of role the user has.

- -

The authorization.global.php file provides multiple configurations specifying multiple roles as well as the types of permissions to which these roles have access.

- -
//example of a flat RBAC model that specifies two types of roles as well as their permission
-'roles' => [
-    'superuser' => [
-        'permissions' => [
-            'authenticated',
-            'edit',
-            'delete',
-            //etc..
-        ]
-    ],
-    'admin' => [
-        'permissions' => [
-            'authenticated',
-            //etc..
-        ]
-    ]
-]
- -

The authorization-guards.global.php file provides configuration to restrict access to certain actions based on the permissions defined in authorization.global.php so basically we have to add the permissions in the dot-rbac configuration file first to specify the action restriction permissions.

- -
//example of configuration example to restrict certain actions of some routes based on the permissions specified in the dot-rbac configuration file
-'rules' => [
-    [
-        'route' => 'account',
-        'actions' => [
-            //list of actions to apply , or empty array for all actions
-            'unregister',
-            'avatar',
-            'details',
-            'changePassword'
-        ],
-        'permissions' => [
-            'authenticated'
-        ]
-    ],
-    [
-        'route' => 'admin',
-        'actions' => [
-            'deleteAccount'
-        ],
-        'permissions' => [
-            'delete'
-            //list of roles to allow
-        ]
-    ]
-]
- -

CLI

- -

For registering new command first make sure your command class extends Symfony\Component\Console\Command\Command, then you can enable the command by registering it in config/autoload/cli.global.php.

- -

Here you will also find our brand new file locker configuration so you can easily turn in on or off ( by default: 'enabled' => true )

- -

Note: The File Locker System will create a command-{command-default-name}.lock file which will not let another instance of the same command to run until the previous one has finished.

- -

You can list the existing commands running the following in a terminal:

- -
php /bin/cli.php list
- -

Note: You can take as example Dot\Cli\Command\DemoCommand

- -

Routing

- -

Providing each Module with a RoutesDelegator.php file for managing existing routes inside that specific module and an easy way of adding new ones providing the route path, Middleware that the route will use, an array of accepted methods and the route name.

- -
$app->route(
-    '/admin[/{action}[/{uuid}]]',
-    AdminController::class,
-    [
-        RequestMethodInterface::METHOD_GET,
-        RequestMethodInterface::METHOD_POST
-    ],
-    'admin'
- );
- -

Note: The optional attributes on the route path are marked between [] like 

- -

Frontend

- -

As for the frontend toolkit we chose to use Bootstrap 4.5.0 in combination with Fontawesome 5.0.6 for a minimalist but efficient design.

- -

For assembling the app.js and app.css along with handling packages from package.json we recommend using npm 7 or up.

- -

Our choice for listing raw data was Bootstrap Table because it is easy to implement and configurable in many ways.

- -

Lets take admin table as an example:

- -
<table data-toggle="table" data-url="/admin/list" data-click-to-select="true"
-       data-mobile-responsive="true" data-min-width="800"
-       data-check-on-init="true" data-id-field="uuid"
-       data-show-refresh="true" data-show-toggle="true" data-show-columns="true"
-       data-search="true" data-pagination="true" data-toolbar="#tableToolbar"
-       data-silent-sort="false" data-pagination-loop="false"
-       data-side-pagination="server" data-page-list="[10, 30, 50, 100, 200]"
-       data-page-size="30" data-single-select="true"
-       data-sort-name="created" data-sort-order="desc" id="bsTable">
-           <thead>
-                <tr>
-                     <th data-field="checked" data-checkbox="true">Id</th>
-                     <th data-field="identity" data-sortable="true">Identity</th>
-                     <th data-field="firstName" data-sortable="true">First Name</th>
-                     <th data-field="lastName" data-sortable="true">Last Name</th>
-                     <th data-field="roles" data-sortable="false">Roles</th>
-                     <th data-field="status" data-sortable="false">Status</th>
-                     <th data-field="created" data-sortable="true">Created</th>
-                </tr>
-           </thead>
-</table>
- -

Here we can tweak directly into the table data how our table looks, arrange in page or a couple of usages like the api url for pulling data, pagination, allowing multiple row select and more...

- -

Note: You`ll find in the #tableToolbar buttons that toggle a modal for adding/editing/deleting admins.

- -

- -

Frequently Asked Questions

- -
-
- When was Dotkernel Admin V4 released, and what is it built on? + -
-

Dotkernel Admin V4 was officially released on 19 July 2022. It's Dotkernel's PSR-7 Admin, an application based on Mezzio, mainly for managing and displaying tabular data from one or more database components.

-
-
-
- What are the demo credentials for trying out Dotkernel Admin V4? + -
-

Username admin and password dotadmin, on the demo at https://admin7.dotkernel.net/.

-
-
-
- What PHP version does Dotkernel Admin V4 support? + -
-

Full support for PHP 8.1, with a minimum requirement of PHP 7.4.

-
-
-
- How do you register a new module or middleware? + -
-

Register a module by adding its ConfigProvider.php in config.php. New middleware goes into pipeline.php, where you can also edit the order in which middleware runs.

-
-
-
- What packages provide the authorization guards? + -
-

dot-rbac-guard and dot-rbac work together to restrict access based on user role. authorization.global.php defines roles and their permissions, while authorization-guards.global.php restricts specific actions to those permissions.

-
-
-
- What frontend toolkit does Dotkernel Admin V4 use for displaying tables? + -
-

Bootstrap 4.5.0 combined with Fontawesome 5.0.6 for the overall design, and Bootstrap Table specifically for listing raw data because it's easy to implement and configurable in many ways.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig deleted file mode 100644 index 500213fd..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-admin-version-3-launched.html.twig +++ /dev/null @@ -1,64 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

-

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.5.0 , using Doctrine and performing basic CRUD operations over a database.

-

-

-

You can clone it from github

-

-

-

Live demo: admin7.dotkernel.net

-

-

-

Changelog:

-

-

-

Branch 3.0 is the current and default branch , and :

-

-

- -

-

-

 

-

-

-

 

-

- -

Frequently Asked Questions

- -
-
- What is Dotkernel Admin? + -
-

A basic admin panel based on Bootstrap ^4.5.0, using Doctrine to perform basic CRUD operations over a database, built as a collection of PSR-7 Middleware applications on top of Mezzio and Laminas components.

-
-
-
- Where can you see a live demo of version 3? + -
-

At admin7.dotkernel.net.

-
-
-
- What are the core requirements listed in the version 3 changelog? + -
-

Mezzio ^3.2, PHP ^7.4, Doctrine 2.7.x, and Twig 3.x, plus Bootstrap 4.5, with dot-* packages limited to version 3.x and above.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig deleted file mode 100644 index a829e871..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-api-architecture-and-components.html.twig +++ /dev/null @@ -1,391 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -
This article refers to Dotkernel API v5. Checkout out the new additions for Dotkernel API v6 to stay up-to-date.
-

Based on Enrico Zimuel's Zend Expressive API - Skeleton example, Dotkernel API runs on Mezzio microframework   andLaminas components and implements standards like PSR-3, PSR-4, PSR-7, PSR-11 and PSR-15.

-

-

-

Here is a list of the core components:

-

-

- - -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

-

-

Doctrine 2 ORM

-

-

-

For the persistence in a relational database management system we chose Doctrine ORM (object-relational mapper).

-

-

-

The benefit of Doctrine for the programmer is the ability to focus on the object-oriented business logic and worry about persistence only as a secondary priority.

-

-

-

Documentation

-

-

-

Our documentation is Postman based. We use the following files in which we store information about every available endpoint ready to be tested:

-

-

- - -

-

- -

-

-

-

Hypertext Application Language

-

-

-

For our API payloads (a value object for describing the API resource, its relational links and any embedded/child resources related to it) we chose mezzio-hal.

-

-

-

CORS

-

-

-

By using MezzioCorsMiddlewareCorsMiddleware, the CORS preflight will be recognized and the middleware will start to detect the proper CORS configuration. The Router is used to detect every allowed request method by executing a route match with all possible request methods. Therefore, for every preflight request, there is at least one Router request.

-

-

-

OAuth 2.0

-

-

-

OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user accounts on your Dotkernel API. We are using mezzio/mezzio-authentication-oauth2 which provides OAuth 2.0 authentication for Mezzio and PSR-7/PSR-15 applications by using league/oauth2-server package.

-

-

-

Email

-

-

-

It is not unlikely for an API to send emails depending on the use case. Here is another area where Dotkernel API shines. Using DotMailServiceMailService provided by dotkernel/dot-mail you can easily send custom email templates.

-

-

-

Configuration

-

-

-

From authorization at request route level to API keys for your application, you can find every configuration variable in the config directory.

-

-

-

Registering a new module can be done by including its ConfigProvider.php in config.php.

-

-

-

Brand new middlewares should go into pipeline.php. Here you can edit the order in which they run and find more info about the currently included ones.

-

-

-

You can further customize your api within the autoload directory where each configuration category has its own file.

-

-

-

Routing

-

-

-

Each module has a RoutesDelegator.php file for managing existing routes inside that specific module. It also allows an quick way of adding new routes by providing the route path, Middlewares that the route will use and the route name.

-

-

-

You can allocate permissions per route name in order to restrict access for a user role to a specific route in config/autoload/authorization.global.php.

-

-

-

Commands

-

-

-

For registering new commands first make sure your command class extends SymfonyComponentConsoleCommandCommand. Then you can enable it by registering it in config/autoload/cli.global.php.

-

-

-

File locker

-

-

-

Here you will also find our brand new file locker configuration so you can easily turn it on or off ( by default: 'enabled' => true )

-

-

-

Note: The File Locker System will create a command-{command-default-name}.lock file which will not let another instance of the same command to run until the previous one has finished.

-

-

-

PSR Standards

-

-

- - -

-

- -

-

- -

-

- -

-

- -

-

-

-

Tests

-

-

-

One of the best ways to ensure the quality of your product is to create and run functional and unit tests. You can find factory-made tests in the tests/AppTest/ folder, and you can also register your own.

-

- -

Frequently Asked Questions

- -
-
- What is Dotkernel API based on? + -
-

It's based on Enrico Zimuel's Zend Expressive API - Skeleton example, and runs on the Mezzio microframework and Laminas components, implementing PSR-3, PSR-4, PSR-7, PSR-11 and PSR-15.

-
-
-
- What ORM does Dotkernel API use for persistence, and why? + -
-

Doctrine ORM (object-relational mapper), chosen because it lets the programmer focus on the object-oriented business logic and treat persistence only as a secondary priority.

-
-
-
- How is Dotkernel API documented? + -
-

The documentation is Postman-based, using two files that store information about every available endpoint ready to be tested: documentation/Dotkernel_API.postman_collection.json and documentation/Dotkernel_API.postman_environment.json.

-
-
-
- How does CORS handling work in Dotkernel API? + -
-

MezzioCorsMiddlewareCorsMiddleware recognizes the CORS preflight request and detects the proper CORS configuration. The Router is used to detect every allowed request method by executing a route match with all possible request methods, so there's at least one Router request for every preflight request.

-
-
-
- How do you register a new module or add new middleware? + -
-

Registering a new module is done by including its ConfigProvider.php in config.php. Brand new middleware should go into pipeline.php, where you can also edit the order in which middleware runs.

-
-
-
- What does the File Locker System do? + -
-

It's enabled by default and creates a command-{command-default-name}.lock file, which prevents another instance of the same command from running until the previous one has finished.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig deleted file mode 100644 index 8303f27c..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel-frontend-version-3-launched.html.twig +++ /dev/null @@ -1,48 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.dotkernel.net

- -

Changelog:

- -

Branch 3.0 is the current and default branch , and :

- - - -

 

- -

 

- -

Frequently Asked Questions

- -
-
- What is Dotkernel Frontend built on? + -
-

It's part of the Dotkernel collection of PSR-7 Middleware applications, built on top of the Mezzio microframework using Laminas components.

-
-
-
- Where can you see a live demo of version 3? + -
-

At v3.dotkernel.net.

-
-
-
- What are the changelog requirements for version 3? + -
-

Requires Mezzio ^3.2, PHP ^7.4, Doctrine 2.7.x, and Twig 3.x, with dot-* packages limited to version 3.x and above.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig deleted file mode 100644 index 5953166e..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel3/dotkernel3-stable-release-version-1-0.html.twig +++ /dev/null @@ -1,110 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Dotkernel was updated to support Zend Expressive 3 alongside with PSR-15 middleware. - -We have updated the core packages to support PSR-15 Middleware. - -By updating the core packages both frontend and admin are easier to migrate. - -The new versions are: -frontend -> 1.0.0 -admin -> 1.0.1 - - - -If your project is a Dotkernel 3 instance (based on expressive 2), you can migrate your project to Zend Expressive 3 by following this guide. -

What's new

- -  -

Changes

- -  -

Packages Updated

- -

Zend Packages (support) Updated

- -  -

Packages Removed

- - -

Frequently Asked Questions

- -
-
- What is the headline change in this stable release? + -
-

Dotkernel was updated to support Zend Expressive 3 alongside PSR-15 middleware, with the core packages updated to support PSR-15 Middleware, making both frontend and admin easier to migrate.

-
-
-
- What are the new component versions in this release? + -
-

frontend 1.0.0 and admin 1.0.1.

-
-
-
- What PHP version does this release require? + -
-

PHP >= 7.1.

-
-
-
- Do you need to migrate your middleware code for this release? + -
-

If your middleware code is based on the dotkernel/dot-controller package, no migration is needed. If it's based on http-interop/http-middleware, you must migrate it to implement the interfaces in psr/http-server-middleware.

-
-
-
- What packages were removed in this release? + -
-

http-interop/http-middleware and webimpress/http-middleware-compatibility.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-admin.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-admin.html.twig deleted file mode 100644 index 2172b0ae..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-admin.html.twig +++ /dev/null @@ -1,161 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

With the release of PHP 8.3, the Dotkernel team has been working on updating the dependencies in our packages. Eventually, this allowed us to update our Applications as well, starting with the Admin, in release 4.3.1

- -

There were some issues and backward incompatibilities that we will list below.

- -

The full list of file changes is here.

- - - -

Updating to PHP 8.3 has not been as streamlined as in previous updates, but the advantages of doing so outweigh the extra work on the custom code to remove potential incompatibilities.

- -

- -

The Dotkernel team is focused on the cache that has been removed in the current version because doctrine/cache deleted their cache implementation classes. We are also updating our existing projects to PHP 8.3 and testing the changes on live environments to iron out any other issues.

- -

Frequently Asked Questions

- -
-
- Which Dotkernel Admin release added PHP 8.3 support? + -
-

Release 4.3.1.

-
-
-
- Which PHP versions does this release support? + -
-

Only PHP 8.2 and PHP 8.3 - support for PHP 8.1 was removed, including from the workflows.

-
-
-
- Did the logAdminVisit function change? + -
-

Yes. src/Admin/src/Service/AdminService.php's logAdminVisit function was updated and no longer returns AddressNotFoundException.

-
-
-
- Why were cache-related parameters removed from the configuration files? + -
-

The PhpFileCache class and its related parameters were removed from local.php.dist (local.php) and doctrine.global.php because doctrine/cache deleted their cache implementation classes.

-
-
-
- Are there side effects from updating the dot-twigrenderer dependencies? + -
-

Yes. doctrine/doctrine-module was removed because of a conflict, which means other packages that depended on it - such as doctrine/doctrine-laminas-hydrator, laminas-cache-storage-adapter-filesystem, laminas-cache-storage-adapter-memory, laminas-mvc and laminas-paginator - may no longer get installed, so you should check whether your project actually relies on them.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-api.html.twig deleted file mode 100644 index 1095f7ac..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-api.html.twig +++ /dev/null @@ -1,149 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

The last remaining application to be updated to support PHP 8.3 is the API, now at v4.2.1. The steps taken to perform the update are similar to the ones in the update for the Frontend.

- -

The full list of file changes is here.

- - - -

Frequently Asked Questions

- -
-
- What PHP versions does Dotkernel API support after this update? + -
-

Dotkernel API, now at v4.2.1, supports only PHP 8.2 and PHP 8.3. Support for PHP 8.1 was removed, including from the project's workflows.

-
-
-
- What changed with caching in Dotkernel API? + -
-

The PhpFileCache class and its related parameters were removed from config/autoload/doctrine.global.php, which previously required it for cache configuration. The dot-cache package was added instead, with corresponding updates to composer.json, config/autoload/doctrine.global.php, and config/config.php.

-
-
-
- Do I need to update my Doctrine queries after this update? + -
-

Yes. Wherever useQueryCache() is used in your queries, it should be replaced with setCacheable().

-
-
-
- What should I do when upgrading an older Dotkernel API installation to this version? + -
-

When upgrading from a version of Dotkernel API older than the PHP 8.3 support, you may need to run composer update and/or install Sodium, for example by running sudo dnf install php-sodium.x86_64.

-
-
-
- Where can I find the full list of changes for this update? + -
-

The full list of file changes is available in the linked pull request on GitHub for the dotkernel/api repository, and the steps taken mirror those used for the PHP 8.3 update of the Dotkernel Frontend.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-frontend.html.twig b/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-frontend.html.twig deleted file mode 100644 index 468b7eb4..00000000 --- a/src/Blog/templates/page/blog-resource/dotkernel3/php-8-3-support-in-dotkernel-frontend.html.twig +++ /dev/null @@ -1,145 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.2.0. Very much like for the Admin update before it, the Frontend must also remove support for PHP 8.1 and the no-longer-available PhpFileCache class until a replacement is implemented.

- -

The full list of file changes is here.

- - - -

Next on the menu is to implement these changes in the Dotkernel Frontend live projects and process the feedback.

- -

Frequently Asked Questions

- -
-
- What update adds PHP 8.3 support to Dotkernel Frontend? + -
-

The Dotkernel team updated the Frontend application to version 4.2.0 so it can take advantage of PHP 8.3 support in the newest packages.

-
-
-
- Which PHP versions does Dotkernel Frontend 4.2.0 support? + -
-

Version 4.2.0 supports only PHP 8.2 and PHP 8.3. Support for PHP 8.1 was removed, similar to the earlier Admin update.

-
-
-
- What changed with caching in this update? + -
-

The no-longer-available PhpFileCache class is no longer supported until a replacement is implemented. The related parameters were removed from configuration files, including removing the use Doctrine\Common\Cache\PhpFileCache; statement and the configuration and cache parameters in doctrine.global.php.

-
-
-
- What npm and Node.js versions are recommended for this update? + -
-

Alongside updated npm dependencies in packagist.json, it is recommended to use npm v10.0.4 and Node.js v20.11.0.

-
-
-
- Where can I find the full list of file changes for this update? + -
-

The full list of file changes is linked in the article, pointing to the corresponding pull request on the Dotkernel Frontend GitHub repository.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/headless-platform/complementary-admin-in-dotkernel-headless-platform.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/complementary-admin-in-dotkernel-headless-platform.html.twig deleted file mode 100644 index fca9f0d4..00000000 --- a/src/Blog/templates/page/blog-resource/headless-platform/complementary-admin-in-dotkernel-headless-platform.html.twig +++ /dev/null @@ -1,114 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

What is Dotkernel Admin?

- -

Dotkernel Admin is a in separate repository and is a fully-functional, independent application. It's built on top of the same Mezzio + Laminas foundation, very much like Dotkernel API.

- -

As with all Dotkernel applications, Admin is installed via Git within your folder of choice:

- -
git clone https://github.com/dotkernel/admin.git .
- -

Next follow the step-by-step installation guide to configure the database credentials and run migrations and fixtures that create and populate the database. It takes only a few minutes and you’ll have a ready-to-use admin panel.

- -

Make sure to change the default credentials when in production.

-
- -

The Admin has a built-in UI with authentication, user management and web interface included.

- -

Features & Architecture

- -

Dotkernel Admin comes with several useful features out of the box:

- - - -

The file architecture features a modular design with several functional modules such as:

- - - -

How They Work Together

- - - -

One of the greatest benefits to using both Dotkernel API and Admin is the unified tech stack. Both use the versatile Mezzio microframework, Laminas components, Doctrine ORM for database storage and object mapping, PHP FIG's PSR standards and share modules like authentication and dot-rbac.

- -

Put together, these ensure consistency across backend and admin tooling, while also making it easier to switch between developing one or the other. In fact, the similar architecture normally means you can employ fewer developers to manage codebases.

- -

Summary

- -

Dotkernel Admin is a complementary admin panel that provides effective reporting out of the box and is a good starting point for your admin requirements. It's not bundled by default, but is provided as a first-party, installable package designed to work seamlessly alongside your API.

- -

Together, the two applications form a cohesive system. You normally start with the API backend and add the rich administrative interface to the mix to complete your admin platform.

- -

Both API and Admin are built with the same underlying file architecture and implement the same PHP standards - PSR-7 (HTTP messages) and PSR-15 (middleware).

- -

If you're looking for a headless API with a ready-to-use admin UI that you can launch quickly, the Dotkernel solution is the way to go. Here are some of the benefits for both components:

- - - -

Frequently Asked Questions

- -
-
- What is Dotkernel Admin? + -
-

Dotkernel Admin is a fully-functional, independent application that lives in its own separate repository. It's built on the same Mezzio + Laminas foundation as Dotkernel API and includes a built-in UI with authentication, user management and a web interface.

-
-
-
- How do I install Dotkernel Admin? + -
-

Install it via Git into the folder of your choice with git clone https://github.com/dotkernel/admin.git ., then follow the step-by-step installation guide to configure the database credentials and run the migrations and fixtures that create and populate the database. Be sure to change the default credentials before going to production.

-
-
-
- What features does Dotkernel Admin include out of the box? + -
-

It ships with Role-Based Access Control powered by dot-rbac and dot-rbac-guard to manage permissions, routes and handler-level security; OpenAPI annotations in each module for documented and testable admin endpoints; and frontend tooling built on Twig and NodeJS/NPM, including build and watch scripts for JavaScript/CSS bundling.

-
-
-
- What modules make up Dotkernel Admin's architecture? + -
-

The file architecture is modular, featuring an Admin module that manages administrator users, an App module that houses core functions like authentication and error reporting, and a Setting module that stores and manages display or application settings for various report pages.

-
-
-
- How do Dotkernel API and Admin work together? + -
-

The REST API serves JSON endpoints for clients, services and mobile apps, while the Admin UI is a web-based interface that communicates with the database via its own endpoints to manage data, users and settings. Both share a unified tech stack - Mezzio, Laminas components, Doctrine ORM and PHP FIG's PSR standards - plus shared modules like authentication and dot-rbac, which keeps backend and admin tooling consistent and lets fewer developers manage both codebases.

-
-
-
- -

Additional Resources

- - -{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.html.twig deleted file mode 100644 index 47f85537..00000000 --- a/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-api-v6-the-root-of-dotkernel-headless-platform.html.twig +++ /dev/null @@ -1,88 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. Our ultimate goal is to stay relevant in the PHP ecosystem by implementing best practices recommended by the PHP community.

- -

New Features

- - - -

Some smaller changes are listed below:

- - - -

Coming Soon

- -

Currently we support:

- - - -

This setup is soon going to change. The very next version in the Roadmap for version 6.1 will implement:

- - - -

Composer updates

- -

As with all regular updates, all packages were updated to their most recent versions that still allow for an installable set of dependencies.

- -

One of the packages that stands out is ramsey/uuid. UUID version 7 is already being used for ramsey/uuid version 4 and thus will be available with no additional code changes for the release of ramsey/uuid version 5.

- -

Tweaked configuration and testing

- -

Alongside some smaller changes related to cache configuration for doctrine and route grouping, the test suite has been revised. Psalm has been replaced with PHPStan. This decision was made to remain in line with developers from popular projects like Doctrine and Composer who have also made the switch recently. A separate upgrade was dedicated to increasing the rule level to 8 which enables stricter static analysis and reveales more potential errors.

- -

Frequently Asked Questions

- -
-
- What are the main new features in Dotkernel API v6? + -
-

Dotkernel API v6 adds Content Negotiation for smoother communication between client and server, standardized error codes via mezzio-problem-details, a new Core module that centralizes common logic for easier sharing across Dotkernel applications, a custom templating solution that replaces Twig/mezzio-twigrenderer, and refactored handle delegators and injected InputFilters that reduce the number of dependencies in handlers.

-
-
-
- Why was common logic moved into a Core module? + -
-

Moving the shared logic into a Core module makes it easier to reuse that functionality across other Dotkernel applications. The Core module can be saved as a separate Git repository and then added as a submodule to any Dotkernel application.

-
-
-
- Why did Dotkernel API replace Twig with a custom templating solution? + -
-

Twig was replaced to avoid depending on mezzio-twigrenderer. The custom solution offers more reliable template handling, at the small cost of some features that are normally not used in APIs anyway.

-
-
-
- What is planned for Dotkernel API v6.1? + -
-

Dotkernel API currently supports Service Manager version 3, restricted because of constraints from some dependencies, and PHP 8.3. The next version on the roadmap, v6.1, is planned to add support for Service Manager 4 as well as PHP 8.4 and 8.5.

-
-
-
- Why was Psalm replaced with PHPStan in the test suite? + -
-

Psalm was replaced with PHPStan to remain in line with developers from popular projects like Doctrine and Composer, who have also made the switch. A separate upgrade also increased the rule level to 8, enabling stricter static analysis and revealing more potential errors.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.html.twig deleted file mode 100644 index c3508793..00000000 --- a/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-headless-platform-the-whats-hows-and-whys.html.twig +++ /dev/null @@ -1,151 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. In this article we will explore the Dotkernel implementation of the Headless Platform architecture, how to use it and what are its benefits.

- -

Components of the Dotkernel Headless Platform

- -

The Dotkernel Headless Platform contains 2 major components:

- - - -

Dotkernel API is a REST API based on the Mezzio skeleton.

- -

Dotkernel Admin, a complementary component for the API, is aimed at quickly setting up an backend for your platform.

- -

The Admin is not bundled by default but is available as a first-party, installable application designed to work seamlessly alongside your API. When the API and Admin are put together, you gain a cohesive system that contains an API-first backend with a rich administrative interface, both built with the same underlying architecture and standards support.

- -

Both of the above have a PSR-Compliant Middleware Stack to promote a lean, modular architecture and create a common ground between components from various sources. They implement PSR-7 and PSR-15 as defined by the PHP Framework Interop Group.

- -

Rather than making use of elements built into the framework, you build business logic from scratch, from handlers, to dependencies. This gives you full control over business logic and data architecture.

- -

How to use the Dotkernel Headless Platform

- -

Dotkernel API and Admin can be installed separately or together, based on your business requirements. The two are normally designed to complement each other:

- - - -

A simple project can be designed around one of the two at first, then expand to the other. For example, you can start with Dotkernel Admin to populate your database. Later on you can employ Dotkernel API to present the data to other frontends written in your programming language of choice. You can integrate the API with 3rd-party applications which gives you full control over how you present the data.

- -

Alternatively, you can start with Dotkernel API and integrate it into your existing platform. The API can manage the access permissions to keep your data secure:

- - - -

The Admin can be added later on for its simple table-based approach, its reports and graphs.

- -

Our recommendation is to use both API and Admin from the get go. We will highlight the benefits of this setup in the next chapter.

- -

Benefits of the Dotkernel Headless Platform architecture

- -

As we mentioned before, you decide what components of the Dotkernel Headless Platform are installed in your project. If you decide to use both, you gain several benefits, as we will highlight below.

- -

Can be set up to have common code between applications

- -

You can configure the file structure to use a Core module. The Core then becomes the common code repository between API and Admin, ensuring that the entities and queries are the same. The entities for your platform, whether they be products, articles or something else are the building blocks of your application. The queries interact with the entities for CRUD purposes. By having these components in a single repository used in both API and Admin, you ensure consistency when interacting with them.

- -
- -

Implement only the handlers you need

- -

Just because you have e.g. 20 entities in your Core doesn't mean you have to implement handlers for each one in both API and Admin. You only handle what you need in each application. For example, your shop may need to edit orders in Admin, but only build monthly sales reports via your API.

- -

Has a shared file structure

- -

Another advantage is the fact that the API and Admin share a file structure. When you become familiar with one, you're good to go with the other as well. This means that onboarding is easy and can be maintained by fewer developers, maybe even a single developer, at least in the beginning. Conversely, working with an API+Angular approach means you often need 2 developers working in concert to implement any new feature.

- -
-
- -
-
-
- -

Can satisfy any application size

- -

Thanks to its use of the common Core module, the API and Admin evolve together. The pair are a good starting point for anything from microservices, to enterprise-grade APIs. The initial learning curve is worth it, considering you have a large number of packages available for integrating into your platform. You decide exactly what modules to include, meaning the finished application is more lightweight. This prevents bloat and helps with technical debt in the long run.

- -

Has a versatile architecture

- -

The aim of the architecture implemented in Dotkernel applications is to provide solutions that follow several architectural designs.

- -

Clean Architecture

- -

The software components are organized into concentric layers. Code dependencies go from the outer to the inner layers which results in improved testability and independence from components like libraries, user interfaces and databases.

- -
- -

Domain-Driven Design (DDD)

- -

The main focus of the Dotkernel applications is to provide a custom solution for your business logic. Rather than implementing reusable services for a Service Oriented Architecture (SOA), we encourage DDD which only implements the specific components to satisfy your requirements.

- -

Hexagonal Architecture

- -

The hexagonal architecture divides a system into several loosely-coupled interchangeable components, such as the application core, the database, the user interface, test scripts and interfaces with other systems. This approach is an alternative to the traditional layered architecture.

- -

Supported by an active community

- -

The Dotkernel development team is actively working on investigating and implementing recommended design patterns. All Dotkernel applications and a large number of packages are still receiving updates that implement bugfixes, improvements and recommendations from the PHP community. Some updates may brake BC (backward compatibility), but this is often highlighted in companion articles that may also contain step-by-step tutorials. The Dotkernel team is available to help you with any issues related to the Dotkernel and Laminas apps and components.

- -

Frequently Asked Questions

- -
-
- What is a Headless Platform? + -
-

A Headless Platform decouples 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.

-
-
-
- What are the two main components of the Dotkernel Headless Platform? + -
-

The Dotkernel Headless Platform contains two major components: Dotkernel API, a REST API based on the Mezzio skeleton, and Dotkernel Admin, a complementary component aimed at quickly setting up a backend. The Admin is not bundled by default but is available as a first-party, installable application designed to work alongside the API.

-
-
-
- Can Dotkernel API and Admin be installed separately? + -
-

Yes. They can be installed separately or together, based on your business requirements. You can start with Dotkernel Admin to populate your database and later add Dotkernel API to present the data to other frontends, or start with Dotkernel API and integrate it into your existing platform, adding Admin later for its table-based approach, reports, and graphs.

-
-
-
- What benefits come from using both API and Admin together? + -
-

Using both together lets you share a common Core module containing the same entities and queries between the two applications, implement only the handlers you actually need, and benefit from a shared file structure that makes onboarding easy and can be maintained by fewer developers. This setup can also satisfy any application size, from microservices to enterprise-grade APIs.

-
-
-
- What architectural patterns does the Dotkernel Headless Platform follow? + -
-

The architecture is designed to be versatile, following Clean Architecture (concentric layers with dependencies flowing from outer to inner layers), Domain-Driven Design (implementing only the specific components needed for your business logic rather than generic reusable services), and Hexagonal Architecture (dividing the system into loosely-coupled, interchangeable components).

-
-
-
- -

Additional Resources

- - - -

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.html.twig deleted file mode 100644 index 2fbfa53e..00000000 --- a/src/Blog/templates/page/blog-resource/headless-platform/dotkernel-queue-asynchronous-execution-in-dotkernel-headless-platform.html.twig +++ /dev/null @@ -1,179 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

Some everyday operations are time-consuming and resource-intensive, so it's best if they run on separate machines, decoupled from the regular request-response cycle. With the addition of asynchronous execution performed by background workers, you can ensure that these operations are not lost, terminated by PHP because of timeouts, or interrupted by new requests. The most important benefit is that you prevent the main platform from overloading, allowing it to return a response, remain responsive for new requests, while the heavy lifting is scheduled to run later.

- -

The main goal for the queue is to NOT have the main platform wait for a response, because the queue will execute the task sometime in the future. The regular request-response cycle must be completed swiftly by the main platform, possibly with a confirmation message. After that, the main platform can check with the queue regularly to see if the requested task was completed.

-
- -

Why Use Dotkernel Queue?

- -

You might be inclined to go straight to Symfony Messenger and remove the middle man. There are several benefits that you gain from using Dotkernel Queue:

- - - -

Why Decouple Operations from the HTTP Request-Response?

- -

By default, the PHP settings impose a maximum execution time of 30 seconds via the max_execution_time parameter. You could extend the interval, but certainly not to hours or days for a simple request. Even that 30 second interval is unreasonably long in any website which normally expects a response in a few seconds or even under 1 second. The solution is to delegate the execution of those tasks to an outside system specifically designed for this purpose.

- -

What Are Tasks with Long Execution?

- -

Normally, the tasks delegated to the queue:

- - - -

Many tasks can take an extended interval of time to finish execution:

- - - -

There are multiple reasons for the long execution times:

- - - -

The tasks listed above take a long time no matter how many gigahertz, gigabytes and gigaflops you throw at them. Even NASA computers need minutes to compute the launch window to put a rocket in orbit.

- -

How the Dotkernel Queue Works

- -

The queue system has an active daemon that listens for TPC connections on a specific port and stores incoming messages into Redis. This method supports a large number of requests per second without overloading. Operations are then scheduled for execution when resources are available. The order of the execution uses the FIFO (First-In, First-Out) method where the oldest request is processed first, followed by newer requests.

- -
- -

Main Features

- -

We feel that a queue would be incomplete without at least these features. Several more features are in the works or scheduled for the future - we will discuss these in the next segment. We are using this queuing component in our live projects, so every feature has been tried and tested extensively in production before making it in this open-source version of Dotkernel Queue.

- -

Logging

- -

Logging is a vital feature that allows developers to monitor operations in a platform and investigate issues. It can help pinpoint the cause of an error which leads to faster bug fixes and less downtime.

- -

Detailed queue logs ensure the execution of long operations is maintainable and allows debugging. You must code your project-specific core logic to satisfy your business requirements.

- -

Security

- -

Access control is secured by the firewall which only allows requests from whitelisted IPs. This ensures that the queue responds as fast as possible, without delays from e.g. generating and authenticating dynamic tokens.

- -

Of course, a better security setup is to keep the Dotkernel Queue server accessible only via the internal network. In his case, you don't need the firewall, which simplifies the initial queue setup.

- -

Retry Mechanism

- -

If message processing fails, the internal retry feature comes into play to guarantee reliable and stable execution. The system can be configured to retry failing tasks a certain number of times - while logging the cause - before giving up and reporting a failure in execution.

- -

After configuring the numer of retries for the queue, you are responsible for the additional handling of failed messages. In some cases, it makes sense to give up on retrying an operation from the first failure, e.g. if an email is invalid, it won't fix itself on the next try. Other times, the cause of the error is temporary, e.g. a database is overloaded, but the queries are valid, so they just need to run later.

- -

Reporting

- -

The logs enable developers to investigate various metrics via console commands like:

- - - -

Dead Letter Queue (DLQ)

- -

A Dead-Letter Queue is a separate message queue that temporarily stores messages that failed execution due to errors. The reason for the errors can vary from having incomplete messages, to the 3rd party receiver not being able to process the request or not being available at all (being unresponsive).

- -

Certain errors may move the messages into the DLQ on the first try which ensures that the main queue is not overflowing with or outright blocked by messages that will never be processed in their current state. At the same time, it provides troubleshooters a central location to help identify the causes for errors. The queue will allow troubleshooters to apply fixes, then either manually push the messages back into the main queue, or delete them altogether.

- -

Future Features

- -

We are looking into ways to improve Dotkernel Queue and add new features as they are needed in the field.

- -

Priorities

- -

Currently, the queue system works on the FIFO method, like we mentioned before. Priorities determine how soon a task is to begin execution - immediately or delayed. Certain tasks may be fast enough that they can be handled as they come in, while other tasks are purposefully pushed further down the line. Priorities are set to be integrated into Dotkernel Queue in the near future.

- -

Parallel Execution

- -

Execution is made for each task, one at a time. We are investigating parallel execution of operations via multiple workers, where applicable. For example, preprocessing a report may be executed in parallel with sending emails because they are separate systems (email processing is external, while the report uses data from the internal database).

- -

Frequently Asked Questions

- -
-
- What is Dotkernel Queue? + -
-

Dotkernel Queue is a component based on Symfony Messenger used to queue asynchronous tasks. It uses the netglue/laminas-messenger adapter to integrate Symfony Messenger with the Laminas Service Manager container for Mezzio/Laminas applications.

-
-
-
- Why use Dotkernel Queue instead of using Symfony Messenger directly? + -
-

Dotkernel Queue builds on Symfony Messenger's mechanisms so it stays updated as Symfony Messenger evolves, uses netglue/laminas-messenger to work as Mezzio/Laminas-compatible middleware, shares the same file structure and Core submodule as the Dotkernel Headless Platform for seamless compatibility, works as a standalone application that can be added to an existing platform, and includes features already tried and tested in the team's own live projects.

-
-
-
- Why should long-running operations be decoupled from the HTTP request-response cycle? + -
-

By default, PHP imposes a maximum execution time of 30 seconds via the max_execution_time parameter, which is too long for a typical request. Instead of extending that limit, the solution is to delegate such tasks to an outside system designed for this purpose, so the main platform can return a response quickly and stay responsive to new requests.

-
-
-
- How does the Dotkernel Queue process messages? + -
-

An active daemon listens for TCP connections on a specific port and stores incoming messages into Redis, which supports a large number of requests per second without overloading. Messages are then scheduled for execution using the FIFO (First-In, First-Out) method, so the oldest request is processed first.

-
-
-
- What happens when a queued message fails to process? + -
-

A configurable retry mechanism retries failing tasks a set number of times, while logging the cause, before giving up and reporting a failure. Messages that ultimately fail can be moved into a Dead Letter Queue, a separate store for failed messages, where troubleshooters can identify the cause, apply a fix, and either push the message back into the main queue or delete it.

-
-
-
- What features is Dotkernel Queue planning to add in the future? + -
-

Two features are being investigated: Priorities, which would let tasks begin execution sooner or later instead of strictly following FIFO order, and Parallel Execution, which would let independent operations run at the same time via multiple workers, such as preprocessing a report in parallel with sending emails.

-
-
-
- -

Additional Resources

- - -{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/headless-platform/dotmaker-generate-common-code-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/dotmaker-generate-common-code-in-dotkernel.html.twig deleted file mode 100644 index c240467a..00000000 --- a/src/Blog/templates/page/blog-resource/headless-platform/dotmaker-generate-common-code-in-dotkernel.html.twig +++ /dev/null @@ -1,160 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. Enter DotMaker which provides a big productivity boost compared to doing everything manually by promoting consistenty and standardization for these common tasks.

- -

DotMaker is inspired by Symfony Maker Bundle in terms of functionality. Mezzio has its own CLI Tooling that performs similar actions, while DotMaker has a more opinionated naming pattern and is designed specifically for Dotkernel applications.

- -
-https://www.youtube.com/watch?v=CPDilXP2kAc -
- -

The File Structure for a Module

- -

Below is an example of the file structure for a Book module and the related files it uses from the Core module.

- -
.
-└── src/
-    ├── Book/
-    │   └── src/
-    │       ├── Collection/
-    │       │   └── BookCollection.php
-    │       ├── Handler/
-    │       │   ├── GetBookCollectionHandler.php
-    │       │   ├── GetBookResourceHandler.php
-    │       │   └── PostBookResourceHandler.php
-    │       ├── InputFilter/
-    │       │   ├── Input/
-    │       │   │   ├── AuthorInput.php
-    │       │   │   ├── NameInput.php
-    │       │   │   └── ReleaseDateInput.php
-    │       │   └── CreateBookInputFilter.php
-    │       ├── Service/
-    │       │   ├── BookService.php
-    │       │   └── BookServiceInterface.php
-    │       ├── ConfigProvider.php
-    │       └── RoutesDelegator.php
-    └── Core/
-        └── src/
-            └── Book/
-                └── src/
-                    ├── Entity/
-                    │   └── Book.php
-                    ├── Repository/
-                    │   └── BookRepository.php
-                    └── ConfigProvider.php
- -

The example above is for a Dotkernel API project. This means that it has a Collection item, but is missing Form and template files that Dotkernel Admin requires. DotMaker can tell the difference between Dotkernel applications and will create the required files for each.

-
- -

Why Use dot-maker?

- -

Even this simple example showcases the relatively large number of files and folders needed to make a new module work. A developer can perform the task manually, but there are too many places where things can be left out which generate errors and delay development. The solution is to use DotMaker which was built to help with the heavy lifting here.

- -

How dot-maker Works

- -

Let's say you want to create a new module in your application:

- - - -

Authorization is configured in different files between Dotkernel applications:

- - -
- -

DotMaker can also create individual items related to a module, like an entity or a form, as well as a new command or middleware. Below if a list of the available commands. Most of them are aimed at creating an item within existing modules, excepting the one that handles module creation, of course.

- -

The commands below work only after the basic command is added to composer.json.

-
- - - -

This tool won't do all the work for you, but after running through the maker process, you know you are working on a solid foundation that can now accept your custom code.

- -

Frequently Asked Questions

- -
-
- What does the DotMaker library do? + -
-

DotMaker (dotkernel/dot-maker) programmatically generates project files and directories that match the Dotkernel file structure inspired by Mezzio, giving a productivity boost and promoting consistency and standardization compared to creating everything manually.

-
-
-
- What inspired DotMaker and how is it different from Mezzio's own CLI tooling? + -
-

DotMaker is inspired by the Symfony Maker Bundle in terms of functionality. Mezzio has its own CLI Tooling that performs similar actions, but DotMaker uses a more opinionated naming pattern and is designed specifically for Dotkernel applications.

-
-
-
- How do I create a new module with DotMaker? + -
-

Run ./vendor/bin/dot-maker module and follow the prompts that walk you through creating each relevant item, from entities and repositories to handlers and services. DotMaker also automatically creates the module's ConfigProvider.php, tells you how to register it in config/config.php and composer.json, generates documentation in an OpenApi.php file, and tells you what command to run to generate migrations.

-
-
-
- Does DotMaker configure authorization for the new module? + -
-

No, authorization must be configured manually after the files are created, and the location differs between Dotkernel applications: Dotkernel Api uses config/autoload/authorization.global.php, while Dotkernel Admin and Dotkernel Frontend use config/autoload/authorization-guards.global.php.

-
-
-
- Can DotMaker generate individual items instead of a whole module? + -
-

Yes. DotMaker provides individual commands to create items such as a collection, command, entity, form, handler, input, input-filter, middleware, repository, service, or service-interface for an existing module. For example, ./vendor/bin/dot-maker entity creates an Entity and its associated Repository, skipping the Repository if it already exists.

-
-
-
- Do the composer make shortcuts work right away? + -
-

No, commands like composer make entity only work after the basic command has been added to composer.json; the equivalent ./vendor/bin/dot-maker form is available regardless.

-
-
-
- -

Additional Resources

- - -{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/headless-platform/evolution-pattern-versus-api-versioning.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/evolution-pattern-versus-api-versioning.html.twig deleted file mode 100644 index c08f0d10..00000000 --- a/src/Blog/templates/page/blog-resource/headless-platform/evolution-pattern-versus-api-versioning.html.twig +++ /dev/null @@ -1,130 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

Versioning in software is the practice of assigning unique identifiers (version numbers) to different releases of a software platform. These numbers help track changes, improvements, bug fixes, compatibility, and evolution over time. In particular, API versioning is the practice of assigning versions to an API so that it can change over time without breaking existing clients that rely on it.

- -

When to Use Evolution Pattern versus Versioning

- -

Technically, the two concepts are not in conflict:

- - - -

You can very well have a platform employ both strategies, if it suits your needs.

- -
ProblemPotential SolutionStrategy
Fix a typo in the parameter nameSunsetting / Do nothing (it's only a typo)Evolution pattern. Versioning is overkill.
Add a new parameterMake it optional, so it doesn't break existing implementationsEvolution pattern
Implement a BC breaking update that clients must adhere toAnnounce it in time to allow your clients to adaptEvolution pattern
Implement a major update to an endpointAdd a v2 for only that endpoint, not the whole API, and encourage clients to migrate to v2Evolution pattern + Versioning
Return responses in a totally different format (e.g. GraphQL vs REST APIs)Keep both versions activeVersioning
- -

The responsiblity is yours to decide if the costs for development and maintenance are better aimed at one strategy over the other.

- -

Practical Example from Dotkernel API

- -

We try to stay away from full rewrites. They are too risky, costly, and often times simply not the best solution. Software evolution should be gradual and organic, with the purpose of adapting to new requirements with as little impact to the codebase and clients as possible.

- -

For our Dotkernel API, we chose an evolution pattern that takes advantage of a sunsetting mechanism. The codebase remains the same, with new requirements being implemented in a targeted manner.

- -

If we are required to enhance an endpoint, one solution we employ is to distinguish between two calls based on their payload. For example, if the 3rd-party frontend calls our /user endpoint:

- - - -

We announce endpoint deprecation by including an updated response header via handler attributes.

- -
...
-#[ResourceDeprecation(
-    sunset: '2026-01-01',
-    link: 'https://docs.dotkernel.org/api-documentation/v7/tutorials/api-evolution/',
-    deprecationReason: 'Resource deprecation example.',
-    rel: 'sunset',
-    type: 'text/html'
-)]
-class OldHandler implements RequestHandlerInterface
-{
-...
- -

The values for sunset, link and deprecationReason above are customizable. By including this attribute, the reponse for the edited endpoint looks similar to the below.

- -
HTTP/1.1 200 OK
-Host: 0.0.0.0:8080
-Date: Mon, 1 Dec 2025 10:10:10 GMT
-Connection: close
-X-Powered-By: PHP/6.4.20
-Content-Type: application/json
-Permissions-Policy: interest-cohort=()
-Sunset: 2026-01-01
-Link: https://docs.dotkernel.org/api-documentation/v7/tutorials/api-evolution/;rel="sunset";type="text/html"
-Vary: Origin
- -

This header must be read and understood by developers who can then plan an update for their own software before the sunsetting date. The new functionality is normally kept in place alongside the old one for a reasonable amount of time. When the sunset date is reached, the old functionality is removed and only the new one is left in production:

- - - -

We use multiple channels to announce impactful, backward-incompatible updates for Dotkernel API:

- - - -

You should send out notifications early enough for them to reach the developers of 3rd-party software that use your API. This way they can plan and implement the update to ensure that their platforms have little or no downtime.

- -

Conclusions

- -

An evolution pattern requires good planning, which includes allocating enough time for your clients to be aware of your updates and to implement them in their own platforms. Ideally, there should be no downtime for either your, or your clients' platforms.

- -

Though we see it as avoidable in most real-life cases, versioning is still a valid strategy. We strongly believe it should be reserved for impactful architectural changes, as it's not suited for smaller changes like fixing typos in a parameter name or adding a new field.

- -

The strategies discussed in this article are not exhaustive, because ultimately you have to decide what approach works best for your use case.

- -

Frequently Asked Questions

- -
-
- What is the difference between an Evolution Pattern and API versioning? + -
-

An Evolution Pattern is a reusable, high-level strategy for modifying or evolving existing software systems over time while keeping the same codebase, often using mechanisms like sunsetting. API versioning instead maintains multiple versions of an API so that changes do not affect clients relying on older versions. The two concepts are not in conflict, and a platform can use both strategies if it suits its needs.

-
-
-
- When should you use an evolution pattern instead of versioning? + -
-

Small changes, like fixing a typo in a parameter name or adding a new optional parameter, are best handled with an evolution pattern, since versioning would be overkill. Backward-compatible-breaking updates should be announced in time via an evolution pattern. A major update to a single endpoint can combine an evolution pattern with versioning, for example by adding a v2 for just that endpoint. Only when responses need to be returned in a totally different format, such as GraphQL versus REST, is versioning the better fit.

-
-
-
- How does Dotkernel API announce endpoint deprecations? + -
-

Dotkernel API distinguishes calls based on payload (for example, an older response for a request with only an email, versus an enhanced response when both email and name are supplied). Deprecation is announced by including an updated response header via handler attributes, with customizable values for sunset, link, and deprecationReason. These updates are also communicated through documentation pages and public portal channels like blog articles and newsletters.

-
-
-
- What happens once an endpoint's sunset date is reached? + -
-

The old functionality is removed and only the new functionality remains in production. In practice this means an endpoint may start returning 404 Not Found, or a property may be removed from the response.

-
-
-
- -

Additional Resources

- - -{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.html.twig deleted file mode 100644 index 9a8e90cf..00000000 --- a/src/Blog/templates/page/blog-resource/headless-platform/implementing-time-based-one-time-password-totp-in-dotkernel.html.twig +++ /dev/null @@ -1,167 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. Our implementation follows the industry standard of using an Authenticator app to generate temporary, unique 6 digit codes that change every 30 seconds.

- -

In this article we will:

- - - -

You can also follow the installation steps in our documentation site.

-
- -

2FA with TOTP Flow

- -

Below is a simplified flow for the 2FA with TOTP mechanism.

- -
- -

How to Install dot-totp

- -

If you haven't already, install Dotkernel Admin.

- -

These installation steps should work similarly in any middleware-based application.

-
- -

The first step is to include the package into your project by running this command:

- -
composer require dotkernel/dot-totp
- -

We will follow the Dotkernel file structure and create the files in the list below. If you follow the links from the main totp integration example, you can download the files and add them to your codebase.

- - - -

You can use the trait at src/Core/src/App/src/Entity/TotpTrait.php in any entity where you need 2FA.

- -

Make sure to migrate the new columns totpSecret, totp_enabled and recovery_codes in your entity.

-
- -

There are still some code snippets in the _misc folder:

- - - -

dot-totp in Action

- -

Once you have dot-totp implemented, you can activate the feature in your admin accounts. If you navigate to your profile from the top-right image in Dotkernel Admin, you should see this box.

- -
- -

Simply click on 'Enable TOTP' to begin the activation process.

- -

We blurred out the QR code and recovery codes for this tutorial. You will receive dynamically generated versions that will be fully visible to you.

-
- -

You will need to have an Authenticator app installed on your mobile device.

-
- -
- -

Follow the instructions on the screen:

- - - -

The code refreshes every 30 seconds.

-
- -

The TOPT activation flow will list several recovery codes you can use if your mobile device isn't available.

- -
- -

Each recovery code is usable only once.

-
- -

Save the recovery codes in a secure location.

-
- -

If the code is valid, you will be logged in, and TOTP will be activated for your account.

- -

Whenever you need to log into the account, you will start by entering your username and password, like before. Since TOTP is activated, you will need to also submit the code from your Authenticator app. Alternatively, you can submit a recovery code.

- -
- -

That's it! You are now logged in securely.

- -

Frequently Asked Questions

- -
-
- What is a Time-based One-Time Password (TOTP)? + -
-

A Time-based One-Time Password (TOTP) is a security algorithm used as part of two-factor authentication (2FA) that requires both a password and an additional one-time code generated by an Authenticator app. In Dotkernel, this is implemented through the dot-totp package.

-
-
-
- Can dot-totp be used outside of Dotkernel Admin? + -
-

Yes. Although this tutorial installs dot-totp in Dotkernel Admin, the installation steps work similarly in any middleware-based PHP application.

-
-
-
- What happens if I lose access to my authenticator app? + -
-

You can log in using one of the recovery codes generated when you activated TOTP. Each recovery code is usable only once, so make sure to save them in a secure location.

-
-
-
- How often does the TOTP code change? + -
-

The code generated by your Authenticator app refreshes every 30 seconds.

-
-
-
- What database changes are required to support dot-totp? + -
-

You need to migrate three new columns onto the entity that uses the TotpTrait: totpSecret, totp_enabled, and recovery_codes.

-
-
-
- -

Additional Resources

- - -{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.html.twig deleted file mode 100644 index 5f068d7e..00000000 --- a/src/Blog/templates/page/blog-resource/headless-platform/shared-core-submodule-in-dotkernel-headless-platform.html.twig +++ /dev/null @@ -1,154 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

Dotkernel has implemented a Headless solution made up of these applications:

- - - -

This is effectively a Headless CMS architecture where the Dotkernel Admin manages the data via the database. The same data is independently exposed by Dotkernel API and used by Dotkernel Queue.

- -

What is the Core submodule?

- -

The Core submodule is a common codebase set up to be used by the applications you added to your project. It can just as well work for any project setup, e.g. two APIs, one Admin, 3 Frontends.

- -

By having a common module in your Dotkernel applications, you ensure that each of them uses entities and services in the same way. Thus, rather than making e.g. an update in each application's services, you only update the relevant service in the Core once and sync it in each application.

- -

The golden rule for the Core codebase is that it is the only place which manages the database entities.

- -

As much as possible, all Doctrine entities must reside in Core.

- -

The current location of the Core submodule is src/Core.

-
- -

How do I create a Core submodule?

- -

The full steps for creating a submodule are described in Git Tools - Submodules.

- -

There is already a Core module in some of the Dotkernel applications, but it works like any other module (App, Page or User). The Core modules are designed to be a starting point for the module's transformation into a Git submodule.

-
- -

First create a new Git repository that will contain the Core code. To create the submodule in an application, you need to have Git create the .gitmodules file in the root of the main repository by running the command below. Use the url from the new repository you just created instead of <url>:

- -
git submodule add <url>
- -

You can have multiple submodules, but for this article we will only create the Core submodule.

-
- -

The .gitmodules file maps the submodule and its corresponding local directory within the main project (e.g. src/Core). This allows Git to manage the submodule correctly, from cloning, to updating, to tracking its changes.

- -

None of the Dotkernel applications have the .gitmodules file out of the box. Only after isolating the Core into a Git submodule and pushing it to a separate Git repository does it become available to be included into any Dotkernel application.

-
- -

From now on, any changes to the Core submodule must be commited from within the Core folder, like for any other Git repository, using these commands (simplified version, provided as an example):

- -
cd <path/to/submodule>
-git add .
-git commit -m "comment"
-git push
- -

Whenever you clone the project, you simply need to init and update the submodule with these commands:

- -
git submodule init
-git submodule update
- -

Do not forget to delete the existing Core module before adding the submodule to other applications.

-
- -

How do I use the Core submodule?

- -

Once the shared Core submodule is separated and imported into each application, your platform will look something like this:

- - - -
- -

Each box in the image is a different Git repository.

-
- -

Whenever work begins on a new feature or update, the devs should normally have the most recent Core in their development environment. So in total you have four code bases which will be kept in four separate repositories.

- -

The Dotkernel applications include various entities to get you started quickly. This is not a complete list, but it should help you understand what each application is aimed toward:

- - - -

There are already shared entities which are identical, so the best place for them is within the Core submodule. Whenever you create new shared code, you should add it in the Core submodule and make sure to keep it updated in all your applications.

- -

This does not mean that all new code should be in Core, as there are plenty of instances when certain functionality is designed to only be used by one application.

-
- -

Why the Core submodule is effective

- -

This design pattern ensures:

- - - -

As your platform expands, each new application connects to the Dotkernel Headless Platform via the central API which services everything the other applications require. This ensures consistency throughout your platform, while allowing any number of outside connections as requirements arise.

- -
- -

Frequently Asked Questions

- -
-
- What is the Core submodule in Dotkernel's Headless Platform? + -
-

The Core submodule is a common codebase shared by the applications in your project (for example API, Admin and Queue). It ensures each application uses entities and services in the same way, so rather than updating each application separately, you update the relevant service in Core once and sync it across applications.

-
-
-
- What is the golden rule for the Core codebase? + -
-

The Core codebase is the only place which manages the database entities. As much as possible, all Doctrine entities must reside in Core, whose current location is src/Core.

-
-
-
- How do I create a Core submodule? + -
-

First create a new Git repository to hold the Core code. Then, in the main application repository, run git submodule add <url> using that new repository's URL. This generates the .gitmodules file, which maps the submodule to a local directory such as src/Core. From then on, changes to Core must be committed from within the Core folder like any other Git repository.

-
-
-
- How do I get the Core submodule when cloning a project? + -
-

Run git submodule init followed by git submodule update. Remember to delete the existing Core module before adding the submodule to other applications.

-
-
-
- Which entities live in Admin and API versus the shared Core? + -
-

The admin application includes admins, admin logins and settings entities, while the API includes both users and admins, as well as authentication entities. Entities that are identical across applications should instead be kept in the shared Core submodule and synced whenever they change.

-
-
-
- -

Additional Resources

- - - -

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.html.twig b/src/Blog/templates/page/blog-resource/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.html.twig deleted file mode 100644 index 6a09804a..00000000 --- a/src/Blog/templates/page/blog-resource/headless-platform/version-7-adds-postgresql-native-uuid-and-php-8-5.html.twig +++ /dev/null @@ -1,80 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. The release for Dotkernel API v7 has several added features, as well as a few that are removed.

- -

New features are:

- - - -

And these are the features that have been removed:

- - - -

Support for UUID v7

- -

The most important change in v7 is the introduction of support for native UUID. We use the package ramsey/uuid to generate the uuid and then store it in the database. In this way we have full control over the UUID version in use. This solution means you don't depend on extensions or a particular version of the database.

- -

To ensure you have support for the UUID data type, you must use PostgreSQL or MariaDB v11.4 or later.

-
- -

This also brings along a less-impactful change that still deserves mentioning: the table columns named uuid have been renamed to id.

- -

Database Configuration

- -

Based on new scenarios in our own projects, we decided to clarify the instructions regarding multiple database connections. With this update, these aspects should become more obvious:

- - - -

Frequently Asked Questions

- -
-
- What new features does Dotkernel API v7 introduce? + -
-

v7 adds support for UUID v7 using the native UUID data type, support for PostgreSQL, support for PHP 8.5, support for a database table prefix, improved database configuration, and replaces the binary data type for id (index) columns in favor of uuid.

-
-
-
- What features were removed in v7? + -
-

v7 removes the Evolution pattern's support for Method Deprecation, and drops support for MySQL, primarily because MySQL doesn't support UUID as a data type.

-
-
-
- What package generates the UUIDs, and why? + -
-

Dotkernel uses the ramsey/uuid package to generate the UUID before storing it in the database. This gives full control over the UUID version in use, so the application doesn't depend on extensions or a particular database version.

-
-
-
- Which databases support the UUID data type required by v7? + -
-

You must use PostgreSQL or MariaDB v11.4 or later to have support for the UUID data type.

-
-
-
- What else changed alongside the move to native UUID? + -
-

Table columns previously named uuid have been renamed to id. The database configuration was also clarified so it's more obvious which connection is the default and how to switch to another database connection.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.html.twig b/src/Blog/templates/page/blog-resource/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.html.twig deleted file mode 100644 index c5c3b39f..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/cors-policy-setup-in-dotkernel-using-mezzio-cors.html.twig +++ /dev/null @@ -1,116 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. In this article we will run through the steps required to fix it.

- -

Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any other origins (domain, scheme, or port) than its own from which a browser should permit loading of resources.

- -

The library we are going to use is Mezzio CORS .

- - - -

Setup

- -

Install package Mezzio CORS

- -

Run the following command in your application’s root directory:

- -
composer require mezzio/mezzio-cors
- -

Register ConfigProvider

- -

Open your application’s config/config.php file and add the following lines to the $aggregator variable:

- -

Laminas\Diactoros\ConfigProvider::class,

- -

Mezzio\Cors\ConfigProvider::class,

- -

Register Middleware

- -

Open your application’s config/pipeline.php file and add the following line (preferrably between ErrorHandlerInterface::class and the handler/middleware that will return your response):

- -

$app->pipe(CorsMiddleware::class);

- -

Don't forget to add the corresponding use at the top of the file:

- -

use Mezzio\Cors\Middleware\CorsMiddleware;

- -

Create config file

- -

Create and open file config/autoload/cors.local.php and add the following code inside it:

- -
<?php
-
-declare(strict_types=1);
-
-use Mezzio\Cors\Configuration\ConfigurationInterface;
-
-return [
-    ConfigurationInterface::CONFIGURATION_IDENTIFIER => [
-        'allowed_origins' => [
-            ConfigurationInterface::ANY_ORIGIN
-        ],
-        'allowed_headers' => ['Accept', 'Content-Type', 'Authorization'],
-        'allowed_max_age' => '600',
-        'credentials_allowed' => true,
-        'exposed_headers' => [],
-    ],
-];
- -

Note the value ConfigurationInterface::ANY_ORIGIN stored under allowed_origins. Leaving this value as is, makes your application accessible by any origin (permissive mode). To restrict access, replace it with a list of origins that should have access to your application (restrictive mode), for example:

- -
'allowed_origins' => [
-    "domain1.com", "domain2.com"
-],
- -

Don't forget to make a distributable version of you config/autoload/cors.local.php and add that to your repository.

- -

Testing

- -

Make sure your application sends the Origin header and it is set to the correct value, for example example.com.

- -

In your config/autoload/cors.local.php:

- - - -

Frequently Asked Questions

- -
-
- What causes the CORS error described in this article? + -
-

It appears when a browser blocks access to a resource on another origin because the server's response doesn't include an 'Access-Control-Allow-Origin' header, per the browser's CORS (Cross-Origin Resource Sharing) policy.

-
-
-
- What package solves CORS handling in Dotkernel? + -
-

Mezzio CORS, installed by running composer require mezzio/mezzio-cors in the application's root directory.

-
-
-
- What are the setup steps needed to enable CORS? + -
-

Register both Laminas\Diactoros\ConfigProvider::class and Mezzio\Cors\ConfigProvider::class in config/config.php, add $app->pipe(CorsMiddleware::class); to config/pipeline.php (preferably between the error handler and the handler/middleware returning your response), and create a config/autoload/cors.local.php file with your CORS configuration.

-
-
-
- What's the difference between permissive and restrictive mode? + -
-

In permissive mode, allowed_origins is set to ConfigurationInterface::ANY_ORIGIN, making the application accessible from any origin. In restrictive mode, you replace that value with a list of the specific origins that should have access.

-
-
-
- How do you test whether CORS is configured correctly? + -
-

Make sure your application sends the Origin header set to the correct value. In permissive mode you should see the expected response from the resource; in restrictive mode, if the request origin isn't listed under allowed_origins, you should see a 403 Not authorized response.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/creating-admin-accounts-in-dotkernel-api.html.twig b/src/Blog/templates/page/blog-resource/how-to/creating-admin-accounts-in-dotkernel-api.html.twig deleted file mode 100644 index 3399b563..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/creating-admin-accounts-in-dotkernel-api.html.twig +++ /dev/null @@ -1,136 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- - - -

Method 1: Using API endpoint

- -

This action can be performed only by an authenticated admin/superuser.

- -

Once authenticated call the protected endpoint POST /admin with the following JSON body:

- -
{
-   "identity": "{IDENTITY}",
-   "password": "{PASSWORD}",
-   "passwordConfirm": "{PASSWORD}",
-   "firstname": "{FIRSTNAME}",
-   "lastname": "{LASTNAME}",
-   "roles":
- }
- -

after replacing:

- - - -

NOTE: You can specify multiple admin roles under the roles key.

- -

If the submitted data is valid, the response will be similar to the below:

- -
{
-     "uuid": "d436b044-be36-11eb-9eb1-78f29ef45f43",
-     "identity": "Letha_Runolfsson96",
-     "firstName": "Oswald",
-     "lastName": "Swift",
-     "status": "active",
-     "roles": [
-     {
-       "uuid": "{ROLE_UUID}"
-     }
-   ][
-         {
-             "uuid": "eaa3d01c-9fc0-11eb-b099-78f29ef45f43",
-             "name": "admin",
-             "created": {
-                 "date": "2021-04-17 23:07:27.000000",
-                 "timezone_type": 3,
-                 "timezone": "Europe/Berlin"
-             },
-             "updated": null
-         }
-     ],
-     "created": {
-         "date": "2021-05-26 17:27:06.194613",
-         "timezone_type": 3,
-         "timezone": "Europe/Berlin"
-     },
-     "updated": {
-         "date": "2021-05-26 17:27:06.279705",
-         "timezone_type": 3,
-         "timezone": "Europe/Berlin"
-     },
-     "_links": {
-         "self": {
-             "href": "http://localhost:8080/admin/d436b044-be36-11eb-9eb1-78f29ef45f43"
-         }
-     }
- }
- -

The new admin account is ready to use.

- -

Method 2: Using terminal command

- -

Run the following command in your application's root directory:

- -
php ./bin/cli.php admin:create -i {IDENTITY} -p {PASSWORD}
- -

or

- -
php ./bin/cli.php admin:create --identity {IDENTITY} --password {PASSWORD}
- -

after replacing:

- - - -

NOTE:

- - - -

If the submitted data is valid, the outputted response is:

- -
Admin account has been created.
- -

The new admin account is ready to use.

- -

You can get more help with this command by running:

- -
php ./bin/cli.php help admin:create
- -

Frequently Asked Questions

- -
-
- What are the two ways to create an admin account in Dotkernel API? + -
-

You can either call the protected API endpoint POST /admin with a JSON body, or run the terminal command php ./bin/cli.php admin:create.

-
-
-
- Who can create an admin account via the API endpoint? + -
-

This action can be performed only by an authenticated admin/superuser, calling the protected POST /admin endpoint.

-
-
-
- What data does the POST /admin request body need? + -
-

identity, password, passwordConfirm, and roles (one or more valid admin role UUIDs, obtainable via the protected GET /admin/role endpoint). firstname and lastname are optional.

-
-
-
- What role does an admin get when created via the terminal command? + -
-

The terminal command doesn't allow specifying an admin role, so newly created accounts are given the role of admin.

-
-
-
- How do I get more help with the admin:create command? + -
-

Run php ./bin/cli.php help admin:create. Also note that if the identity or password contain special characters, they must be surrounded with double quote signs.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/database-migrations-and-how-to-use-them.html.twig b/src/Blog/templates/page/blog-resource/how-to/database-migrations-and-how-to-use-them.html.twig deleted file mode 100644 index 6655f93c..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/database-migrations-and-how-to-use-them.html.twig +++ /dev/null @@ -1,117 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. - -This article assumes that you're familiar with the basic concepts of migrations, if you're not, there's an excellent article available here - -To recap the article above, a database migration in a change in the database schema. - -  -

Benefits

-By using migrations and seeders, you ensure a better workflow for teams, as changes to the database no longer require convoluted messages. It also ensures that all team members use the same types with the same limits on each column, and someone doesn't have VARCHAR(50) while another has VARCHAR(150). It keeps the database synchronised throughout the team with minimal effort. - -When pushing to production, the deployment script will automatically run the migrations and keep the database schema in sync with the codebase. - -It requires no manual SSH'ing into production servers and no risky manual changes to production databases. - -  -

Getting started

-To setup migrations, simply follow the installation instructions in the package provided for Dotkernel. -After the package has been successfully setup, you're ready to go. -To see the available commands, simply write "php dot" in a console that's located in your project root. - -  -

How to use migrations in my project?

-Migrations are very easily adapted to any project. - -
    -
  1. Whenever you would normally manually touch the database, make a migration for doing it instead. (php dot make:migration <name>)
  2. -
  3. Give the migrations reasonable names, so it's easy to see their action, and the DB progression, in the project tree.
  4. -
  5. Run the migrate command, and watch the database automatically generate and run the SQL to update the tables and columns as configured.
  6. -
  7. Enjoy a safe and worry free database that won't suffer from manually touching it.
  8. -
-  - -Notice; Migrations are stored in version control, so whenever you pull an update, quickly run php dot migrate, as it will only ever migrate new files, and leave out the ones that have been run already. - -  -

Seeders

-If you need to add some data to a table, e.g. adding a default admin profile on a fresh installation, this can be done via Seeders. - -Migrations manage the database layouts, seeders add data to the database. - -Seeders can be configured to run every time a certain migration is run, so no matter when you or how you reset your database, it'll make sure that there's always a default admin profile when the CreateAdminTableMigration is run. - -Seeders are created the same way as migrations, and also benefits hugely from being given reasonable names that explain what they do. -
    -
  1. Create the required seeders, one for each table is the best practice, so you can have a "UserTableSeeder" and a "CarTableSeeder" etc.
  2. -
  3. Setup when these seeders should run; -
      -
    • When running a migration
    • -
    • Via a console-command, this is used to seed x amount of rows of (random) data to the database for testing.
    • -
    -
  4. -
-

Frequently Asked Questions

- -
-
- What is a database migration? + -
-

A database migration is a change to the database schema. The Dotkernel stack integrates migrations (and seeders) via a package that adds the "php dot" command to the application.

-
-
-
- What are the benefits of using migrations and seeders? + -
-

They keep the database synchronised across the team with minimal effort, so column types and limits stay consistent instead of drifting (e.g. one dev using VARCHAR(50) and another VARCHAR(150)). Deployment scripts can also run migrations automatically in production, avoiding manual SSH access and risky manual database changes.

-
-
-
- How do I create a new migration? + -
-

Run php dot make:migration <name>, giving the migration a reasonable name so its action and the database's progression are easy to follow in the project tree.

-
-
-
- What's the difference between migrations and seeders? + -
-

Migrations manage the database layout/schema, while seeders add data to the database, such as adding a default admin profile on a fresh installation.

-
-
-
- What happens when I run php dot migrate more than once? + -
-

Since migrations are stored in version control, running php dot migrate will only ever run new migration files, leaving out the ones that have already been run.

-
-
-
- -
-
- -
- -
-
-
-

Acknowledgements

-

The images in the article shows screenshots from MySQL Workbench, and the migration package used is Phinx

- -
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/doctrine-cache-in-mezzio-and-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/how-to/doctrine-cache-in-mezzio-and-dotkernel.html.twig deleted file mode 100644 index 06bcfa9f..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/doctrine-cache-in-mezzio-and-dotkernel.html.twig +++ /dev/null @@ -1,189 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

Doctrine caching in Dotkernel

- -

Following version 2 of doctrine/cache, in 2024 we published an update to this article here: https://www.dotkernel.com/dotkernel/doctrine-cache-using-symfony-cache/

-
- -

- -

- -

Using an ORM in production without any sort of cache strategy is a very bad move. The Database server chokes; lots of CPU cycles wasted only to generate metadata and queries over and over again at each request; the system slows down and so on.

- -

Following Doctrine documentation [ - 'orm_default' => [ - // it is recommended to disable doctrine cache on development - // just comment any type of cache you dont want to be applied on development - 'query_cache' => \Doctrine\Common\Cache\PhpFileCache::class, - 'metadata_cache' => \Doctrine\Common\Cache\PhpFileCache::class, - 'result_cache' => \Doctrine\Common\Cache\PhpFileCache::class - ] - ], we choose to configure doctrine cache system through psr/container.

- -

The main parts we are going to use are query_cache, metadata_cache and result_cache.

- -

As cache type we choose PhpFileCache and for result cache we are setting a lifetime of 3600 seconds.

- -

Configuration

- -

In the file: config/autoload/doctrine.global.php add the below entry:

- -
'doctrine' =>
-        ]
-    ],
-'resultCacheLifetime' => 3600
- -

In the file: config/autoload/local.php add the below entry in the doctrine section:

- -
    'configuration' =>
-    ],
- -

Metadata Cache

- -

Your class metadata is being parsed on each request. Instead of parsing this information we should cache it using one of the cache drivers.
We enabled this by adding the following metadata_cache key to our doctrine configuration:

- -
'doctrine' =>
-        ]
-    ]
- -

- -

Query Cache

- -

It is highly recommended that in a production environment you cache the transformation of a DQL query to its SQL counterpart.
It doesn't make sense to do this parsing multiple times as it doesn't change unless you alter the DQL query.
We enabled this by adding the following query_cache key to our doctrine configuration:

- -
'doctrine' =>
-        ]
-    ]
- -

Usage

- -
$query = $em->createQuery('select u from \Entities\User u');
-$query->useQueryCache(true);
- -

- -

Result Cache

- -

The result cache can be used to cache the results of your queries so that doctrine don't have to query the database or hydrate the data again after the first time.
We enabled this by adding the following result_cache key to our doctrine configuration:

- -
'doctrine' =>
-        ]
-    ]
- -

Usage

- -
$query = $em->createQuery('select u from \Entities\User u');
-$query->enableResultCache();
- -

Note: You can set a lifetime for result cache to live before it will be rewrite by simply passing the value as the first argument:

- -
$query->enableResultCache( $resultCacheLifetime);
- -

Note: You can set a custom ID for the result cache which is automatically generated for you if you don't set a custom ID yourself:

- -
$query->enableResultCache( $resultCacheLifetime, 'my_custom_id');
- -

Dotkernel Admin Example

- -

Below there is the code used in Dotkernel Admin in order to list all Admins.

- -

Is using both query_cache and result_cache.

- -
$qb = $this->getEntityManager()->createQueryBuilder();
-$qb->select('admin')
-->from(Admin::class, 'admin');
-
-if (!is_null($search)) {
-$qb->where($qb->expr()->like('admin.identity', ':search'))
-->setParameter('search', '%' . $search . '%');
-}
-
-$qb->setFirstResult($offset)
-->setMaxResults($limit);
-$qb->orderBy('admin.' . $sort, $order);
-
-return $qb->getQuery()->useQueryCache(true)->enableResultCache($this->getCacheLifetime())->getResult();
-
- -

How to return Collections

- -

For returning collections that extends Doctrine Paginator (Doctrine\ORM\Tools\Pagination\Paginator) just enable result and/or query cache before passing query builder to collection

- -
$qb = $this->getEntityManager()->createQueryBuilder();
-$qb->select('collection')
-->from(MyCollection::class, 'collection');
-
-$qb->setFirstResult($offset)
-->setMaxResults($limit);
-
-$qb->getQuery()->enableResultCache($this->getCacheLifetime())->useQueryCache(true);
-
-return new MyCollection($qb);
- -

and the MyCollection look like:

- -
<?php
-
-use Doctrine\ORM\Tools\Pagination\Paginator;
-
-/**
-* Class MyCollection
-* @package Core\Collection
-*/
-class MyCollection extends Paginator
-{
-}
- -

Frequently Asked Questions

- -
-
- Is this article still current? + -
-

The article notes that following version 2 of doctrine/cache, an updated version of this article covering Doctrine cache using Symfony Cache was published in 2024.

-
-
-
- Which three Doctrine cache types does this configure? + -
-

query_cache, metadata_cache and result_cache, configured through psr/container using PhpFileCache as the cache type.

-
-
-
- What is the default result cache lifetime? + -
-

A lifetime of 3600 seconds, set via resultCacheLifetime in config/autoload/doctrine.global.php.

-
-
-
- How do I enable query cache and result cache in code? + -
-

Call $query->useQueryCache(true) and $query->enableResultCache() on the query object. enableResultCache() can also take a lifetime as its first argument and a custom cache ID as an argument, otherwise one is auto-generated.

-
-
-
- How should caching be enabled for a Doctrine Paginator-based collection? + -
-

Enable result and/or query cache on the query builder's query before passing the query builder into the collection (a class extending Doctrine\ORM\Tools\Pagination\Paginator).

-
-
-
- -

References

- - - -

- -

- -

- -

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.html.twig b/src/Blog/templates/page/blog-resource/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.html.twig deleted file mode 100644 index f6d4c0db..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/generating-a-doctrine-migration-without-dropping-custom-tables.html.twig +++ /dev/null @@ -1,88 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- - - -

Usage

- -

Run the following command in your application’s root directory:

- -
vendor/bin/doctrine-migrations diff
- -

If you have mapping modifications, this will create a new migration file under data/doctrine/migrations/ directory. - Opening the migration file, you will notice that it contains some queries that will drop your oauth_* tables because they are unmapped (there is no doctrine entity describing them). - You should delete your latest migration with the DROP queries in it as we will create another one, without the DROP queries in it. - In order to avoid dropping these tables, you need to add a parameter called filter-expression. -

- -

The command to be executed without dropping these tables looks like this:

- -

On Windows (use double quotes):

- -
vendor/bin/doctrine-migrations diff --filter-expression="/^(?!oauth_)/"
- -

On Linux/macOS (use single quotes):

- -
vendor/bin/doctrine-migrations diff --filter-expression='/^(?!oauth_)/'
- -

Filtering multiple unmapped table patterns

- -

If your database contains multiple unmapped table groups, then the pattern in filter-expression should hold all table prefixes concatenated by pipe character (|). - For example, if you need to filter tables prefixed with foo_ and bar_, then the command should look like this: -

- -

On Windows:

- -
vendor/bin/doctrine-migrations diff --filter-expression="/^(?!foo_|bar_)/"
- -

On Linux/macOS:

- -
vendor/bin/doctrine-migrations diff --filter-expression='/^(?!foo_|bar_)/'
- -

Troubleshooting

- -

On Windows, running the command in PowerShell might still add the DROP TABLE oauth_* queries to the migration file. This happens because for PowerShell the caret (^) is a special character, so it gets dropped ("/^(?!oauth_)/" becomes "/(?!oauth_)/" when it reaches your command). Escaping it will not help either.
In this case, we recommend running the command:

- - - -

Help

- -

You can get more help with this command by running:

- -
vendor/bin/doctrine-migrations help diff
- -

- -

Frequently Asked Questions

- -
-
- Why does doctrine-migrations diff try to drop custom tables? + -
-

Because those tables are unmapped (there's no Doctrine entity describing them), so diff generates queries that drop them when it creates a new migration file.

-
-
-
- How do I avoid dropping unmapped tables when generating a migration? + -
-

Delete the migration containing the DROP queries, then re-run the diff command with a filter-expression parameter, e.g. vendor/bin/doctrine-migrations diff --filter-expression='/^(?!oauth_)/' on Linux/macOS (single quotes) or with double quotes on Windows.

-
-
-
- How do I filter multiple unmapped table prefixes at once? + -
-

Concatenate the table prefixes in the filter-expression pattern with a pipe character, for example --filter-expression='/^(?!foo_|bar_)/' to exclude tables prefixed with foo_ and bar_.

-
-
-
- Why might Windows PowerShell still drop the tables even with a filter expression? + -
-

PowerShell treats the caret (^) as a special character and strips it, turning "/^(?!oauth_)/" into "/(?!oauth_)/" by the time it reaches the command, and escaping it doesn't help. The recommended workaround is to run the command from your IDE, a Linux shell, or the Command Prompt instead.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.html.twig b/src/Blog/templates/page/blog-resource/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.html.twig deleted file mode 100644 index 3d7171e0..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/implementation-of-seo-friendly-url-in-an-generic-laminas-mezzio-app.html.twig +++ /dev/null @@ -1,101 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

Prerequisites:

- - - -

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. In this article, we discuss the importance, benefits and how to implement human-readable URLs.

- -

The first 3 reasons why we should consider implementing slugs:

- - - -

How to implement slugs into Dotkernel, a practical example:

- -

Let's consider we want to implement a feature that gives users the possibility to view other user's profile.

- -

When viewing another user's profile we have this URL: website.com/user/11 and this doesn't look so appealing, am I right?

- -

So, the solution is to format the URL in a human-readable way, like this : website.com/user/john-doe.

- -

To implement slugs into our codebase we are going to use a popular package, gedmo/doctrine-extensions.

- -

First, we need to install the package via composer by running :

- -
composer require gedmo/doctrine-extensions
- -

After installing, we need to register the package's event listener into doctrine.

- -

To do this, add the following codeblock in your doctrine global configuration. In our case it would be in the doctrine.global.php file.

- -
'doctrine' => [
-    'event_manager' => [
-        'orm_default' => [
-            'subscribers' => [
-                Gedmo\Sluggable\SluggableListener::class,
-            ]
-        ]
-    ],
-]
- -

This is all the configuration you will need, the next step is to create a new database column that will be our slug, we will add the column in the src/User/src/Entity/User.php entity and run the migrations.

- -
use Gedmo\Mapping\Annotation as Gedmo;
-
-...
-
-/**
- * @ORM\Column(name="identity", type="string", length=64, nullable=false, unique=true)
- */
-protected string $identity;
-
-/**
- * @ORM\Column(name="slug", type="string", length=64, nullable=false, unique=true)
- * @Gedmo\Slug(fields={"identity"})
- */
-protected string $slug;
- -

When generating the slug we can specify what field(s) to be used, in our case the slug will be generated using the 'identity' field, more details here.

- -

Now you can create a new user and see that the slug column was autocompleted.

- -

To wrap things up, human-readable URL's are a must nowadays, thanks to its swift implementation and multiple benefits.

- -

Frequently Asked Questions

- -
-
- What are the benefits of human-readable slugs cited in this article? + -
-

Readability and a more user-friendly experience, better search engine optimization (SEO), and improved shareability and trustworthiness of the URL.

-
-
-
- What package is used to generate slugs in this example? + -
-

gedmo/doctrine-extensions, installed by running composer require gedmo/doctrine-extensions.

-
-
-
- How is the slug listener registered with Doctrine? + -
-

By adding Gedmo\Sluggable\SluggableListener::class under the doctrine.event_manager.orm_default.subscribers configuration, e.g. in doctrine.global.php.

-
-
-
- How is the slug field configured on the entity in this example? + -
-

A new slug column is added (e.g. to src/User/src/Entity/User.php) annotated with @Gedmo\Slug(fields={"identity"}), so the slug is generated automatically from the identity field, for example when a new user is created.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.html.twig b/src/Blog/templates/page/blog-resource/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.html.twig deleted file mode 100644 index 21d8a980..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/installing-almalinux-10-in-wsl2-php-mariadb-composer-phpmyadmin.html.twig +++ /dev/null @@ -1,121 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

What You Get

- -

Like for AlmaLinux 9, the WSL recipe sets up the development environment with all the components a regular PHP developer needs:

- - - -

Requirements

- -

Make sure that you have the minimum requirements for running WSL2.

- -

OS-wise you must be running:

- - - -

Hardware-wise, you need virtualization support. On some older hardware that supports this feature, it may be disabled from the BIOS.

- -

WSL2 needs Virtual Machine Platform feature enabled. You can enable it via the PowerShell running as an Administrator:

- -
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
- -

Installing the distro

- -

Start by downloading and installing the Linux distro with this command:

- -
wsl --install -d AlmaLinux-10
- -

If everything ran ok, you are asked to enter a password at the end.

- -

Next run each of the ansible playbook commands to install the development components.

- -

Using WSL2 with AlmaLinux 10

- -

You can start AlmaLinux10 by executing this coomand in the Windows Terminal:

- -
wsl -d AlmaLinux-10
- -

Our PHP projects can be run by navigating into the root project folder and running this command:

- -
php -S 0.0.0.0:8080 -t public
- -

Then you need to navigate to http://localhost:8080 in your preferred browser.

- -

Running the Application Via Virtual Hosts

- -

An easier way to run your applications is via virtual hosts. The full instructions to set up your virtual hosts are in the Create virtualhosts page.

- -

After you set up your virtual hosts and install your project in e.g. the /var/www/example.localhost/html folder, you can run the application in the browser by accessing example.localhost.

- -

If you run into any issues, make sure to first check our FAQ page. If all else fails, create an issue with details and we can talk.

- -

Frequently Asked Questions

- -
-
- What does this WSL recipe set up? + -
-

It sets up a full PHP development environment: WSL2, AlmaLinux 10, PHP, Apache, MariaDB, Git, Composer, Node.js and PhpMyAdmin.

-
-
-
- What are the OS and hardware requirements? + -
-

Windows 10 (Version 1903+ / Build 18362.1049+ for x64, or Version 2004+ / Build 19041+ for ARM64) or Windows 11, plus hardware virtualization support. If needed, enable the Virtual Machine Platform feature via dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart in an administrator PowerShell.

-
-
-
- How do I install AlmaLinux 10 via WSL2? + -
-

Run wsl --install -d AlmaLinux-10, set a password when prompted, then run each of the Ansible playbook commands from the Dotkernel docs to install the development components.

-
-
-
- How do I run a PHP project after setup? + -
-

Either run php -S 0.0.0.0:8080 -t public from the project root and browse to http://localhost:8080, or, more easily, set up virtual hosts per the Dotkernel docs and access the project at its configured hostname (e.g. example.localhost).

-
-
-
- What should I do if I run into issues during setup? + -
-

First check the Dotkernel FAQ page. If that doesn't resolve it, create an issue on the dotkernel/development GitHub repository with details.

-
-
-
- -

Additional Resources

- - -{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.html.twig b/src/Blog/templates/page/blog-resource/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.html.twig deleted file mode 100644 index daa2cd7c..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/list-available-endpoints-in-dotkernel-api-using-dot-cli.html.twig +++ /dev/null @@ -1,123 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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 ./bin/cli.php route:list
- -

The command runs through all routes and extracts endpoint information in realtime. The output should be similar to the following:

- -
+--------+---------------------------------+--------------------------------+
-| Method | Name                            | Path                           |
-+--------+---------------------------------+--------------------------------+
-| PATCH  | account.activate                | /account/activate/{hash}       |
-| POST   | account.activate.request        | /account/activate              |
-| PATCH  | account.modify-password         | /account/reset-password/{hash} |
-| POST   | account.recover-identity        | /account/recover-identity      |
-| POST   | account.register                | /account/register              |
-| POST   | account.reset-password.request  | /account/reset-password        |
-| GET    | account.reset-password.validate | /account/reset-password/{hash} |
-| POST   | admin.create                    | /admin                         |
-| DELETE | admin.delete                    | /admin/{uuid}                  |
-| GET    | admin.list                      | /admin                         |
-| PATCH  | admin.my-account.update         | /admin/my-account              |
-| GET    | admin.my-account.view           | /admin/my-account              |
-| GET    | admin.role.list                 | /admin/role                    |
-| GET    | admin.role.view                 | /admin/role/{uuid}             |
-| PATCH  | admin.update                    | /admin/{uuid}                  |
-| GET    | admin.view                      | /admin/{uuid}                  |
-| POST   | error.report                    | /error-report                  |
-| GET    | home                            | /                              |
-| POST   | security.generate-token         | /security/generate-token       |
-| POST   | security.refresh-token          | /security/refresh-token        |
-| POST   | user.activate                   | /user/{uuid}/activate          |
-| POST   | user.avatar.create              | /user/{uuid}/avatar            |
-| DELETE | user.avatar.delete              | /user/{uuid}/avatar            |
-| GET    | user.avatar.view                | /user/{uuid}/avatar            |
-| POST   | user.create                     | /user                          |
-| DELETE | user.delete                     | /user/{uuid}                   |
-| GET    | user.list                       | /user                          |
-| DELETE | user.my-account.delete          | /user/my-account               |
-| PATCH  | user.my-account.update          | /user/my-account               |
-| GET    | user.my-account.view            | /user/my-account               |
-| POST   | user.my-avatar.create           | /user/my-avatar                |
-| DELETE | user.my-avatar.delete           | /user/my-avatar                |
-| GET    | user.my-avatar.view             | /user/my-avatar                |
-| GET    | user.role.list                  | /user/role                     |
-| GET    | user.role.view                  | /user/role/{uuid}              |
-| PATCH  | user.update                     | /user/{uuid}                   |
-| GET    | user.view                       | /user/{uuid}                   |
-+--------+---------------------------------+--------------------------------+
- -

Filtering results

- -

The following filters can be applied when displaying the routes list:

- - - -

The filters are case-insensitive and can be combined.

- -

Example

- -

Let's find which path should one call to register their user account. For this we will list routes where method is POST and name contains the string register:

- -
php ./bin/cli.php route:list --method=post --name=register
- -

The output is the following:

- -
+--------+------------------+-------------------+
-| Method | Name             | Path              |
-+--------+------------------+-------------------+
-| POST   | account.register | /account/register |
-+--------+------------------+-------------------+
- -

You can get more help with this command by running:

- -
php ./bin/cli.php route:list --help
- -

Frequently Asked Questions

- -
-
- What tool does Dotkernel API use to list available endpoints? + -
-

Starting from version 3, Dotkernel API uses dot-cli to display a list of available endpoints.

-
-
-
- Which command lists all routes? + -
-

Run php ./bin/cli.php route:list in your application's root directory. It runs through all routes and extracts endpoint information in realtime, outputting a table with Method, Name and Path columns.

-
-
-
- How can the route list be filtered? + -
-

You can filter routes by name using -i|--name, by path using -p|--path, or by method using -m|--method. The filters are case-insensitive and can be combined.

-
-
-
- How would you find the endpoint used to register a user account? + -
-

By combining the method and name filters: php ./bin/cli.php route:list --method=post --name=register, which returns the account.register route mapped to /account/register.

-
-
-
- How do you get more help on this command? + -
-

Run php ./bin/cli.php route:list --help to get more information about the command.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/migrating-dotkernel-3-from-zend-expressive-2-to-zend-expressive-3.html.twig b/src/Blog/templates/page/blog-resource/how-to/migrating-dotkernel-3-from-zend-expressive-2-to-zend-expressive-3.html.twig deleted file mode 100644 index ad71d155..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/migrating-dotkernel-3-from-zend-expressive-2-to-zend-expressive-3.html.twig +++ /dev/null @@ -1,181 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -  - -Notes before starting: - -For a better understanding of the migration and how it affects support we recommend reading Zend Expressive's article on migration. - -If your project contains old middleware it must be refactored to reflect the interfaces provided in the psr/http-server-middleware package. - -By updating your middleware the Delegates will become RequestHandlersThe interfaces are provided in the psr/http-server-handler package. - -  - -If your project only contains controller-based middleware it can be migrated by following the guides below. - -  -

Dotkernel3 Migration - Expressive 2.0 -> 3.0

-  -

Packages

-In composer.json replace the matching repositories with the following: -
"dotkernel/dot-authentication-service":"^1.0",
-"dotkernel/dot-authentication-service":"^1.0",
-"dotkernel/dot-authentication-web":"^1.0.1",
-"dotkernel/dot-authentication":"^1.0",
-"dotkernel/dot-authorization":"^0.1.2",
-"dotkernel/dot-controller":"^1.0",
-"dotkernel/dot-controller-plugin-authentication":"^1.0",
-"dotkernel/dot-controller-plugin-authorization":"^1.0",
-"dotkernel/dot-controller-plugin-forms":"^1.0",
-"dotkernel/dot-controller-plugin-flashmessenger":"^1.0",
-"dotkernel/dot-controller-plugin-mail":"^1.0",
-"dotkernel/dot-controller-plugin-session":"^1.0",
-"dotkernel/dot-form":"^1.1.1",
-"dotkernel/dot-filter":"^1.1.1",
-"dotkernel/dot-flashmessenger":"^1.0",
-"dotkernel/dot-helpers":"^1.0",
-"dotkernel/dot-inputfilter":"^1.1",
-"dotkernel/dot-mail":"^1.0",
-"dotkernel/dot-mapper":"^1.0",
-"dotkernel/dot-navigation":"^1.0",
-"dotkernel/dot-rbac-guard":"^1.0",
-"dotkernel/dot-session":"^3.0",
-"dotkernel/dot-twigrenderer":"^1.1",
-"dotkernel/dot-user":"^1.0",
-"dotkernel/dot-rbac":"^0.2.1",
-"dotkernel/dot-validator":"^1.1",
-
-"zendframework/zend-escaper":"^2.6",
-"zendframework/zend-expressive-helpers":"^5.0",
-"zendframework/zend-expressive-twigrenderer":"^2.0",
-"zendframework/zend-expressive-template":"^2.0",
-"zendframework/zend-expressive":"^3.0",
-"zendframework/zend-expressive-fastroute":"^3.0",
-"zendframework/zend-expressive-tooling":"^1.0",
-"zendframework/zend-expressive-router":"^3.0",
-"zendframework/zend-stratigility":"^3.0",
-"zendframework/zend-component-installer":"^2.0
-
-also update require-dev dependencies -
"zendframework/zend-expressive-tooling:": "^1.0",
-"zendframework/zend-component-installer": "^2.0",
-
-
-Remove packages: - -

Configurations

-

Main Configuration

-In config/config.php add the following config providers: -
-
// zend expressive & middleware factory
-\Zend\Expressive\ConfigProvider::class,
-
-// router config
-\Zend\Expressive\Router\ConfigProvider::class,
-\Zend\Expressive\Router\FastRouteRouter\ConfigProvider::class,
-
-\Zend\Expressive\Twig\ConfigProvider::class,
-\Zend\Expressive\Helper\ConfigProvider::class,
-
-// handler runner
-\Zend\HttpHandlerRunner\ConfigProvider::class,
-
-Make sure they are the first ConfigProviders or before cached config (ArrayProvider) -

Routing

-Wrap routing from config/routes.php in a callable with the following format: -
-
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
-    /** @var \Zend\Expressive\Application $app */
-    $app->route('/', , , 'home');
-};
-
-add the following use statements and make sure the names are not duplicate: -
-
use Psr\Container\ContainerInterface;
-use Zend\Expressive\Application;
-use Zend\Expressive\MiddlewareFactory;
-
-

Pipeline

-Wrap routing from config/pipeline.php in a callable with the following format: -
-
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
-    /** @var \Zend\Expressive\Application $app */
-    $app->route('/', , , 'home');
-};
-
-add the following use statements and make sure the names are not duplicate: -
-
use Psr\Container\ContainerInterface;
-use Zend\Expressive\Application;
-use Zend\Expressive\MiddlewareFactory;
-
-

Routing middleware migration

-add the following use statements -
-
use Zend\Expressive\Router\Middleware\RouteMiddleware;
-use Zend\Expressive\Router\Middleware\DispatchMiddleware;
-
-Replace the following lines to reflect the changes: - -$app->pipeRoutingMiddleware(); -> $app->pipe(RouteMiddleware::class); $app->pipeDispatchMiddleware(); -> $app->pipe(DispatchMiddleware::class); - -  - -You can check the complete guides and example files from the following links - -Migration guide for Dotkernel Frontend: github.com/dotkernel/frontend/tree/master/docs - -Migration guide for Dotkernel Admin: docs.dotkernel.org/admin-documentation (the old github.com/dotkernel/admin/tree/master/docs folder has since moved to this documentation site) - -

Frequently Asked Questions

- -
-
- What should you read before starting the migration? + -
-

For a better understanding of the migration and how it affects support, it's recommended to read Zend Expressive's article on migration regarding HTTP interop.

-
-
-
- What happens to old middleware during the migration? + -
-

If a project contains old middleware, it must be refactored to reflect the interfaces provided in the psr/http-server-middleware package. As part of this, Delegates become RequestHandlers, using interfaces from the psr/http-server-handler package.

-
-
-
- Which packages should be removed during migration? + -
-

http-interop/http-middleware and webimpress/http-middleware-compatibility should be removed from composer.json.

-
-
-
- What needs to be added to the main configuration file? + -
-

In config/config.php, several ConfigProviders need to be added – for Zend Expressive, the router, FastRoute router, Twig, helpers, and the handler runner – and they must be the first ConfigProviders or placed before the cached config (ArrayProvider).

-
-
-
- How do the routes.php and pipeline.php files change? + -
-

Routing from config/routes.php and the pipeline from config/pipeline.php both need to be wrapped in a callable of the form function (Application $app, MiddlewareFactory $factory, ContainerInterface $container), with the corresponding use statements added.

-
-
-
- What replaces pipeRoutingMiddleware() and pipeDispatchMiddleware()? + -
-

$app->pipeRoutingMiddleware(); becomes $app->pipe(RouteMiddleware::class);, and $app->pipeDispatchMiddleware(); becomes $app->pipe(DispatchMiddleware::class);.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.html.twig b/src/Blog/templates/page/blog-resource/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.html.twig deleted file mode 100644 index fa5d4a46..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/replacing-dot-console-with-dot-cli-based-on-laminas-cli.html.twig +++ /dev/null @@ -1,96 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.php file and add Dot\Cli\ConfigProvider::class, under the DK packages comment.

- -

Create bootstrap file

- -

Locate the provided vendor/dotkernel/dot-cli/bin/cli.php and copy it into your application's bin directory.

- -

This is the file you will execute your commands through.

- -

Create config file

- -

Locate the provided vendor/dotkernel/dot-cli/config/autoload/cli.global.php and copy it into your application's config/autoload directory.

- -

This file already contains a sample command (demo:command), that serves as an example on creating an registering new commands.

- -

For more information on configuring/chaining commands, see laminas-cli documentation.

- -

Usage

- -

Run the following command in your application's root directory:

- -
php ./bin/cli.php
- -

The output should look similar to this, containing information on how to start using dot-cli:

- -
Dotkernel CLI 1.0.0

Usage:
command

Options:
-h, --help Display help for the given command. When no command is given display help for the list command
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
help Display help for a command
list List commands
demo
demo:command Demo command description.
- -

Set up command as cronjob

- -

Open your crontab and add the following line:

- -
* * * * {PATH_TO_PHP_EXECUTABLE} {PATH_TO_APPLICATION}/bin/cli.php demo:command
- -

Where {PATH_TO_PHP_EXECUTABLE} needs to replaced with the full path to the PHP executable and {PATH_TO_APPLICATION} with the full path to your application's root directory.

- -

FileLocker

- -

dot-cli has a built-in locking system, called FileLocker, enabled by default. This feature prevents multiple calls to the same command overlapping eachother by making sure the latter calls won't run until the former one is finished.

- -

You can toggle this modifying in the previously created config/autoload/cli.global.php under FileLockerInterface::class -> enabled

- -

Frequently Asked Questions

- -
-
- Why was dot-console replaced by dot-cli? + -
-

dot-console was abandoned after Laminas abandoned its laminas-console package, which dot-console was based on, so dot-cli was created as its replacement.

-
-
-
- How do you install dot-cli in an application? + -
-

Run composer require dotkernel/dot-cli in the application's root directory, then register Dot\Cli\ConfigProvider::class, in config/config.php, and copy the provided bin/cli.php bootstrap file and config/autoload/cli.global.php config file into the application.

-
-
-
- What does the provided cli.global.php config file contain? + -
-

It already contains a sample command (demo:command) that serves as an example on how to create and register new commands.

-
-
-
- How do you run a dot-cli command as a cronjob? + -
-

Add a line to your crontab such as * * * * {PATH_TO_PHP_EXECUTABLE} {PATH_TO_APPLICATION}/bin/cli.php demo:command, replacing the placeholders with the full path to the PHP executable and the full path to the application's root directory.

-
-
-
- What is FileLocker? + -
-

FileLocker is a built-in locking system in dot-cli, enabled by default, that prevents multiple calls to the same command from overlapping by making sure later calls won't run until the earlier one finishes. It can be toggled in config/autoload/cli.global.php under FileLockerInterface::class -> enabled.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/using-postman-for-documentation-in-dotkernel-api-3.html.twig b/src/Blog/templates/page/blog-resource/how-to/using-postman-for-documentation-in-dotkernel-api-3.html.twig deleted file mode 100644 index f9aae3ad..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/using-postman-for-documentation-in-dotkernel-api-3.html.twig +++ /dev/null @@ -1,147 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

Using Postman documentation in Dotkernel API 3

- -

Starting from version 3.0 Dotkernel API provides it's documentation using Postman.

- -

In this article we will go into the details of creating and maintaining your application's documentation.

- - - -

Prerequisites

- -

In order to follow the steps, you will need the following:

- - - -

TIP: If you don't have a Postman account, we recommend you to create one. This way Postman is able to backup your workspace in the cloud and synchronize it across multiple devices.

- -

Introduction

- -

For better understanding on how Postman works, we need to run through the "building blocks" of Postman:

- - - -

There are more terms to cover, like Mock servers, Monitors etc but they are not relevant yet in our case.

- -

Importing documentation files

- -

Open Postman (and log in if you have an account), then follow the below steps:

- - - -
Select files to import . 2/2 selected
-NAME                 FORMAT                         IMPORT AS
-Dotkernel_API        Postman Collection v2.1        Collection
-Dotkernel_API        Postman Environment            Environment
- - - -

Under My Workspace you should see a tab called Collections. Clicking on it should display all your collections, one of them called Dotkernel_API. This collection holds all of the provided requests and serves as the documentation of your application.

- -

Dotkernel API 3 collection and environment files ar now imported and ready to use.

- -

For more information on importing files into Postman, please consult their docs.

- -

Interacting with the environment

- -

On the top right corner of Postman, you'll see the Environment quick look button (it looks like an eye). On it's left side, there's a dropdown with all available environments, one of the being called Dotkernel_API - click on it to set it as the active environment.

- -

After this, clicking the Environment quick look button will bring up a modal containing all variables found in the environment:

- -
Dotkernel_API                                                                              Edit
-VARIABLE               INITIAL VALUE                CURRENT_VALUE
-APPLICATION_URL        http://localhost:8080        http://localhost:8080
- -

If your application runs on a different host/port, click the Edit button to modify the initial/current value of the APPLICATION_URL variable.

- -

Once in Edit mode, you can also create new variables. You only have to enter the variable name and it's initial value - current value will be automatically filled in when the variable is used.

- -

Usage

- -

Creating a new request

- -

Inside the collection, right-click on any folder and click Add Request. The new request is automatically opened in a new tab, where you will:

- - - -

Sending a request

- -

Once opened, you can immediately send a request by clicking the Send button.

- -

Optionally, you can save the response as an example (click on Save Response, then select Save as example), so other developers will have a visual representation of the response without making a request. This opens a new tab, where you can manage the example.

- -

Features

- -

The collection provided by Dotkernel API 3 comes with some built-in security features.

- -

Global security

- -

Due to the hierarchy of the Collection, all requests are children of the Collection's root folder, so they automatically inherit it's Authorization settings. If you edit the Collection's root folder, you will find the following tabs:

- - - -

To disable this feature globally, set Authorization Type to No Auth or whatever value suits your application's requirements.

- -

To disable this feature at folder/request level, edit the folder/request and set Type to No Auth under the Authorization tab.

- -

Automatic Token storage

- -

Once an authorization token is re/generated, two environment variables are set/updated:

- - - -

This is achieved by applying the following modifications to both Admin/Security and User/Security folders:

- - - -

Exporting documentation

- -

TIP: You can inspect your application's documentation at any moment by clicking on the three horizontal dots (you will find them on hover next to your collection's name) then click on View documentation.

- -

Exporting collection

- -

Click on the same three horizontal dots, then select Export. You will be presented an Export collection modal asking you to choose the format of the export. Unless specified, choose the recommended version, then click Export. When asked to save the exported file, choose to overwrite your application's collection file (documentaion/Dotkernel_API.postman_collection.json).

- -

Export environment

- -

This step is optional. It is needed only if you made modifications to the environment, else you can skip it.

- -

Start by clicking on the Environment quick look button, then click Edit. Once the edit environment tab opens, locate the three horizontal dots next to the Share button on the right of the Postman window. Click them and select Export. When asked to save the exported file, choose to overwrite your application's environment file (documentaion/Dotkernel_API.postman_environment.json).

- -

Frequently Asked Questions

- -
-
- What are Postman's main "building blocks"? + -
-

Workspace (the main container holding all collections, APIs and environments), Collection (holds all requests and dependencies of a project, such as Dotkernel API's collection), Environment (holds environment-specific variables, like the application URL), and Request (the definition of an API endpoint, including method, name, description, URL, body, parameters and an example).

-
-
-
- How do you import Dotkernel API's documentation files into Postman? + -
-

Open the import modal via File -> Import (or Ctrl + O), go to the File tab, click Upload files, then select both Dotkernel_API.postman_collection.json and Dotkernel_API.postman_environment.json from the application's documentation directory, and click Import.

-
-
-
- What variable controls which host/port the requests target? + -
-

The APPLICATION_URL environment variable, found under the Dotkernel_API environment, defaults to http://localhost:8080 and can be edited via the Environment quick look button if the application runs on a different host/port.

-
-
-
- What built-in security features does the collection provide? + -
-

Global security: all requests inherit Bearer Token authorization (reading the ACCESS_TOKEN environment variable) from the Collection's root folder, which can be disabled globally or per folder/request. Automatic token storage: the Admin/Security and User/Security folders use Test scripts to set/update the ACCESS_TOKEN and REFRESH_TOKEN environment variables whenever a token is re/generated.

-
-
-
- How do you export the collection after making changes? + -
-

Click the three horizontal dots next to the collection's name, select Export, choose the recommended format, click Export, and overwrite the application's collection file (documentation/Dotkernel_API.postman_collection.json).

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/using-the-urlgenerator-work-in-fastroute.html.twig b/src/Blog/templates/page/blog-resource/how-to/using-the-urlgenerator-work-in-fastroute.html.twig deleted file mode 100644 index 105389fb..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/using-the-urlgenerator-work-in-fastroute.html.twig +++ /dev/null @@ -1,57 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -To save you some time, here's a how-to for creating and using routes in Dotkernel 3. -

Declaring routes

-When defining routes in the routes.php file, they need to follow a specific pattern, a usual route would look like this: -
$app->route('/page', , , 'page');
-
Notice the '/page' part, as this is crucial. FastRoute cannot handle a slash-suffix, which means that /page will give you the indexAction, whereas /page/ will give you a 404.
-To prevent the URLGenerator from adding a slash-suffix, we need to make sure that the optional slash is included in the optional block. The above example is correct, if we instead wrote it like this, it'd be woefully bad. -
$app->route('/page/', , , 'page');
-
Notice that the slash has changed places and is now outside the optional block. This would generate a /page/ route for the index action, which is bad, so always include any optional parts, even slashes, in the optional block.
-

Referencing routes

-Developers are used to hand-write and hard-code URLs into anchor tags, but this is a bad idea, as a URL may change and then you'd have to go through the entire app to find every single reference for it. Instead, every route we define has a name that can be used to reference it in the URLGenerator. -
$app->route('/page', , , 'page');
-
That very last part there, that's the route name, which can be referenced in any view or controller to generate a route to a specific route in the app.
-To use it in a Controller, you have access to $this->url(); which takes the route name, in this case, 'page', as the first parameter, and then any optional blocks as the second parameter. - -A controller redirect may then look like this: -
return new RedirectResponse($this->url('contact', ['action' => 'thank-you']));
-Using the URLGenerator in views is approximately the same, since you have access to the global path() method. - -The path method works the same way as the url() method, and takes the route name as the first parameter, and any optional blocks as its second parameter. This is of course done in Twig, so we need to put curly brackets around it since it's dynamic and not static HTML. It could look like this: -
{% verbatim %}{{ path(name: 'page', parameters: {action: 'thank-you'}, relative: false) }}{% endverbatim %}
- -

Frequently Asked Questions

- -
-
- What is a common quirk to watch out for when declaring routes in FastRoute? + -
-

FastRoute cannot handle a slash-suffix: for a route defined as /page, requesting /page works, but /page/ gives a 404. Any optional part, including slashes, needs to be included inside the optional block, not appended outside it – otherwise the URLGenerator ends up producing the wrong route for the index action.

-
-
-
- Why should route names be used instead of hard-coded URLs? + -
-

Hand-writing and hard-coding URLs into anchor tags is a bad idea because a URL may change and then every reference throughout the app would need to be updated. Instead, every defined route has a name that can be referenced through the URLGenerator.

-
-
-
- How do you generate a URL from a route name inside a controller? + -
-

Use $this->url(), passing the route name as the first parameter and any optional blocks as the second parameter, for example in a redirect: return new RedirectResponse($this->url('contact'));.

-
-
-
- How do you generate a URL from a route name inside a Twig view? + -
-

Use the global path() function, which works the same way as url(), taking the route name as the first parameter and any optional blocks as the second parameter, wrapped in curly brackets since it's dynamic Twig code.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/what-is-cross-origin-token-redemption.html.twig b/src/Blog/templates/page/blog-resource/how-to/what-is-cross-origin-token-redemption.html.twig deleted file mode 100644 index c9a53c43..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/what-is-cross-origin-token-redemption.html.twig +++ /dev/null @@ -1,57 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

When a token is issued from one domain, it is typically encrypted and signed to prevent tampering and ensure that it can only be used by the intended recipient. When the token is redeemed on a different domain, the receiving domain must first verify the authenticity of the token by checking the signature and decrypting the token.

- -

JSON Web Token (JWT)

- -

One common method of cross-origin token redemption is to use the JWT standard. JWTs are a compact, URL-safe means of representing claims to be transferred between two parties. They consist of three parts:

- - - -

In order to redeem a JWT on a different domain, the receiving domain must first verify the signature using the algorithm specified in the header and the secret key that was used to sign the token. Once the signature is verified, the domain can then read the claims from the payload to determine if the user is authorized to access the requested resources.

- -

OAuth 2.0

- -

Another method of cross-origin token redemption is to use the OAuth 2.0 standard. OAuth 2.0 is an open standard for authorization that enables a third-party application to obtain limited access to an HTTP service. In this method, when a user is logged in to one website and wants to access resources from another website, the first website will redirect the user to the second website and pass a token. The second website will then validate the token and provide the user with access to the requested resources.

- -

In summary, 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 is achieved by verifying the signature of the token and decrypting it and by using standards like JWT or OAuth 2.0.

- -

Frequently Asked Questions

- -
-
- What is cross-origin token redemption? + -
-

It 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, commonly used when a user needs to access resources from multiple domains.

-
-
-
- What are the three parts of a JSON Web Token (JWT)? + -
-

A JWT consists of a header (information about the token, including the signing algorithm), a payload (the claims, i.e. the pieces of information the token conveys), and a signature (used to verify the token has not been tampered with).

-
-
-
- How does a receiving domain verify a JWT? + -
-

The receiving domain first verifies the signature using the algorithm specified in the header and the secret key used to sign the token, then reads the claims from the payload to determine if the user is authorized to access the requested resources.

-
-
-
- How does OAuth 2.0 handle cross-origin token redemption? + -
-

OAuth 2.0 is an open standard for authorization enabling a third-party application limited access to an HTTP service. When a user logged in to one website wants to access resources on another, the first website redirects the user to the second and passes a token, which the second website validates before granting access.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/how-to/what-is-psr-7-and-how-to-use-it.html.twig b/src/Blog/templates/page/blog-resource/how-to/what-is-psr-7-and-how-to-use-it.html.twig deleted file mode 100644 index 4315f240..00000000 --- a/src/Blog/templates/page/blog-resource/how-to/what-is-psr-7-and-how-to-use-it.html.twig +++ /dev/null @@ -1,101 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

What is PSR-7 and how to use it

PSR-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.

Any web application using this set of interfaces is a PSR-7 application.

More about interfaces and interfaces examples can be found here.

Interfaces

In this section the Interfaces methods will be listed. The purpose of this list is to help in finding the methods when working with PSR-7. This can be considered as a cheatsheet for PSR-7 interfaces. The interfaces defined in PSR-7 are the following:

Class NameDescription
PsrHttpMessageMessageInterfaceRepresentation of a HTTP message
PsrHttpMessageRequestInterfaceRepresentation of an outgoing, client-side request.
PsrHttpMessageServerRequestInterfaceRepresentation of an incoming, server-side HTTP request.
PsrHttpMessageResponseInterfaceRepresentation of an outgoing, server-side response.
PsrHttpMessageStreamInterfaceDescribes a data stream
PsrHttpMessageUriInterfaceValue object representing a URI.
PsrHttpMessageUploadedFileInterfaceValue object representing a file uploaded through an HTTP request.

Working with PSR-7

The following examples will illustrate how basic operations are done in PSR-7.

Zend Diactoros is an implementation for PSR-7 interfaces. It will be used to illustrate these examples. Installation guide for Zend Diactoros: Zend Diactoros Documentation – Installation

All other PSR-7 implementations should have the same behaviour.

Alternative PSR-7 implementations.

To use the Zend Diactoros classes add this at the beggining of the php file:

use ZendDiactorosServerRequestFactory;
-use ZendDiactorosResponse;
-// autoloading
-$request = ServerRequestFactory::fromGlobals($_SERVER, $_GET, $_POST, $_COOKIE, $_FILES);
-$response = new Response();
-

Note: The article applies to all PSR-7 implementations from this point forward.

Working with HTTP Headers

Adding headers to response:

$response->withHeader('My-Custom-Header', 'My Custom Message');
-

Appending values to headers

$response->withAddedHeader('My-Custom-Header', 'The second message');
-

Checking if header exists:

$response->hasHeader('My-Custom-Header'); // will return true
-

Note: My-Custom-Header was only added in the Response

Getting comma-separated values from a header (also applies to request)

// getting value from request headers
-$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8"
-// getting value from response headers
-$response->getHeaderLine('My-Custom-Header'); // will return:  "My Custom Message; The second message"
-
// getting value from request headers
-$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8"
-// getting value from response headers
-$response->getHeaderLine('My-Custom-Header'); // will return:  "My Custom Message; The second message"
-

Getting array of value from a header (also applies to request)

// getting value from request headers
-$request->getHeader('Content-Type'); // will return:
-// getting value from response headers
-$response->getHeader('My-Custom-Header'); // will return:
-

Removing headers from HTTP Messages

// removing a header from Request, removing deprecated "Content-MD5" header
-$request->withoutHeader('Content-MD5');
-// removing a header from Response
-// effect: the browser won't know the size of the stream
-// the browser will download the stream till it ends
-$response->withoutHeader('Content-Length');
-

Working with HTTP Message Body

When working with the PSR-7 there are two methods of implementation:

1. Getting the body separately

This method makes the body handling easier to understand and is useful when repeatedly calling body methods. (You only call getBody() once). Using this method mistakes like $response->write() are also prevented.

$body = $response->getBody();
-// operations on body, eg. read, write, seek
-// ...
-// replacing the old body
-$response->withBody($body);
-// this last statement is optional as we working with objects
-// in this case the "new" body is same with the "old" one
-// the $body variable has the same value as the one in $request, only the reference is passed
-

2. Working directly on response

This method is useful when only performing few operations as the $request->getBody() statement fragment is required

$response->getBody()->write('hello');
-

Getting the body contents

The following snippet gets the contents of a stream contents.

Note: Streams must be rewinded, if content was written into streams, it will be ignored when calling getContents() because the stream pointer is set to the last character, which is \0 – meaning end of stream.


-$body = $response->getBody();
-$body->rewind(); // or $body->seek(0);
-$bodyText = $body->getContends();
-

Note: If $body->seek(1) is called before $body->getContents(), the first character will be ommited as the starting pointer is set to 1, not 0. This is why using $body->rewind() is recommended.

Append to body

$response->getBody()->write('Hello'); // writing directly
-$body = $request->getBody(); // which is a `StreamInterface`
-$body->write('xxxxx');
-

Prepend to body

Prepending is different when it comes to streams. The content must be copied before writing the content to be prepended. The following example will explain the behaviour of streams.

// assuming our response is initially empty
-$body = $repsonse->getBody();
-// writing the string "abcd"
-$body->write('abcd');
-// seeking to start of stream
-$body->seek(0);
-// writing 'ef'
-$body->write('ef'); // at this point the stream contains "efcd"
-

Prepending by rewriting separately

// assuming our response body stream only contains: "abcd"
-$body = $response->getBody();
-$body->rewind();
-$contents = $body->getContents(); // abcd
-// seeking the stream to beginning
-$body->rewind();
-$body->write('ef'); // stream contains "efcd"
-$body->write($contents); // stream contains "efabcd"
-

Note: getContents() seeks the stream while reading it, therefore if the second rewind() method call was not present the stream would have resulted in abcdefabcd because the write() method appends to stream if not preceeded by rewind() or seek(0).

Prepending by using contents as a string

$body = $response->getBody();
-$body->rewind(); // or $body->seek(0);
-$bodyText = $body->getContends();
-

More information can be found in the PSR-7 article in Dotkernel3 documentation portal.

Sources: PSR-7: HTTP messages zend-diactoros

- -

Frequently Asked Questions

- -
-
- What is PSR-7? + -
-

PSR-7 is a set of common interfaces defined by the PHP Framework Interop Group, representing HTTP messages and URIs for use when communicating through HTTP. Any web application using this set of interfaces is a PSR-7 application.

-
-
-
- What interfaces does PSR-7 define? + -
-

PSR-7 defines MessageInterface (representation of an HTTP message), RequestInterface (an outgoing, client-side request), ServerRequestInterface (an incoming, server-side HTTP request), ResponseInterface (an outgoing, server-side response), StreamInterface (a data stream), UriInterface (a URI value object), and UploadedFileInterface (a file uploaded through an HTTP request).

-
-
-
- What implementation is used to illustrate the PSR-7 examples? + -
-

Zend Diactoros is used as the PSR-7 implementation for the examples, though the article notes that all other PSR-7 implementations should have the same behaviour.

-
-
-
- How do you add or check headers on a response? + -
-

Use $response->withHeader('My-Custom-Header', 'My Custom Message') to add a header, $response->withAddedHeader(...) to append another value to it, and $response->hasHeader('My-Custom-Header') to check whether it exists.

-
-
-
- Why must a stream be rewound before reading its contents? + -
-

If content was written into a stream, calling getContents() without first calling $body->rewind() (or $body->seek(0)) will return nothing, because the stream pointer is left at the end of the stream after writing.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.html.twig b/src/Blog/templates/page/blog-resource/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.html.twig deleted file mode 100644 index 6a55ccbc..00000000 --- a/src/Blog/templates/page/blog-resource/javascript/codelobster-php-edition-free-php-html-css-javascript-editor-ide.html.twig +++ /dev/null @@ -1,59 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

Free PHP, HTML, CSS, JavaScript editor (IDE) - Codelobster PHP Edition

-
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 .

- - -  - -

Also there are special plugins for work with:

- -  - -http://www.codelobster.com - -  - -

Frequently Asked Questions

- -
-
- What kind of code highlighting does Codelobster PHP Edition support? + -
-

It highlights code depending on its type, even within a mixed file, so HTML, PHP and Javascript sections are each highlighted according to their own language, with a choice of color schemes including those of popular IDEs.

-
-
-
- What frameworks and CMS does Codelobster PHP Edition have plugins for? + -
-

There are plugins for the Drupal and Joomla CMS, the CakePHP, CodeIgniter, Symfony and Yii PHP frameworks, the JQuery JavaScript library, the WordPress blogging engine, and the Smarty template engine.

-
-
-
- What other tools does the editor include besides autocompletion and highlighting? + -
-

It includes an HTML/CSS inspector similar to Firebug, context help (F1 opens documentation for the current tag, attribute or function), a PHP debugger for incremental execution with variable watching, an SQL manager for database operations, FTP support for working directly on a remote server, and a portable option that requires no installation.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/javascript/intro-to-jquery.html.twig b/src/Blog/templates/page/blog-resource/javascript/intro-to-jquery.html.twig deleted file mode 100644 index f54fd411..00000000 --- a/src/Blog/templates/page/blog-resource/javascript/intro-to-jquery.html.twig +++ /dev/null @@ -1,97 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Starting with the 1.5 release, Dotkernel will make the switch from Dojo to jQuery. - -On jQuery's website, there's an excellent "Getting started with jQuery tutorial", as well as a extensive documentation for the framework, so I'll just go over a few basic concepts and common examples. - -

The jQuery object

- jQuery is the global object that contains all of jQuery's functionality. You will probably want to use $ instead which is a synonim of the same object, only faster to type. - -It contains a few useful methods such as jQuery.map, but it's mostly used with a selector parameter to retrieve a set of matched elements. -

Selectors

-jQuery supports the same selectors as CSS (note: all selectors will work even in browsers that don't actually have CSS support for them) - -

Examples:

-
// the element that matches an id
-$("#someId");
-// all elements with a certain class
-$(".someClass")
-// all odd rows from a certain table
-$("table#monthlyStats tbody tr:nth-child(odd)")
-
-The results of these expressions is a jQuery object that contains one, more, or no matched elements. -

Manipulating matched elements

-There are a number of functions that can be applied to a jQuery object, for example: -
$("input#username").addClass("error")
-Since most of these functions will also return the same jQuery object, they can be chained. For example, the following expression will set the text of the #errors element and fade it in: -
$("#errors").text("The was an eror").fadeIn()
-

Events

-Adding events in Javascript instead of using "onclick" attributes in HTML means that the markup is much cleaner, and you get better support for more events in all browsers. - -In the following example, a click event is attached to an element. The function that will be called when the event is fired (callback) is passed as an argument to the click function -
$("#helpButton").click(function(){
-    $("#helpMessage").show()
-});
-
-

Ajax

-jQuery has many ajax helper functions, here is a simple example that will replace the contents of an element with data loaded from the server: -
$.get("get-news.php", function(result){
-   $("#news").html(result)
-})
-
-

Putting it all together

-The following snippet can be used on a registration form to check whether the username already exists, before the form is submitted. -
$(document).ready(
-  $("input#username").blur(function(){
-    $.getJSON(
-      "/check-username.php",
-      {
-         username:$("input#username").val()
-      },
-      function(data){
-        if (data.taken === true){
-          $("input#username").addClass("taken")
-        }else{
-          $("input#username").removeClass("taken")
-        }
-      }
-  });
-);
-
- -

Frequently Asked Questions

- -
-
- Why is Dotkernel switching to jQuery? + -
-

Starting with the 1.5 release, Dotkernel makes the switch from Dojo to jQuery.

-
-
-
- What is the jQuery object, and what is $? + -
-

jQuery is the global object that contains all of jQuery's functionality. $ is a synonym for the same object that's faster to type, and is mostly used with a selector parameter to retrieve a set of matched elements.

-
-
-
- What selectors does jQuery support? + -
-

jQuery supports the same selectors as CSS, and these selectors work even in browsers that don't natively support them, for example $("#someId"), $(".someClass"), or $("table#monthlyStats tbody tr:nth-child(odd)").

-
-
-
- How do you attach a click event to an element? + -
-

Pass a callback function to the click function on a jQuery selection, for example: $("#helpButton").click(function(){ $("#helpMessage").show() });. This keeps markup cleaner than using "onclick" attributes in HTML.

-
-
-
- How does jQuery handle Ajax requests? + -
-

jQuery provides ajax helper functions such as $.get() and $.getJSON(), for example loading data from the server and replacing an element's contents with $.get("get-news.php", function(result){ $("#news").html(result) }).

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/javascript/javascript-email-validator.html.twig b/src/Blog/templates/page/blog-resource/javascript/javascript-email-validator.html.twig deleted file mode 100644 index fccf2acc..00000000 --- a/src/Blog/templates/page/blog-resource/javascript/javascript-email-validator.html.twig +++ /dev/null @@ -1,34 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Problem: email should allow +/- characters in user, - in domain. -dash (-) should be allowed anywhere in an email address or domain. -plus (+) is allowed in the username (many people use this for categorization, especially at gmail) - -Solution : -
var regex = new RegExp("^+(\.+)*@+(\.+)*\.({2,})$","i");
-This will validated also emails like: username1+username2@gmail-domain.co.uk - -

Frequently Asked Questions

- -
-
- What problem does this email validator solve? + -
-

Common email regex patterns fail to allow the plus (+) character in the username and the dash (-) character anywhere in the address or domain. The plus sign is used by many people, especially on Gmail, for categorization, and dashes commonly appear in domain names, so a validator that rejects them is too strict.

-
-
-
- What is the suggested regex solution? + -
-

The article proposes the regular expression ^+(\.+)*@+(\.+)*\.({2,})$ (case-insensitive) as a replacement that permits both the plus and dash characters in the appropriate parts of the address.

-
-
-
- What kind of email addresses does this regex validate? + -
-

According to the article, this pattern will also successfully validate addresses like username1+username2@gmail-domain.co.uk, which combine a plus-separated username with a dashed domain.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/licensing/mit-versus-lgpl-in-practice-dotkernel-case.html.twig b/src/Blog/templates/page/blog-resource/licensing/mit-versus-lgpl-in-practice-dotkernel-case.html.twig deleted file mode 100644 index 69188590..00000000 --- a/src/Blog/templates/page/blog-resource/licensing/mit-versus-lgpl-in-practice-dotkernel-case.html.twig +++ /dev/null @@ -1,60 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. We implemented a workaround detailed below.

- -

Detailing the problem

- -

The package in question is matomo/device-detector which requires the developers to share their derivative code publicly. This goes against our contractual obligations to the client to keep their custom code business and enterprise friendly. The conflict that results in this scenario may cause legal issues in the future.

- -

The solution

- -

Some companies explicitly steer clear of scenarios of this nature and Dotkernel has decided to do the same. Our solution for Dotkernel applications and libraries is to discard the use of 3rd party packages with LGPL v3. In this particular case we used the matomo/device-detector package in our dotkernel/dot-user-agent-sniffer package for the purpose of identifying the user agent and using the results in internal reports.

- -

ALL of Dotkernel's packages are licensed under MIT which has no restrictions regarding the source code, other than keeping the license and copyright notice in a file within each package. Other non-restrictive licenses include Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MPL-2.0 and OSL-3.0. Some of them are used by the dependencies in Dotkernel’s packages instead of MIT, but are still deemed acceptable.

- -

The resolution

- -

The dotkernel/dot-user-agent-sniffer package must follow the LGPL v3 license, beginning with version 3.4.0. Our admin application will not use the features from matomo/device-detector, but will contain instructions on how to add the package, if other developers intend to use it.

- -

Important note

- -

Warning: Any application using dotkernel/dot-user-agent-sniffer with a version lower than 3.4.0 is still a legal liability. We at Dotkernel will not abandon the issue as is, but are looking into a solution to bring back the device detector functionality in the future under a less restrictive license. For now we will not include dotkernel/dot-user-agent-sniffer in any of our applications by default.

- -

Rob Allen has created an automation to check the licenses of installed packages. You can follow his article here.

- -

Frequently Asked Questions

- -
-
- Why did Dotkernel need a workaround for this package? + -
-

An analysis found that an upstream package, matomo/device-detector, was licensed under LGPL v3, which requires developers to share their derivative code publicly. Since Dotkernel's own projects use the MIT license, and the more restrictive license would have to apply to the whole application, this conflicted with contractual obligations to keep client code business and enterprise friendly.

-
-
-
- Where was the LGPL v3 package being used? + -
-

The matomo/device-detector package was used in Dotkernel's dot-user-agent-sniffer package to identify the user agent and use the results in internal reports.

-
-
-
- What solution did Dotkernel adopt? + -
-

Dotkernel decided to discard the use of third-party packages licensed under LGPL v3. All of Dotkernel's own packages are licensed under MIT, which has no restrictions on the source code other than keeping the license and copyright notice in a file within each package. Other non-restrictive licenses considered acceptable include Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MPL-2.0 and OSL-3.0.

-
-
-
- What changed in dot-user-agent-sniffer as a result? + -
-

Starting with version 3.4.0, the dotkernel/dot-user-agent-sniffer package must follow the LGPL v3 license. The admin application will not use the features from matomo/device-detector, but will contain instructions for developers who intend to add the package themselves.

-
-
-
- Is it still risky to use an older version of dot-user-agent-sniffer? + -
-

Yes. Any application using dotkernel/dot-user-agent-sniffer with a version lower than 3.4.0 is still a legal liability. Dotkernel states it will not include dotkernel/dot-user-agent-sniffer in any of its applications by default for now, while it looks into bringing back the device detector functionality under a less restrictive license in the future.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.html.twig b/src/Blog/templates/page/blog-resource/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.html.twig deleted file mode 100644 index f27a7401..00000000 --- a/src/Blog/templates/page/blog-resource/middleware/dotkernel-light-improvements-psr-15-handlers-vite-phpstan.html.twig +++ /dev/null @@ -1,95 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

Dotkernel Light is the smallest complete Mezzio application - a PSR-15 pipeline, routing and templating, with nothing to strip out. A good starting point for a simple site, like a presentation site, that can be expanded as needed.

- -

Dotkernel Light has come a long way since our last update. It's just as useful for a presentation site, but our perfectionist devs thought we could do better. Let's see what we have improved to make your use of Light that much more convenient.

- -

Migrating from controllers to handlers

- -

dotkernel/dot-controller isn't going away, but we have switched to PSR-15 handlers instead. PSR-7 HTTP message interfaces defines the request handler as an individual component that processes a request and produces a response. Given that it's used in a middleware architecture, the request can be wildly different (e.g. filtered, augmented) by the time it reaches your custom code. Handlers split the code into more manageable chunks and cleaner file structure. An added benefit is it allows us to figure out an application's functionality easier. We will expand on this aspect further down in the article, in the 'Naming pattern' chapter.

- -

Implementing PSR-15 compatible handlers

- -

We are striving to implement every feature with the most modern standards and design patterns in mind. The PHP Framework Interop Group defines PSR-15 as common interfaces for HTTP server request handlers and HTTP middleware that use HTTP messages as described by PSR-7. The MVC design pattern is considered obsolete by respected members of the PHP community, so we are replacing it with Middleware and HTTP message desing pattern.

- -

Adopting a naming pattern for PSR-15 handlers

- -

Now the file names reflect the functionality at a glance. We devised a naming pattern for our PSR-15 handlers that highlights the method, resouce and action for each file to make navigation and onboarding that much easier. We like to keep things tidy, as well as informative.

- -

Implementing Vite

- -

Vite replaces webpack as our static modules bundler. It concatenates and compresses .css and .js files to enable faster downloads. It also preprocesses .scss files into .css. As far as devs using Light are concerned, Vite was configured to work similarly to webpack. We decided the technical advantages like easier dependency management and execution speed justified the migration to Vite. It's also highly recommended by the PHP community.

- -

Replacing Psalm with PHPStan

- -

Both Psalm and PHPStan are respected and widely-used static analysis tools. Some of the big names in the PHP ecosystem have opted for the same switch, so we decided to follow suit. Ultimately, the functionality is similar to our previous tool, but the growing interest and improved detection quality make PHPStan a great choice for our applications. PHPStan is configured to run at rule level 8 to help prevent bugs and write better code.

- -

Other updates

- -

Some of the smaller updates involve:

- - - -

Frequently Asked Questions

- -
-
- Why did Dotkernel Light switch from controllers to handlers? + -
-

Dotkernel Light switched to PSR-15 handlers instead of dotkernel/dot-controller (which isn't going away, but is no longer used here). Since the request can be filtered or augmented by the time it reaches custom code in a middleware architecture, handlers split the code into more manageable chunks and a cleaner file structure, and make an application's functionality easier to figure out.

-
-
-
- What naming pattern is used for the new PSR-15 handlers? + -
-

A naming pattern was devised that highlights the method, resource, and action for each handler file, so file names reflect their functionality at a glance, making navigation and onboarding easier.

-
-
-
- Why was Vite adopted instead of webpack? + -
-

Vite replaces webpack as the static modules bundler, concatenating and compressing .css and .js files and preprocessing .scss files into .css. It was configured to work similarly to webpack for developers using Light, and was chosen for its easier dependency management, execution speed, and its recommendation by the PHP community.

-
-
-
- Why was Psalm replaced with PHPStan? + -
-

Both Psalm and PHPStan are respected, widely-used static analysis tools with similar functionality, but growing interest and improved detection quality led Dotkernel to follow other big names in the PHP ecosystem and switch to PHPStan, which is configured to run at rule level 8 to help prevent bugs and write better code.

-
-
-
- What other smaller updates were made to Dotkernel Light? + -
-

Other updates include adding support for PHP 8.4, enabling PHPStan and Qodana to run for PHP 8.4, cleaning up the error configuration file, updating laminas/laminas-coding-standard to its latest major version, updating composer.json dependencies and removing obsolete items, implementing a more reliable and efficient post-install script, and removing dotkernel/dot-twigrenderer in favor of a direct implementation of mezzio/mezzio-twigrenderer.

-
-
-
- -

Additional resources

- -

Dotkernel Light Git

- -

Dotkernel Light demo

- -

Dotkernel Light documentation

- -

PHP Framework Interop Group

- -

PSR-15: HTTP Server Request Handlers

- -

Static Analysis – Replacing Psalm with PHPStan

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.html.twig b/src/Blog/templates/page/blog-resource/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.html.twig deleted file mode 100644 index 14437e23..00000000 --- a/src/Blog/templates/page/blog-resource/middleware/handling-dynamic-routing-using-fastroute-in-dotkernel.html.twig +++ /dev/null @@ -1,148 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

RoutesDelegator.php is used to configure the routes in Dotkernel applications. Routing allows web applications to respond to user requests by executing the correct code based on URL paths. The dynamic aspect discussed in this article moves the relevant items for each route into the local.php file. The RoutesDelegator then reads the route configuration and generates the routes.

- -

This is the first in a series of articles for switching from controllers to handlers that are PSR-15 compliant. It's aimed at static pages alone, so any other method like post, put or delete will return a 405 status code.

-
- -

The old way of doing things

- -

In the past, declaring routes had a more static, hard-coded approach. All our modules have a RoutesDelegator.php file in the src folder for each module. Each route would have an entry like the one below:

- -
$app->get('/page[/{action}]', [GetPageViewHandler::class], 'page');
- -

This route would be used for urls like /page/about or /page/who-we-are that we will reference later.

- -

These are the required items:

- - - -

In a live application the route list can quickly grow to many more entries, each with its own logic:

- - - -

These items can be grouped into modules, meaning you have to dig into multiple RoutesDelegators when a routing error occurs.

- -

The new approach

- -

The update centralizes the route configuration in the config/autoload/local.php file. Here is how the routing looks out of the box:

- -
'routes'      => [
-    'page' => [
-        'about'      => 'about',
-        'who-we-are' => 'who-we-are',
-    ],
-],
- -

This supports the urls /page/about and /page/who-we-are.

- -

Let's list the components under the routes array:

- - - -

In this setup, the RoutesDelegator file doesn't need to be touched most of the time. This is how it generates the routes for each page:

- -
$routes = $container->get('config')['routes'] ?? [];
-foreach ($routes as $prefix => $moduleRoutes) {
-    foreach ($moduleRoutes as $routeUri => $templateName) {
-    $app->get(
-        sprintf('/%s/%s', $prefix, $routeUri),
-        GetPageViewHandler::class,
-        sprintf('%s::%s', $prefix, $templateName)
-    );
-    }
-}
- -

Each item under routes array for each module will have its own entry. The result is you have a get for the about page and another for the who-we-are page.

- -

Advanced configuration

- -

You have the same versatility as before for route configuration. Below we explore some scenarios that showcase the control you still have over the routing.

- -

The template file is taken from the path, but you can still change that in the handler based on your requirements. Replace $template = $request->getAttribute(RouteResult::class)->getMatchedRouteName(); with a template of your choosing, like $template = 'my-template';

- -

Say you want to change the url from /page/about, to /about for SEO purposes. All you have to do is remove the $moduleName parameter from sprintf('/%s/%s', $moduleName, $routeUri), making it sprintf('/%s', $routeUri),. Take care not to break other routes, though!

- -

If you need to change the url parameter, you can do that in the local.php file. Edit 'about' => 'about', into 'about-us' => 'about',. This changes the original url to /page/about-us, but uses the same about template as before.

- -

Do you need a dynamic parameter? Edit the route entry from 'about' => 'about', to 'about/{id}' => 'about',. This expands the matched url to support something like /page/about/us, /page/about/company, /page/about/123 which will allow you to customize each url with different content in the handler. All you need is to use $request->getAttribute('id') to tell what page you are on.

- -

Frequently Asked Questions

- -
-
- What is the goal of this dynamic routing update? + -
-

The goal is to replace the static way of creating routes with a more dynamic implementation, resulting in a cleaner approach that is easier to set up and review at a glance.

-
-
-
- How were routes configured in the old, static approach? + -
-

Each module had its own RoutesDelegator.php file in the src folder, with hard-coded route entries such as $app->get('/page', , 'page');. Each route needed a method (e.g. get), a path used to direct execution to a handler, a handler like GetPageViewHandler, and a unique route name. In a live application the route list can grow to many entries grouped across modules, meaning multiple RoutesDelegators had to be checked when a routing error occurred.

-
-
-
- How does the new approach centralize route configuration? + -
-

The update centralizes the route configuration in the config/autoload/local.php file, moving the relevant items for each route into a routes array. RoutesDelegator.php then reads this configuration and generates the routes automatically, so it doesn't need to be touched most of the time.

-
-
-
- What do the entries under the routes array represent? + -
-

For a module like page, the module name is the top-level key, the array key (e.g. about) is used to build the page's path, and its value (e.g. about) is the template file. This supports urls like /page/about and /page/who-we-are.

-
-
-
- Can routes still be customized beyond the default setup? + -
-

Yes. You can change the template used by editing the handler's template attribute, change a URL for SEO purposes by removing the module name parameter from the generated path, change the URL segment by editing the key in local.php (e.g. turning 'about' into 'about-us' while keeping the same template), or add a dynamic parameter (e.g. 'about/{id}') and read it in the handler via $request->getAttribute('id').

-
-
-
- Does this update support methods other than GET? + -
-

No - this is the first in a series of articles for switching from controllers to PSR-15 compliant handlers, and it is aimed at static pages alone, so any other method like post, put or delete will return a 405 status code.

-
-
-
- -

Additional resources

- -

Dotkernel Light

- -

Dotkernel Light Routing How to

- -

FastRoute

- -

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.html.twig b/src/Blog/templates/page/blog-resource/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.html.twig deleted file mode 100644 index b7a2274d..00000000 --- a/src/Blog/templates/page/blog-resource/middleware/replacing-controllers-with-psr-15-compliant-handlers-in-dotkernel-light.html.twig +++ /dev/null @@ -1,165 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

- -

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.

- -

We strive to keep our applications up-to-date with the recommended design guidelines. This ensures that we keep the applications secure, while also implementing standards widely adopted by the PHP community.

- -

What makes handlers better than controllers?

- -

It's all fine and good if you have one large controller file with several actions, but handlers split the code into manageable chunks that make your life a lot easier in the long run. This follows the first of the SOLID principles. SOLID stands for:

- -
-
- -

We are focusing on that first S in the SOLID acronym. Instead if having multiple actions we would normally include in controllers, with single-responsibility the handlers separate each action into its own class. This makes handlers easier to maintain, refactor and test.

- -

Expanding your application is also helped by handlers. Rather than searching for a place to fit in that new code, simply create a handler to keep thing orderly. Your future self or the programmer that takes over from you will thank you for it.

- -

Refactoring is always easier if you don't have to worry about edge cases that are unexpectedly not supported because of an error on your part. Simpler code means refactoring steps are more obvious.

- -

Writing tests for actions that have multiple branches tends to take a lot of time. Since handlers only deal with a single action, your tests only have to inject or bind mocks for that specific action.

- -

How to implement the Page handler

- -

When it comes to Dotkernel Light, replacing controllers with handlers means we don't need the dot-controller package any more. Go ahead and remove it, along with any Controllers you may have.

- -

Below we are going to detail how to set up the GetPageViewHandler. If you already have Controllers in your application, you will have to repeat the steps below for each controller. Based on your application, you may have to split your actions over multiple Handlers.

- -

For Dotkernel Light we were able to combine the functionality of most of the old Controller's actions under a single Handler, since the actions performed a single task - displaying static content. The only exception is IndexHandler.php which we opted to leave separate, but its setup is similar to GetPageViewHandler.php.

- -

Handlers use the ConfigProvider in each module to map factories under getDependencies(). You should already have delegators and aliases, but make sure to add GetPageViewHandler under the factories key and remove any reference to PageController.

- -
public function getDependencies(): array
-{
-    return ,
-        ],
-        'factories'  => ,
-        'aliases'    => ,
-    ];
-}
- -

GetPageViewHandlerFactory.php adds the template as a dependency, making it available in the Handler. We don't need the PageControllerFactory.php file, so go ahead and delete it.

- -
<?php
-
-declare(strict_types=1);
-
-namespace Light\Page\Factory;
-
-use Light\Page\Handler\GetPageViewHandler;
-use Mezzio\Template\TemplateRendererInterface;
-use Psr\Container\ContainerExceptionInterface;
-use Psr\Container\ContainerInterface;
-use Psr\Container\NotFoundExceptionInterface;
-
-use function assert;
-
-class GetPageViewHandlerFactory
-{
-    /**
-     * @param class-string $requestedName
-     * @throws NotFoundExceptionInterface
-     * @throws ContainerExceptionInterface
-     */
-    public function __invoke(ContainerInterface $container, string $requestedName): GetPageViewHandler
-    {
-        $template = $container->get(TemplateRendererInterface::class);
-        assert($template instanceof TemplateRendererInterface);
-
-        return new GetPageViewHandler($template);
-    }
-}
- -

GetPageViewHandler.php determines the template file name from the route name and displays it. No dynamic elements are included, since we are dealing only with static pages right now. If you haven't already, delete PageController.php.

- -
<?php
-
-declare(strict_types=1);
-
-namespace Light\Page\Handler;
-
-use Laminas\Diactoros\Response\HtmlResponse;
-use Mezzio\Router\RouteResult;
-use Mezzio\Template\TemplateRendererInterface;
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestInterface;
-use Psr\Http\Server\RequestHandlerInterface;
-
-class GetPageViewHandler implements RequestHandlerInterface
-{
-    public function __construct(
-        protected TemplateRendererInterface $template,
-    ) {
-    }
-
-    public function handle(ServerRequestInterface $request): ResponseInterface
-    {
-        $template = $request->getAttribute(RouteResult::class)->getMatchedRouteName();
-
-        return new HtmlResponse(
-            $this->template->render($template)
-        );
-    }
-}
- -

And that's it! These are the bare essentials to get yourself started with handlers for a website that displays static pages.

- -

Frequently Asked Questions

- -
-
- What is the goal of replacing controllers with PSR-15 handlers? + -
-

The goal is to implement PSR-15 handlers into Dotkernel Light, keeping the application up to date with recommended design guidelines, ensuring it stays secure while implementing standards widely adopted by the PHP community.

-
-
-
- What makes handlers better than controllers? + -
-

Handlers split code into manageable chunks instead of one large controller file with several actions, following the Single-responsibility Principle (the "S" in SOLID). Each action becomes its own class, which makes handlers easier to maintain, refactor and test, easier to expand with new functionality, and simpler to write focused tests for since each handler only deals with a single action.

-
-
-
- Do you still need the dot-controller package? + -
-

No. Replacing controllers with handlers in Dotkernel Light means the dot-controller package is no longer needed and can be removed, along with any existing Controllers.

-
-
-
- How is GetPageViewHandler set up? + -
-

Handlers are registered in each module's ConfigProvider, mapping factories under getDependencies(). GetPageViewHandler is added under the factories key, replacing any reference to PageController, and GetPageViewHandlerFactory adds the template renderer as a dependency so it's available in the handler, which determines the template file name from the matched route name and renders it.

-
-
-
- Does every action need its own separate handler? + -
-

Not necessarily. For Dotkernel Light, most of the old Controller's actions were combined under a single Handler since they performed a single task - displaying static content. The exception is IndexHandler.php, which was kept separate but is set up similarly to GetPageViewHandler.php. Depending on your application, you may need to split actions over multiple handlers.

-
-
-
- -

Additional resources

- -

PSR-15

- -

Dotkernel Light

- -

While the PR for replacing controllers with handlers might not be as focused on the task, because it implements a few other bells and whistles, it's worth reviewing since it includes all the coding details.

- -

Mezzio features

- -

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.html.twig b/src/Blog/templates/page/blog-resource/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.html.twig deleted file mode 100644 index 0cbcdf56..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin.html.twig +++ /dev/null @@ -1,126 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. Open Windows Terminal and execute the following command:

- -
wsl -v
- -

The output should look similar to this:

- -
WSL version: 2.2.4.0
-Kernel version: 5.15.153.1-2
-WSLg version: 1.0.61
-MSRDC version: 1.2.5326
-Direct3D version: 1.611.1-81528511
-DXCore version: 10.0.26091.1-240325-1447.ge-release
-Windows version: 10.0.22631.3737
- -

If instead of the above output, you get an error, it means that WSL is not (completely) installed on your machine. In this case, please follow the instructions found in this guide, then return to this page and continue with the next step.

- -

Download and install AlmaLinux 9

- -

Open Microsoft Store and search for AlmaLinux. From the results, select AlmaLinux 9 and install it. Once installed, clicking on Open will open it in Windows Terminal.

- -

The installer will prompt you for your username, your password and password confirmation.

- -

Setup AlmaLinux 9

- -

While still in the AlmaLinux 9 terminal, start executing the following commands.

- -

Install required packages:

- -
sudo dnf install epel-release dnf-utils http://rpms.remirepo.net/enterprise/remi-release-9.rpm -y
- -

Update installed packages:

- -
sudo dnf upgrade -y
- -

Install Ansible:

- -
sudo dnf install ansible -y
- -

Clone our development environment setup package:

- -
git clone https://github.com/dotkernel/development.git
- -

Navigate to the directory with the Ansible recipes:

- -
cd ~/development/wsl/
- -

Using your preferred text editor, open config.yml where you must fill in the empty fields. Save and close the file.

- -

Run Step 1 of the installer script (it will prompt you for the password you entered during the installation process):

- -
ansible-playbook -i hosts install.yml --ask-become-pass
- -

Restart AlmaLinux 9:

- - - -

Navigate back to the directory with the Ansible recipes:

- -
cd ~/development/wsl/
- -

Run Step 2 of the installer script (once again, it will prompt you for the password you entered during the installation process):

- -
ansible-playbook -i hosts install.yml --ask-become-pass
- -

Once finished, the installation is complete, your AlmaLinux 9 development environment is ready to use.

- -

Test AlmaLinux 9

- -

Check if everything works by opening in your browser:

- - - -

The complete guide with additional features is available here.

- -

Frequently Asked Questions

- -
-
- How do I check if my machine is ready for WSL2? + -
-

Open Windows Terminal and run wsl -v. If it returns version information (WSL, Kernel, WSLg, MSRDC, Direct3D, DXCore, Windows versions), WSL2 is installed. If you get an error instead, WSL is not completely installed and you need to follow the linked setup guide first.

-
-
-
- How do I download and install AlmaLinux 9? + -
-

Open Microsoft Store, search for AlmaLinux, select AlmaLinux 9 from the results, and install it. Once installed, clicking Open launches it in Windows Terminal, where the installer prompts for your username, password, and password confirmation.

-
-
-
- What are the main setup steps inside AlmaLinux 9? + -
-

Install epel-release, dnf-utils and the Remi repository RPM, upgrade installed packages, install Ansible, clone the dotkernel/development Git repository, navigate to its wsl directory, fill in the empty fields in config.yml, then run the installer script's Step 1 with ansible-playbook -i hosts install.yml --ask-become-pass (it will prompt for the password set during installation).

-
-
-
- Why do I need to restart AlmaLinux 9 partway through setup? + -
-

After Step 1 of the installer script, you press Control+D, open Windows Terminal, stop AlmaLinux 9 with wsl -t AlmaLinux9, and start it again with wsl -d AlmaLinux9. Then you navigate back to the Ansible recipes directory and run Step 2 of the installer script (again with ansible-playbook -i hosts install.yml --ask-become-pass), which prompts for the password again. Once Step 2 finishes, the AlmaLinux 9 development environment is ready.

-
-
-
- How do I verify the installation worked? + -
-

Check three URLs in your browser: http://localhost/ for Apache's default home page, http://localhost/info.php for the PHP info page, and http://localhost/phpmyadmin/ for PhpMyAdmin, logging in with username root and the root password configured in config.yml under mariadb -> root_password.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/aptana-php-installation-in-aptana-2-x.html.twig b/src/Blog/templates/page/blog-resource/php-development/aptana-php-installation-in-aptana-2-x.html.twig deleted file mode 100644 index 64c053ec..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/aptana-php-installation-in-aptana-2-x.html.twig +++ /dev/null @@ -1,19 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -As all aptana fans know, Aptana PHP plugin was discontinued in Aptana 2.x, in favor of PDT. - But PDT is a joke, not suitable for professional PHP development, major features are missing. - So if you want to continue using Aptana PHP, that's what need to be done: -
    -
  1. Aptana -> Help -> Install New Software
  2. -
  3. Add http://update.aptana.com/install/php
  4. -
  5. Then select Aptana PHP and install it.
  6. -
- -In case you don't have yet a SVN plugin, go to -
    -
  1. Aptana -> Help -> Install Aptana Features
  2. -
  3. Others -> Subclipse
  4. -
  5. Follow the instructions
  6. -
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.html.twig b/src/Blog/templates/page/blog-resource/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.html.twig deleted file mode 100644 index b836ae0e..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/better-unicode-support-in-mysql-5-5-utf8mb4.html.twig +++ /dev/null @@ -1,19 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Beginning with version 5.5 of MySQL , utf8mb4 character set was introduced, in order to better support Unicode. -Further reading: MySQL and Unicode , also directly related to PHP - -Sample dk.sql file, part of Dotkernel framework, was updated in revision 793 - -Only one downside , is not possible to use anymore VARCHAR(255), as you will get the error: -
#1071 - Specified key was too long; max key length is 767 bytes
-Explanation here ; so we use instead VARCHAR(150) - -Now we should change the connection charset. In config/application.ini file, edit the below line : -
database.params.charset = utf8mb4
-Other changes that can be made in my.cnf file are all related to replacing the string utf8_* with utf8mb4_* -
character_set_server=utf8mb4
-
collation_server=utf8mb4_general_ci
-
collation_server=utf8mb4_unicode_ci
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.html.twig b/src/Blog/templates/page/blog-resource/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.html.twig deleted file mode 100644 index 2a17c859..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/database-seeding-doctrine-data-fixtures-vs-phinx.html.twig +++ /dev/null @@ -1,257 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

Previous versions of Dotkernel 3 used cakephp/phinx for seeding the database. While the package did a great job at populating the database, we wanted more. We wanted more flexibility - so we started to search for alternatives.

- -

Because we are using Doctrine as our database abstraction layer, the obvious choice was to give doctrine/data-fixtures a shot and use it as our database seeder, instead of phinx - but there's a catch.

- -

The catch

- -

Using the doctrine/data-fixtures package provides a concrete implementation of data fixtures, without a CLI interface.

- -

We need a way to interact with the fixtures, so we created a package (dotkernel/dot-data-fixtures) to provide a CLI interface. While there are alternatives that can achieve this out-of-the-box we wanted something slim (in terms of dependencies) and easy to use.

- -

- -

NOTE:

- -

The package dotkernel/dot-data-fixture does NOT depend on other Dotkernel packages. The only dependency is Doctrine

- -

Installation

- -

Run the following command in you project directory:

- -
$ composer require dotkernel/dot-data-fixtures
- -

Register the package's ConfigProvider.php in config/config.php.

- -
\Dot\DataFixtures\ConfigProvider::class,
- -

In doctrine.global.php (or your custom doctrine config file) add a new key fixtures, in the doctrine array, the value should be a valid path to a folder where your fixtures can be found.

- -
return [
-    'dependencies' => [ ... ],
-    'doctrine' => [
-        ...,
-        'fixtures' => getcwd() . '/data/doctrine/fixtures',
-    ],
-];
- -

Make sure the path is valid before proceeding to the next step.

- -

The fixtures can be found in the /data/doctrine/fixtures folder, but you can create a custom folder for them. We choose this location because the migrations live in the /data/doctrine/migrations folder.

- -

The last step is to register 2 commands.

- -

We will register the commands to work with the doctrine default CLI but you can register them as normal commands also.

- -

Create a new php file bin/doctrine if you don't have it already and paste the below code block.

- -
<?php
-
-use Doctrine\ORM\Tools\Console\ConsoleRunner;
-use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
-
-require_once 'vendor/autoload.php';
-
-$container = require getcwd() . '/config/container.php' ;
-
-$entityManager = $container->get(\Doctrine\ORM\EntityManager::class);
-
-$commands = [
-    $container->get(Dot\DataFixtures\Command\ExecuteFixturesCommand::class),
-    $container->get(Dot\DataFixtures\Command\ListFixturesCommand::class),
-];
-
-ConsoleRunner::run(
-    new SingleManagerProvider($entityManager),
-    $commands
-);
- -

The installation is complete, we can verify it by running the following command in our terminal.

- -
php bin/console
- -

It should print out all the doctrine CLI commands available, including our fixtures commands.

- -
- -

Usage

- -

List all available fixtures, by order of execution:

- -
php bin/doctrine fixtures:list
- -
- -

By using this command you can check the execution order of your fixtures before executing them.

- -

Executing fixtures:

- -

To execute all fixtures run : php bin/doctrine fixtures:execute

- -

To run a specific fixture run : php bin/doctrine fixtures:execute --class={FixtureClassName}

- -

Example :

- -
php bin/console fixtures:execute --class=RoleLoader
- -

Creating fixtures

- -

When creating fixtures, we need to :

- - - -

Example :

- -
<?php
-
-namespace Frontend\Fixtures;
-
-use Doctrine\Common\DataFixtures\FixtureInterface;
-use Doctrine\Persistence\ObjectManager;
-use Frontend\User\Entity\UserRole;
-
-/**
- * Class RoleLoader
- * @package Frontend\Fixtures
- */
-class RoleLoader implements FixtureInterface
-{
-    public function load(ObjectManager $manager): void
-    {
-        $adminRole = new UserRole();
-        $adminRole->setName('admin');
-
-        $userRole = new UserRole();
-        $userRole->setName('user');
-
-        $guestRole = new UserRole();
-        $guestRole->setName('guest');
-
-        $manager->persist($adminRole);
-        $manager->persist($userRole);
-        $manager->persist($guestRole);
-
-        $manager->flush();
-    }
-}
- -

Ordering fixtures

- -

We can order fixtures using 2 methods :

- -
  1. By Order - you can specify the order of execution, by implementing OrderedFixtureInterface interface.
  2. By dependencies - let's you specify dependency fixtures, chaining fixtures and executing them in the right order
- -

Practical example:

- -

Requirements: Seed the database with a new admin user.

- -

We will use the second method to order fixtures and need 2 fixtures to achieve this, one of them will create a new user and the other will create a new admin role. In this case the order matters, we can't create the admin user without having an admin role.

- -

Create new php file in data/doctrine/fixtures with the name RoleLoader.php.

- -

This fixture will be executed first and create our user roles.

- -
<?php
-
-namespace Frontend\Fixtures;
-
-use Doctrine\Common\DataFixtures\FixtureInterface;
-use Doctrine\Persistence\ObjectManager;
-use Frontend\User\Entity\UserRole;
-
-/**
- * Class RoleLoader
- * @package Frontend\Fixtures
- */
-class RoleLoader implements FixtureInterface
-{
-    public function load(ObjectManager $manager): void
-    {
-        $adminRole = new UserRole();
-        $adminRole->setName('admin');
-
-        $userRole = new UserRole();
-        $userRole->setName('user');
-
-        $guestRole = new UserRole();
-        $guestRole->setName('guest');
-
-        $manager->persist($adminRole);
-        $manager->persist($userRole);
-        $manager->persist($guestRole);
-
-        $manager->flush();
-    }
-}
- -

The second fixture is UserLoader.php and will contain the following code:

- -
<?php
-
-namespace Frontend\Fixtures;
-
-use Doctrine\Common\DataFixtures\DependentFixtureInterface;
-use Doctrine\Common\DataFixtures\FixtureInterface;
-use Doctrine\Persistence\ObjectManager;
-use Frontend\User\Entity\User;
-use Frontend\User\Entity\UserDetail;
-use Frontend\User\Entity\UserRole;
-
-class UserLoader implements FixtureInterface, DependentFixtureInterface
-{
-    public function load(ObjectManager $manager): void
-    {
-        $user = new User();
-        $user->setIdentity('admin@test.com');
-        $user->setPassword(password_hash('admin', PASSWORD_DEFAULT));
-        $user->setIsDeleted(false);
-        $user->setHash('hash');
-
-        $userDetail = new UserDetail();
-        $userDetail->setUser($user);
-        $userDetail->setFirstName('Admin');
-
-        $user->setDetail($userDetail);
-
-        $roleRepository = $manager->getRepository(UserRole::class);
-
-        /** @var UserRole $adminRole */
-        $adminRole = $roleRepository->findOneBy(['name' => UserRole::ROLE_ADMIN]);
-
-        $user->addRole($adminRole);
-
-        $manager->persist($user);
-
-        $manager->flush();
-    }
-
-    public function getDependencies(): array
-    {
-        return [
-           RoleLoader::class
-        ];
-    }
-}
- -

Notice how UserLoader.php implements 2 interfaces, FixtureInterface and DependentFixtureInterface.

- -

The method getDependencies() returns an array containing the dependencies (fixtures) that need to be executed prior to the current one.

- -

After running all the fixtures using php bin/doctrine fixtures:execute the output should look like this:

- -
- -

RoleLoader was executed before UserLoader because UserLoader had RoleLoader as a dependency.

- -

To wrap things up, we implemented a database seeder and saw a practical example of how to use it.

- -

More details about Fixtures in this blogpost: https://matthiasnoback.nl/2018/07/about-fixtures/ .

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/end-of-support-for-php-5-2-x-branch.html.twig b/src/Blog/templates/page/blog-resource/php-development/end-of-support-for-php-5-2-x-branch.html.twig deleted file mode 100644 index f2aa2576..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/end-of-support-for-php-5-2-x-branch.html.twig +++ /dev/null @@ -1,30 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -PHP 5.2.14 was just released . And this is the end of support for 5.2.x branch of PHP . -
NOTE:This release marks the end of the active support for PHP 5.2. Following this release the PHP 5.2 series will receive no further active bug maintenance. Security fixes for PHP 5.2 might be published on a case by cases basis. All users of PHP 5.2 are encouraged to upgrade to PHP 5.3
- PHP 5.3.3 is just released ,  so is time to upgrade every project  to PHP 5.3.x branch, and also upgrade all servers to 5.3.x - -

Frequently Asked Questions

- -
-
- What does the release of PHP 5.2.14 mean for the 5.2.x branch? + -
-

PHP 5.2.14 marks the end of active support for the PHP 5.2 branch. After this release, PHP 5.2 will receive no further active bug maintenance, though security fixes might still be published on a case-by-case basis.

-
-
-
- What should users of PHP 5.2 do? + -
-

All users of PHP 5.2 are encouraged to upgrade to PHP 5.3.

-
-
-
- Which PHP version was just released alongside the end of 5.2.x support? + -
-

PHP 5.3.3 was just released, so it is time to upgrade every project to the PHP 5.3.x branch and also upgrade all servers to 5.3.x.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.html.twig b/src/Blog/templates/page/blog-resource/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.html.twig deleted file mode 100644 index 4a47cf06..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/floating-point-arithmetic-why-is-int-0-7-0-1-10-7.html.twig +++ /dev/null @@ -1,102 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - - - -So the output for (0.7 + 0.1) * 10 is: - -var_dump((0.7+0.1)*10); # float(8) -var_dump(intval((0.7+0.1)*10)); # int(7) - -Now let's try it with 0.6 instead of 0.7 - -var_dump((0.6+0.1)*10); # float(7) -var_dump(intval((0.6+0.1)*10)); # int(7) -

-

How does the CPU understand these numbers?

-The CPU makes the calculations binary, the floating point numbers are represented as follows: - -IEEE Short Real: 32 bits     1 bit for the sign, 8 bits for the exponent, and 23 bits for the mantissa. Also called single precision. -IEEE Long  Real: 64 bits     1 bit for the sign, 11 bits for the exponent, and 52 bits for the mantissa. Also called double precision. - -The numbers that can easily be represented binary are: 1/(2^1). 1/(2^2) . 1/(2^3), 1/(2^4) etc. This because they have mantissa equal to 1 (enconded as 0). - -The number values can only be represented exactly if they can be represented by this formula:  exponent * mantissa - -The mantissa is the number which the exponent is multiplied to. The mantissa value is 1 + 1/rb, where rb is the reverse binary interpretation - -Let's take the number 3.5 for example. - -The float numbers are sign * exponent * mantissa.  3.5 = 1 * 2 * 1.75 . - -Mantissa: 1 + 11000000000000000000000 -> 1/(2^0) + 1/(2^1) + 1/(2^2) + 0*(2^3) + ... + 0*(2^23) -> 1 + 0.5 + 0.25 -> 1.75 - -Some numbers cannot be represented exactly (such as  0.99999999) - -To learn more about how IEEE Real numbers are formed follow this link. -

Why does this happen?

-Using this IEEE 754 converter we have found out that: - -0.7 is actually represented as 0.699999988079071 - -0.1 is actually represented as 0.10000000149011612 - -If we add these two values we will obtain 0.7999999895691871 - -And if we multiply it by 10 we obtain 7.999999895691871 which if casted to int is 7 the same way 3.5 is 3 if casted to int. - -The other example still shows 7 because 0.6 is actually represented as 0.6000000238418579 and  (0.6000000238418579 + 0.10000000149011612)*10 is 7,00000025331974 - - -  -

But still ...

-If we use echo and var_dump or if applying mathematical operations, PHP automatically adjusts the values, but intval and casting to int work on their bits before the values were adjusted - - -var_dump((0.7+0.1)*10); # float(8) -var_dump(intval( ((0.7+0.1)*10) ) );   # int(7) -var_dump(intval( ((0.7+0.1)*10)+1 ) ); # int(9) - -If these values are very important for your project you can get the correct values we by using the BCMath PHP Extension. - -For a more technical & mathematical approach read this document. - -

Frequently Asked Questions

- -
-
- Why does (int)((0.7+0.1)*10) return 7 instead of 8? + -
-

Because 0.7 is actually represented internally as 0.699999988079071 and 0.1 as 0.10000000149011612. Adding them gives 0.7999999895691871, and multiplying by 10 gives 7.999999895691871, which truncates to 7 when cast to int.

-
-
-
- Does the same rounding issue happen with 0.6 instead of 0.7? + -
-

Yes. 0.6 is represented as 0.6000000238418579, and (0.6000000238418579 + 0.10000000149011612) * 10 equals about 7.00000025331974, which also truncates to int(7).

-
-
-
- Why do var_dump and echo show the expected value while intval/int casting does not? + -
-

When using echo, var_dump, or mathematical operations, PHP automatically adjusts the displayed values. However, intval and casting to int operate on the underlying bits before those values were adjusted, which is why they can produce a different (truncated) result.

-
-
-
- How are floating-point numbers represented at the CPU level? + -
-

The CPU performs calculations in binary. IEEE Short Real (single precision) uses 32 bits: 1 sign bit, 8 exponent bits, and 23 mantissa bits. IEEE Long Real (double precision) uses 64 bits: 1 sign bit, 11 exponent bits, and 52 mantissa bits. A number can only be represented exactly if it can be expressed as exponent * mantissa; numbers like 0.99999999 cannot be represented exactly.

-
-
-
- Is there a way to get accurate results for calculations like this in PHP? + -
-

If precise values matter for your project, the article recommends using the BCMath PHP Extension to get correct results.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/how-to-upgrade-wamp-to-php-5-3-4.html.twig b/src/Blog/templates/page/blog-resource/php-development/how-to-upgrade-wamp-to-php-5-3-4.html.twig deleted file mode 100644 index f7f7d52e..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/how-to-upgrade-wamp-to-php-5-3-4.html.twig +++ /dev/null @@ -1,63 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -
1.    Stop WAMP server.
-
2.    Go to windows.php.net and download the the latests ZIPPED package for php5.3.4.
-
Make sure it is the VC6 Thread Safe build. DO NOT DOWNLOAD THE INSTALLER.
-
3.  Create a folder php5.3.4 into wamp/bin/php
-
4.  Extract the downloaded zip to the newly created php5.3.4 folder
- - -
5.  Copy the files:
-
-  php.ini
-
-  phpForApache.ini
-
-   wampserver.conf
-
from your existing php5.3 (eg.   wamp/bin/php/php5.3.3) folder to the new php5.3.4 folder.
-
6.  Open the files:
-
-   php.ini
-
-  phpForApache.ini
-
and search/replace the string 5.3.3 with 5.3.4
-
7.   Go to wamp/bin/apache/apache/apache2.2.11/bin and delete the file called php.ini
-
8.   Restart WAMP server.
-
9.   Choose php version 5.3.0
-
10.   Restart WAMP server.
-
11.   Now choose php version 5.3.4 .
-
12.   Check if PEAR path is correct in php.ini , and modify accordingly.
-
13.   Restart WAMP server.
-
14.   Enjoy.
- -

Frequently Asked Questions

- -
-
- Where do I download the PHP 5.3.4 package for WAMP? + -
-

Download the latest ZIPPED package for PHP 5.3.4 from windows.php.net. Make sure it is the VC6 Thread Safe build, and do not download the installer.

-
-
-
- Where should the downloaded PHP 5.3.4 files be extracted? + -
-

Create a folder named php5.3.4 inside wamp/bin/php, then extract the downloaded zip into that newly created folder.

-
-
-
- Which configuration files need to be copied and edited when upgrading? + -
-

Copy php.ini, phpForApache.ini, and wampserver.conf from the existing php5.3 folder (e.g. wamp/bin/php/php5.3.3) into the new php5.3.4 folder. Then, in php.ini and phpForApache.ini, search and replace the string 5.3.3 with 5.3.4.

-
-
-
- Is there anything to remove from the Apache folder before restarting WAMP? + -
-

Yes. Go to wamp/bin/apache/apache/apache2.2.11/bin and delete the file called php.ini, then restart the WAMP server.

-
-
-
- How do I actually switch WAMP to the new PHP version after restarting? + -
-

After restarting WAMP, choose PHP version 5.3.0, restart WAMP server again, and then choose PHP version 5.3.4. Also check that the PEAR path is correct in php.ini and modify it accordingly, then restart WAMP server once more.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/mezzio-app-development-in-wsl2.html.twig b/src/Blog/templates/page/blog-resource/php-development/mezzio-app-development-in-wsl2.html.twig deleted file mode 100644 index f0f2e1ce..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/mezzio-app-development-in-wsl2.html.twig +++ /dev/null @@ -1,80 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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.

- -

Step 1:

- -

Make sure you have WSL2 installed on your machine by following this guide.

- -

Step 2:

- -

Install Ubuntu 20.0 LTS inside WLS2 as described here (the current version of this guide covers AlmaLinux 9, the distro the WSL setup has since moved to).

- -

Step 3:

- -

Create a virtualhost for your project using this guide.

- -

Step 4:

- -

Using your terminal, move into the virtualhost directory that you just created:

- -
cd /home/your-username/projects/your-virtualhost
- -

Install Dotkernel API, by following this guide.

- -

Make sure your data and log directories are writable by changing their permissions, as described here.

- -

Step 5:

- -

Setup PHPStorm to work with WSL2 files like in this article

- -

- -

Note:

- -

There is a guide for AlmaLinux 8 as well, but that's not fully functional because of a well-known issue regarding running systemd inside WSL2.

- -

Frequently Asked Questions

- -
-
- What does this guide help you accomplish? + -
-

It walks you through installing a Mezzio application (Dotkernel API) using WSL2 and running it on Ubuntu 20.04 LTS.

-
-
-
- What do you need before installing Ubuntu inside WSL2? + -
-

You first need WSL2 installed on your machine, which you can set up by following the linked WSL2 installation guide, before installing Ubuntu inside it.

-
-
-
- How do you set up a virtual host for the project? + -
-

After Ubuntu is installed inside WSL2, you create a virtual host for your project by following the linked virtual-host creation guide.

-
-
-
- How do you install Dotkernel API once the virtual host is ready? + -
-

Using your terminal, move into the virtual host directory you created (for example, cd /home/your-username/projects/your-virtualhost), then install Dotkernel API by following its guide. Afterward, make sure the data and log directories are writable by changing their permissions, as described in the linked permission-fix guide.

-
-
-
- How do you get PHPStorm to work with the WSL2 project files? + -
-

You set up PHPStorm to work with WSL2 files by following the linked JetBrains article on using a WSL development environment in the product.

-
-
-
- Does this guide also work for AlmaLinux instead of Ubuntu? + -
-

There is a guide for AlmaLinux 8 as well, but it is not fully functional because of a well-known issue regarding running systemd inside WSL2.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.html.twig b/src/Blog/templates/page/blog-resource/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.html.twig deleted file mode 100644 index c74e784c..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/php-5-3-6-released-no-upgrade-possible-for-wampserver.html.twig +++ /dev/null @@ -1,26 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -PHP 5.3.6 was released, but it come with a bad news for WampServer users: -
"Windows users: please mind that we do no longer provide builds created with Visual Studio C++ 6"
-Since WampServer is built using VC++ 6,  is not possible an upgrade , without a rewrite from scratch of entire WampServer package. - -More details on WampServer forum  here - -

Frequently Asked Questions

- -
-
- Why can't WampServer users upgrade to PHP 5.3.6? + -
-

PHP 5.3.6 no longer provides builds created with Visual Studio C++ 6 for Windows users. Since WampServer is built using VC++ 6, upgrading isn't possible without rewriting the entire WampServer package from scratch.

-
-
-
- Where can WampServer users find more details about this issue? + -
-

More details are available on the WampServer forum, linked in this article.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/php-environment-development-staging-production.html.twig b/src/Blog/templates/page/blog-resource/php-development/php-environment-development-staging-production.html.twig deleted file mode 100644 index b50819eb..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/php-environment-development-staging-production.html.twig +++ /dev/null @@ -1,54 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. The most common environments are described below. - - -

Development

-This is where the software is developed – it’s the working environment for individual developers or small teams. The purpose of this environment is for the developer to work on local host, separate from the rest of the team, allowing them to make various changes without worrying that it may alter the work of the other members of the team. -

Staging

-It is used to assemble, test and review the application before it goes into production. Usually the staging environment tries to simulate as much as possible the production environment (hardware and software-wise). Normally, before releasing an update version of the application on the production environment, the update must be tested on the staging environment. This environment can also be used as a demonstration/training environment. -

Production

-It is the “live” environment, where the final application goes out to the world and becomes active. - -To switch from one environment to another use the Subversion source code. - -Using SVN on Aptana is an article that explains how to set your development environment on your local computer and then to change it on your staging environment. - -To better understand the development of an application using environments, check this helpful article http://dltj.org/article/software-development-practice/ - -

Frequently Asked Questions

- -
-
- What is a "server environment" in hosted software development? + -
-

It refers to a server tier designated to a specific stage in a release process. The purpose of using different environments is to improve the development, testing and release processes in client-server applications.

-
-
-
- What is the development environment used for? + -
-

It's the working environment for individual developers or small teams, where the developer works on local host, separate from the rest of the team, allowing changes to be made without worrying about altering the work of other team members.

-
-
-
- What is the staging environment used for? + -
-

It's used to assemble, test and review the application before it goes into production, and it usually tries to simulate the production environment as closely as possible, both hardware- and software-wise. Before releasing an update to production, it must normally be tested on staging first, which can also serve as a demonstration or training environment.

-
-
-
- What is the production environment? + -
-

It's the "live" environment, where the final application goes out to the world and becomes active.

-
-
-
- How do you switch an application from one environment to another? + -
-

The article recommends using Subversion source code to switch from one environment to another, and points to the "Using SVN on Aptana" article for details on setting up a development environment locally and then moving it to a staging environment.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/php-support-back-in-aptana-3-0.html.twig b/src/Blog/templates/page/blog-resource/php-development/php-support-back-in-aptana-3-0.html.twig deleted file mode 100644 index fac5e96e..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/php-support-back-in-aptana-3-0.html.twig +++ /dev/null @@ -1,40 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Aptana 3.0 is in beta stage, can be downloaded from the official site . - -The very super good news is that Aptana PHP Support is back , based on an annoucement from their forum. And even more,  will be integrated in Studio 3 core, not as a separate plugin. -
Aptana PHP is coming back. We are in the process of developing and integrating it into the Studio 3. -The debugger will also be introduced, but it will take a few weeks till you'll be able to get it. - - -At the moment, the idea is to provide the PHP debugger as a separate set of plugins that you will be able to grab from the Studio. The majority of the PHP editing capabilities will be integrated into, and delivered with, the Studio core. - -Cheers, --Shalom G - -p.s. You can follow us on twitter for any major announcements, or follow me 'sgibly' for more frequent progress updates.
- -

Frequently Asked Questions

- -
-
- Is Aptana 3.0 available yet, and where can it be downloaded? + -
-

Aptana 3.0 is in beta stage and can be downloaded from the official Aptana site.

-
-
-
- Will PHP support in Aptana 3.0 be a separate plugin? + -
-

No. According to an announcement on Aptana's forum, PHP support is coming back and is being developed and integrated directly into the Studio 3 core, rather than delivered as a separate plugin.

-
-
-
- What about the PHP debugger? + -
-

The debugger will also be introduced, but it will take a few weeks to become available. The plan is to provide the PHP debugger as a separate set of plugins you can grab from the Studio, while the majority of the PHP editing capabilities will be integrated into and delivered with the Studio core.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.html.twig b/src/Blog/templates/page/blog-resource/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.html.twig deleted file mode 100644 index 9491ff9a..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/protection-against-sql-injection-using-pdo-and-zend-framework-part-2.html.twig +++ /dev/null @@ -1,88 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. -What you should know about their methods is: - - -Note*: even if you use prepared statements using Zend_Db methods, SQL Injection is still possible if WHERE and ORDER BY clause are wrongly written, so pay attention to them. - -For more details see Stefan Esser slides. - -PS. A short tip, you can use cast type to avoid SQL Injection in WHERE clause where is possible. -
$sql= 'SELECT * FROM table WHERE id = ' . (int)$_POST['id'];
-
- -

Frequently Asked Questions

- -
-
- What is the focus of this article? + -
-

Following the earlier article about SQL Injection, this article makes a stronger argument for using Zend Framework to handle database access. Zend_Db is the primary class used for accessing the database, but there is more to it: Zend_Db_Statement, Zend_Db_Select, and Zend_Db_Tables.

-
-
-
- Does Zend_Db's query() method protect against SQL injection? + -
-

query() uses prepared statements internally, but SQL Injection is still possible if the $sql parameter passed to it is dynamically created.

-
-
-
- What about fetchAll() and the other fetch methods? + -
-

All of the fetch methods use prepared statements internally, but SQL Injection is still possible if the $sql is dynamically created.

-
-
-
- Is insert() safe from SQL injection? + -
-

Yes. insert() uses prepared statements internally, so SQL Injection is not possible with it.

-
-
-
- What about update() and delete()? + -
-

update() uses prepared statements internally, but SQL Injection may be possible if the $where clause is created dynamically. Likewise, with delete(), SQL Injection may be possible if $where is created dynamically.

-
-
-
- Even with prepared statements, when can SQL injection still happen, and what tip helps avoid it in WHERE clauses? + -
-

Even when using prepared statements via Zend_Db methods, SQL Injection is still possible if the WHERE and ORDER BY clauses are wrongly written, so these deserve special attention. A short tip mentioned is to use type casting to avoid SQL Injection in a WHERE clause where possible, for example: $sql = 'SELECT * FROM table WHERE id = ' . (int)$_POST;

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.html.twig b/src/Blog/templates/page/blog-resource/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.html.twig deleted file mode 100644 index f5354eb1..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/protection-against-sql-injection-using-pdo-and-zend-framework.html.twig +++ /dev/null @@ -1,63 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -PDO – PHP Data Objects – is a database access layer providing a standardized method of access to multiple databases.  - PDO provides a data-access abstraction layer, meaning that depending on what database you're using, you will have apply the same functions to issue queries and fetch data. PDO does not provide a database abstraction; it doesn't rewrite SQL or emulate missing features. Among  PDO benefits there are: --    Access methods allow complete control over how attributes are read and written --    Validation on a per-record and per-attribute level --    Easier fetching of objects from related table --    Reusable logic - means that the same codebase is much easier to maintain --    Cleaner code by using object oriented code --    Less errors from SQL query generation --    Last but not least : Protection against SQL injection - -In Zend Framework for database access, methods usually support prepared statements. Dynamic SQL queries are allowed, but you must escape all the parameter, otherwise you have SQL injection.  Because of this, prepared statements are encouraged to be used. They can handle escaping parameters for you. -Most people believe that using prepared statements they are 100% protected from SQL injection. But this is by far true. Input data should always be validated and sanitized, and PDO should be seen as another line of defense. PDO is not protecting you from other security vulnerabilities like XSS(cross-site scripting), but helps protect your application against SQL injection. - -It may also occur a problem in Zend Framework when you have SQL injection in your application while you are using PDO_MySQL. PDO_MySQL is a more dangerous application than any other traditional MySQL applications. Traditional MySQL allows only a single SQL query. In PDO_MySQL there is no such limitation, but you risk to be injected with multiple queries. To avoid this you should try to use the correct prepared statements from Zend Framework. You should also pay attention when you have in your SQL query WHERE IN and ORDER BY; they cannot be handled by prepare statements normally. In this case you should escape your data. - -Zend_Db has two escaping methods which can be used: quote() and quoteIdentifier(). Note that these two methods are handling strings by putting them between single quotes. - -

Frequently Asked Questions

- -
-
- What is SQL injection? + -
-

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 directly into a SQL statement.

-
-
-
- What is PDO and what kind of abstraction does it provide? + -
-

PDO (PHP Data Objects) is a database access layer providing a standardized method of access to multiple databases. It provides a data-access abstraction layer, meaning you apply the same functions to issue queries and fetch data regardless of which database you're using. However, PDO does not provide a database abstraction - it doesn't rewrite SQL or emulate missing database features.

-
-
-
- What are the benefits of using PDO? + -
-

Among the benefits are: access methods that allow complete control over how attributes are read and written, validation on a per-record and per-attribute level, easier fetching of objects from related tables, reusable logic that makes the codebase easier to maintain, cleaner code through object-oriented programming, fewer errors from SQL query generation, and protection against SQL injection.

-
-
-
- Does using prepared statements in Zend Framework fully protect against SQL injection? + -
-

Not entirely. Zend Framework's database access methods usually support prepared statements, which are encouraged because they handle escaping parameters for you, but dynamic SQL queries are still allowed and any parameters must be escaped manually or SQL injection becomes possible. Many people believe prepared statements offer 100% protection, but this isn't true - input data should always be validated and sanitized, with PDO treated as another line of defense. PDO also does not protect against other vulnerabilities such as XSS (cross-site scripting).

-
-
-
- Why can PDO_MySQL be riskier than traditional MySQL usage, and how can this be avoided? + -
-

Traditional MySQL allows only a single SQL query at a time, but PDO_MySQL has no such limitation, meaning there is more risk of being injected with multiple queries. To avoid this, you should use the correct prepared statements from Zend Framework, and pay particular attention to WHERE IN and ORDER BY clauses, since they aren't normally handled correctly by prepared statements - in these cases you should escape the data yourself.

-
-
-
- What escaping methods does Zend_Db provide? + -
-

Zend_Db has two escaping methods that can be used: quote() and quoteIdentifier(). Both of these methods handle strings by putting them between single quotes.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/remote-connections-to-mysql-server-on-plesk-based-servers.html.twig b/src/Blog/templates/page/blog-resource/php-development/remote-connections-to-mysql-server-on-plesk-based-servers.html.twig deleted file mode 100644 index 104acf31..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/remote-connections-to-mysql-server-on-plesk-based-servers.html.twig +++ /dev/null @@ -1,63 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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.5+ version, it still use old style of password storage, pre-mysql 4.1+ - -If you try to connect remote, from your local development machine, you will get an ugly error, like: - -  -
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE  mysqlnd
-cannot connect  to MySQL 4.1+ using the old insecure authentication. Please use an administration
-tool to reset your password with the command SET PASSWORD = PASSWORD('your_existing_password').
-This will store a new, and more secure, hash value in mysql.user.
-If this user is used in other scripts executed by PHP 5.2 or earlier you might need to remove
- the old-passwords flag from your my.cnf file' in
-How to fix that [ You need mysql admin priviledges] : -
    -
  1. Create a new database user,  someuser for instance directly in command line
  2. -
  3. Grant priviledges to that user to the database you want to connect to.
  4. -
  5. Now run the below SQL statements:
  6. -
-
SET old_passwords = 0;
-UPDATE mysql.user SET Password = PASSWORD('somepassword') WHERE User = 'someuser' limit 1;
-SELECT LENGTH(Password) FROM mysql.user WHERE User = 'someuser';
-FLUSH PRIVILEGES;
-  - -Now you can remotely connect to that server, using the user: someuser and password: somepassword - -  - -NOTE: if you browse the table mysql.user , you will note that the password field contain many more characters for the user someuser compared to the others. - -

Frequently Asked Questions

- -
-
- Why can't I connect remotely to MySQL on a Plesk-based server? + -
-

By default, MySQL servers on Linux machines with Plesk installed have the old_passwords flag set to 1 (ON). Even on MySQL 5.5+, this means the old, pre-4.1 password storage style is still used, which causes a PDOException error when connecting remotely.

-
-
-
- What error message indicates this old-passwords problem? + -
-

You get a fatal error mentioning "cannot connect to MySQL 4.1+ using the old insecure authentication," suggesting you reset the password using SET PASSWORD = PASSWORD('your_existing_password') to store a newer, more secure hash.

-
-
-
- How do you fix the old-passwords issue? + -
-

Create a new database user (e.g. someuser) from the command line, grant it privileges on the target database, then run: SET old_passwords = 0; UPDATE mysql.user SET Password = PASSWORD('somepassword') WHERE User = 'someuser' limit 1; SELECT LENGTH(Password) FROM mysql.user WHERE User = 'someuser'; FLUSH PRIVILEGES;. You can then connect remotely using that user and password.

-
-
-
- How can you tell the fix worked by looking at the mysql.user table? + -
-

If you browse the mysql.user table, you'll notice the password field for the fixed user (someuser) contains many more characters than the password fields for the other, unfixed users.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/static-analysis-replacing-psalm-with-phpstan.html.twig b/src/Blog/templates/page/blog-resource/php-development/static-analysis-replacing-psalm-with-phpstan.html.twig deleted file mode 100644 index fb2d93fe..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/static-analysis-replacing-psalm-with-phpstan.html.twig +++ /dev/null @@ -1,204 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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:

- - - -

The top static analysis tools are:

- - - -

Why Switch from Psalm to PHPStan?

- -

Dotkernel has been using psalm for a while now and the results have always been positive. A large part of the PHP community, especially developers in widely-used projects like Doctrine and Composer, have opted for PHPStan instead. For most use cases, psalm and PHPStan have identical findings, so it isn't really justified to use both.

- -

PHPStan has some advantages to psalm:

- - - -

Thus, the better choice becomes PHPStan.

- -

Updating Your Project to Use PHPStan

- -

First, remove the references to psalm

- - - -
-{% verbatim %}
-on:
-  - push
-
-name: Run PHPStan checks
-
-jobs:
-  mutation:
-    name: PHPStan ${{ matrix.php }}-${{ matrix.os }}
-
-    runs-on: ${{ matrix.os }}
-
-    strategy:
-      matrix:
-        os:
-          - ubuntu-latest
-
-        php:
-          - "8.2"
-          - "8.3"
-
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v4
-
-      - name: Install PHP
-        uses: shivammathur/setup-php@v2
-        with:
-          php-version: '${{ matrix.php }}'
-          coverage: pcov
-          ini-values: assert.exception=1, zend.assertions=1, error_reporting=-1, log_errors_max_len=0, display_errors=On
-          tools: composer:v2, cs2pr
-
-      - name: Determine composer cache directory
-        run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
-
-      - name: Cache dependencies installed with composer
-        uses: actions/cache@v4
-        with:
-          path: ${{ env.COMPOSER_CACHE_DIR }}
-          key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
-          restore-keys: |
-            php${{ matrix.php }}-composer-
-      - name: Install dependencies with composer
-        run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi
-
-      - name: Setup project
-        run: |
-          mv config/autoload/local.php.dist config/autoload/local.php
-          mv config/autoload/mail.local.php.dist config/autoload/mail.local.php
-          mv config/autoload/local.test.php.dist config/autoload/local.test.php
-      - name: Run static analysis with PHPStan
-        run:  vendor/bin/phpstan analyse
-{% endverbatim %}
-
- - - -
includes:
-    - vendor/phpstan/phpstan-doctrine/extension.neon
-    - vendor/phpstan/phpstan-phpunit/extension.neon
-parameters:
-    level: 5
-    paths:
-        - src
-        - test
-    treatPhpDocTypesAsCertain: false
-    ignoreErrors:
-        -
-            message: '#Call to an undefined method.*setAllowOverride#'
-            path: test/Functional/AbstractFunctionalTest.php
- -

Running the PHPStan checks

- -

To run the checks, use this command:

- -
composer static-analysis
- -

composer.json is currently set up to run this command which sets up the memory limit to a higher amount that that from the php.ini file in PHP (128M).

- -
vendor/bin/phpstan analyse --memory-limit 1G 
- -

If you still get the error below, try increasing the memory limit further, e.g. 2G or 4G.

- -
Child process error: PHPStan process crashed because it reached configured PHP memory limit: 128M
- -

Summary

- -

In this article we revisited the theoretical meaning of static analysis and focused on the change from psalm to PHPStan. It's highly recommended to use a static analysis tool in your project and, while both psalm and PHPStan perform similar functions, the latter has recently stepped ahead of the former.

- -

Frequently Asked Questions

- -
-
- What is static analysis and why is it used? + -
-

Static analysis (or source code analysis) applies coding rules to debug source code before a program is run. It's used to catch and fix type-related errors, confirm coding standards, identify code that needs refactoring, and enhance security by detecting issues like code injection, XSS, and open redirect vulnerabilities.

-
-
-
- Why did Dotkernel switch from Psalm to PHPStan? + -
-

Dotkernel had used Psalm with positive results, but much of the PHP community, including projects like Doctrine and Composer, moved to PHPStan instead. Since the two tools have largely identical findings, using both wasn't justified, and PHPStan has a faster growing ecosystem, better social media presence, a fulltime contributor (@ondrejmirtes), PHP stubs from PHPStorm, and better quality and depth of detection.

-
-
-
- What are the main steps to migrate a project from Psalm to PHPStan? + -
-

Remove vimeo/psalm from composer.json and add phpstan/phpstan, phpstan/phpstan-doctrine and phpstan/phpstan-phpunit, replace the "static-analysis" composer script with "phpstan analyse --memory-limit 1G", run composer update, delete psalm-baseline.xml and psalm.xml, then add a .github/workflows/static-analysis.yml workflow and a phpstan.neon configuration file.

-
-
-
- What does the phpstan.neon configuration file do in this setup? + -
-

It includes the phpstan-doctrine and phpstan-phpunit extensions, sets the analysis rule level (level 5 in the example), specifies the paths to scan (src and test), sets treatPhpDocTypesAsCertain to false, and can list ignoreErrors patterns to suppress specific known findings.

-
-
-
- How do you run the PHPStan checks, and what if you hit a memory error? + -
-

Run composer static-analysis, which is configured to call vendor/bin/phpstan analyse --memory-limit 1G (higher than PHP's default 128M). If you still see "PHPStan process crashed because it reached configured PHP memory limit," try increasing the memory limit further, e.g. to 2G or 4G.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/using-aptana-to-connect-to-dotkernel-tracker-mantis.html.twig b/src/Blog/templates/page/blog-resource/php-development/using-aptana-to-connect-to-dotkernel-tracker-mantis.html.twig deleted file mode 100644 index 27046de8..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/using-aptana-to-connect-to-dotkernel-tracker-mantis.html.twig +++ /dev/null @@ -1,75 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -Dotkernel Tracker is a Mantis web-based bugtracking system. To simplify the development of Dotkernel, we integrated our web development IDE – Aptana with our bugtracking system – Dotkernel Tracker. In the Downloads section, you will find screenshots with the below steps for Using Aptana with the Mylyn connector for Mantis - - -1. Install Aptana 2.0.5 by following these steps: http://www.dotkernel.com/php-development/aptana-php-installation-in-aptana-2-x/ - - - -2. Install the Mylyn plugin: - -Aptana -> Help ->Software Update - -Add: http://download.eclipse.org/tools/mylyn/update/e3.4/ - -3. Aptana- >Windows -> Show View -> Other -> Task Repository - -4. Right click: Add Task Repository -> Install More Connectors - -5. Select Mantis , then Finish - -6. Install Mylyn Connector Mantis - -7. From the Task Repository window, right click to select Add Task Repository-> Mantis->Next - -Server : http://www.dotkernel.net/api/soap/mantisconnect.php - -Label: Dotkernel Tracker (or a custom string ) - -User ID: your username from http://www.dotkernel.net/. If you don’t have an account, signup here - -8. To check if your connection works, click on Validate Settings, then Finish - -9. A window will popup asking if you want to create a query;  click Yes - -10. Select project: Dotkernel - -Select filter: [Build-in] Latest Dot Kernel tasks - -Query Title: change title – optional - -Finish - -

Frequently Asked Questions

- -
-
- What is Dotkernel Tracker? + -
-

Dotkernel Tracker is a Mantis-based, web-based bugtracking system used by the Dotkernel application framework, where bugs are reported, new features are announced, and other general tickets are added.

-
-
-
- How is Aptana integrated with Dotkernel Tracker? + -
-

Aptana, the web development IDE used for Dotkernel development, is integrated with Dotkernel Tracker via the Mylyn plugin's Mantis connector, so tickets can be managed directly from within the IDE.

-
-
-
- What plugin do you need to install in Aptana before connecting to Mantis? + -
-

You need to install the Mylyn plugin via Aptana -> Help -> Software Update, adding the update site http://download.eclipse.org/tools/mylyn/update/e3.4/, and then install the Mylyn Connector for Mantis.

-
-
-
- What information is needed to add Dotkernel Tracker as a task repository? + -
-

You need the server URL (http://www.dotkernel.net/api/soap/mantisconnect.php), a label of your choosing (e.g. "Dotkernel Tracker"), and your User ID, which is your username from dotkernel.net (you can sign up if you don't already have an account).

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/using-php-7-express-in-zend-studio-13.html.twig b/src/Blog/templates/page/blog-resource/php-development/using-php-7-express-in-zend-studio-13.html.twig deleted file mode 100644 index 4195c8b3..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/using-php-7-express-in-zend-studio-13.html.twig +++ /dev/null @@ -1,83 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - - -

What is PHP 7 Express?

-PHP 7 Express is the newest feature added in Zend Studio (from version 13). This feature is very useful for checking if your pre-PHP7 code will work on a PHP7 server. -

The project creation

-If you already have the project backup and you are sure you can modify your project you can skip this step. - -If you want to create a new project for testing instead of modifying the original project make sure the PHP Version is set to PHP 5.6 or the PHP Version you use in the project. Choosing the PHP 7 option will hide the PHP 7 Express feature because Zend Studio assumes a PHP 7 project doesn't need compatibility checks, but if the project is using a different version of PHP compatibility issues might apper. - -Long story short: Use the exact PHP version your project is using at the project creation. - -The New Local PHP Project dialog in Zend Studio 13 The "New Local PHP Project" dialog in Zend Studio 13 and the PHP version selection -

The 'PHP Interpreter' selection

-If the PHP 7 Express feature is won't show up the issue might be the PHP Interpreter selected. - -To check and modify the PHP Interpreter: - -Zend Studio PHP Interpreter Dialog Zend Studio PHP Interpreter Dialog - -The PHP 7 Express feature should now show up. -

Project preparation

-As we created a local project named zend-framework-test we will add the latest Zend Framework 1 version in our project. - -

Using PHP 7 Express - Testing Zend Framework 1

-Now let's see PHP 7 Express in action. Using PHP 7 Express is very easy: - -After the analysis was done, you should see the PHP 7 Express view (or tab) in the bottom pane in Zend Studio (this depends on the perspective). - -The result should look like this: - -Zend Framework 1 compatibility issues with PHP7 Zend Framework 1 compatibility issues with PHP7 - -

Frequently Asked Questions

- -
-
- What is PHP 7 Express in Zend Studio? + -
-

PHP 7 Express is a feature added in Zend Studio starting with version 13. It's useful for checking whether pre-PHP7 code will work correctly on a PHP7 server.

-
-
-
- What PHP version should you select when creating a test project? + -
-

Use the exact PHP version your project actually uses (e.g. PHP 5.6), not PHP 7. Choosing PHP 7 at project creation hides the PHP 7 Express feature, because Zend Studio assumes a PHP 7 project doesn't need compatibility checks.

-
-
-
- What should you check if the PHP 7 Express feature doesn't show up? + -
-

Check the PHP Interpreter selected for the project: right click the project, type "interpreter" to find the PHP -> Interpreter option, and optionally enable "Enable Project specific settings" so the change only affects that project.

-
-
-
- How do you run PHP 7 Express once it's available? + -
-

Right-click the project itself (not a folder or file, or the feature won't show up), click "Run PHP 7 Express...", and confirm the prompt. After the analysis finishes, results appear in the PHP 7 Express view/tab in the bottom pane.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/version-control-ignore-patterns-in-zend-studio.html.twig b/src/Blog/templates/page/blog-resource/php-development/version-control-ignore-patterns-in-zend-studio.html.twig deleted file mode 100644 index 02229014..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/version-control-ignore-patterns-in-zend-studio.html.twig +++ /dev/null @@ -1,36 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. Team -> Ignored Resources  - -3. using Add Pattern, you can add each ignored pattern , one by one . Or you can remove some of allready existing patterns. - -This is specially useful when you have both Git and SVN projects in your workspace , also when you tend to be less careful about your code and workspace :-) - -Also, on each project, you can use either global ignored  patterns, or use specific ones. - -  - -ignore-patterns - -

Frequently Asked Questions

- -
-
- How do you set global ignored resource patterns in Zend Studio? + -
-

Go to Window -> Preferences, then Team -> Ignored Resources. From there, use Add Pattern to add ignored patterns one by one, applying them globally to all projects instead of adding them manually to each project, or remove existing patterns.

-
-
-
- When is this global ignore configuration especially useful? + -
-

It's especially useful when you have both Git and SVN projects in the same workspace, and also when you tend to be less careful about your code and workspace. On each project you can still choose to use either the global ignored patterns or specific ones.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/welcome-to-the-10th-zend-certified-engineer-in-dotboost-team.html.twig b/src/Blog/templates/page/blog-resource/php-development/welcome-to-the-10th-zend-certified-engineer-in-dotboost-team.html.twig deleted file mode 100644 index ad28fa3d..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/welcome-to-the-10th-zend-certified-engineer-in-dotboost-team.html.twig +++ /dev/null @@ -1,29 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Today is a major milestone for our Dotboost Technologies Inc. Company. We are announcing the 10th member of the team passed the Zend Certified Engineer exam. - -Zend Yellow Pages - -This is part of our commitment to ensure that our clients have access to top level PHP development as standard practice and allows us to follow a strong quality assurance program. -The next step is to implement the Zend Framework 2 best practices and to take the upcoming Zend Framework 2 Certified Architect exam. - -Beginning 2014, a Zend Certification will became mandatory for every developer in our team. - -

Frequently Asked Questions

- -
-
- What milestone is this article announcing? + -
-

Dotboost Technologies Inc. is announcing that the 10th member of its team has passed the Zend Certified Engineer (ZCE) exam, a milestone the company highlights as part of its commitment to top-level PHP development for clients.

-
-
-
- What are the team's next certification-related plans? + -
-

The next step is to implement Zend Framework 2 best practices and to pursue the Zend Framework 2 Certified Architect exam. Additionally, starting in 2014, Zend Certification will become mandatory for every developer on the team.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-development/zend-server-5-5-quick-setup-on-windows.html.twig b/src/Blog/templates/page/blog-resource/php-development/zend-server-5-5-quick-setup-on-windows.html.twig deleted file mode 100644 index 2234d142..00000000 --- a/src/Blog/templates/page/blog-resource/php-development/zend-server-5-5-quick-setup-on-windows.html.twig +++ /dev/null @@ -1,58 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -In order to make  usable a fresh installation of Zend Server 5.5.0 on Windows 7, and be ready for development , few quick steps are required: - - - -1. Enable mod_rewrite - --  Open the file ZEND_INSTALATION_PATH/Apache2/conf/httpd.conf. Change the directive AllowOverride fron None to All - -- Restart Apache service - -2. PHP fine tunes - -- go to Zend Server administration interface, http://localhost:10081/ZendServer/, Server Setup-> Directives, and change the following: - -- set timezone:  set date to America/New_York - --set error reporting: display_errors  set to on, and error_reporting to -1 . - -3. Enable APC - -- APC is listed as been enabled in Zend Server admin interface, but the app dll file is missing, and APC is not working - -- download the latest php apc dll file, from here . Quite old file at the time of this post, php_apc-3.1.5-5.3-nts-vc9-x86.zip, which is the latest Non-thread-safe VC9 file available. Download the archive and extract the file php_apc.dll to ZEND_INSTALATION_PATH/ZendServer/lib/phpext/ - -- open php.ini file, from ZEND_INSTALATION_PATH/ZendServer\etc\php.ini , add the line extension=php_apc.dll - -- Restart Apache service - -  - -  - -

Frequently Asked Questions

- -
-
- How do you enable mod_rewrite after a fresh Zend Server 5.5 install on Windows? + -
-

Open ZEND_INSTALATION_PATH/Apache2/conf/httpd.conf, change the AllowOverride directive from None to All, then restart the Apache service.

-
-
-
- What PHP directive changes are recommended in the Zend Server admin interface? + -
-

In the Zend Server administration interface (http://localhost:10081/ZendServer/), under Server Setup -> Directives, set the date timezone to America/New_York, set display_errors to on, and set error_reporting to -1.

-
-
-
- How do you enable APC when it's listed as enabled but not actually working? + -
-

Download the php_apc dll file (e.g. php_apc-3.1.5-5.3-nts-vc9-x86.zip, the latest non-thread-safe VC9 build at the time), extract php_apc.dll into ZEND_INSTALATION_PATH/ZendServer/lib/phpext/, add the line extension=php_apc.dll to the php.ini file in ZEND_INSTALATION_PATH/ZendServer/etc/php.ini, and restart the Apache service.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.html.twig b/src/Blog/templates/page/blog-resource/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.html.twig deleted file mode 100644 index 98c09e17..00000000 --- a/src/Blog/templates/page/blog-resource/php-troubleshooting/fix-installing-pear-packages-with-php-7-2.html.twig +++ /dev/null @@ -1,71 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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 ...\php\pear\Archive\Tar.php on line 639
-Fatal error: Cannot use result of built-in function in write context in ...\pear\Archive\Tar.php on line 639
-This error is shown because the function is called by reference. - -More details about this issue can be found in this Pull Request. - -  -

The solution

-You might be tempted to execute the following -
pear install Archive_Tar
-which will result in the same error. - -  - -Go to the line indicated in the error (639 in this case) and replace: -
 $v_att_list = & func_get_args();
-with -
 $v_att_list = func_get_args();
-The above means the func_get_args() isn't called by reference anymore. -

Our recommendation

-The above does fix the problem, but we recommend installing the Archive_Tar again so you have the latest working version. - -  - -Run the following command: -
pear install Archive_Tar
-This will update your Archive Tar PEAR package. - -  - -And to install the code sniffer run: -
pear install PHP_CodeSniffer
-  - -

Frequently Asked Questions

- -
-
- What error occurs when installing PEAR packages with PHP 7.2? + -
-

Running a command like pear install PHP_CodeSniffer produces the fatal error "Cannot use result of built-in function in write context" in Archive/Tar.php on line 639, because the function is called by reference.

-
-
-
- How do you fix the "Cannot use result of built-in function in write context" error? + -
-

Go to the line indicated in the error (line 639) and replace "$v_att_list = &func_get_args();" with "$v_att_list = func_get_args();", so func_get_args() is no longer called by reference.

-
-
-
- Does running pear install Archive_Tar directly fix the problem? + -
-

No, running pear install Archive_Tar on its own results in the same error, since the underlying Archive_Tar.php file still has the by-reference function call.

-
-
-
- What is the recommended full fix sequence? + -
-

After manually fixing the func_get_args() line, it's recommended to run pear install Archive_Tar again to get the latest working version of the package, and then run pear install PHP_CodeSniffer to install the code sniffer.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.html.twig b/src/Blog/templates/page/blog-resource/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.html.twig deleted file mode 100644 index 1e50d1c3..00000000 --- a/src/Blog/templates/page/blog-resource/php-troubleshooting/where-is-the-intl-php-extension-problem-solved.html.twig +++ /dev/null @@ -1,137 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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. - -What is Internationalization? - -Got any of these error messages? - -  - -This happened because the PHP Intl extension isn't installed or enabled. - -Parts of this tutorial can be also a guide for installing or enabling other extensions. -

What is PHP Intl?

-
Internationalization extension (further is referred as Intl) is a wrapper for » ICU library, enabling PHP programmers to perform various locale-aware operations including but not limited to formatting, transliteration, encoding conversion, calendar operations
-Source: PHP Documentation -
This extension may be installed using the bundled version as of PHP 5.3.0, or as a PECL extension as of PHP 5.2.0. In other words, there are two methods to install the intl extension.
-Source: PHP Documentation -

Cause

-If you have installed the unbundled PHP version, the extension is not installed on the system. (unless you've installed it separately) - -If you have the bundled PHP version, the extension might be existing but not enabled. - -  -

Solutions

-

For Linux-based Server (assuming you have root access):

- - -For projects hosted on a shared hosting platform you must ask your hosting provider to install/enable the PHP Intl extension. - -  -

For Windows-based Server:

- - - -Edit: changed php7.0 occurrences with php7.x as the version may vary. - -

Frequently Asked Questions

- -
-
- What errors indicate the PHP Intl extension is missing? + -
-

Typical errors include "Zend InputFilter requires intl PHP extension" and "The requested PHP extension intl is missing from your system." These happen because the PHP Intl extension isn't installed or enabled.

-
-
-
- What is the PHP Intl extension used for? + -
-

Intl (Internationalization extension) is a wrapper for the ICU library that lets PHP programmers perform locale-aware operations, including formatting, transliteration, encoding conversion, and calendar operations. It can be installed bundled since PHP 5.3.0, or as a separate PECL extension since PHP 5.2.0.

-
-
-
- How do you find your PHP extensions directory? + -
-

You can find the extension_dir either by calling phpinfo() or by running the command php -r "echo ini_get('extension_dir');" — both read the value straight from the PHP runtime configuration.

-
-
-
- How do you enable the Intl extension on a Linux server if the file already exists? + -
-

Confirm php_intl.so exists in your extensions directory, open the php.ini config file (usually /etc/php.ini), make sure the line "extension=php_intl.so" exists and isn't commented out, restart the web server (e.g. sudo service httpd restart), and verify with phpinfo().

-
-
-
- What if the php_intl.so or php_intl.dll file doesn't exist at all? + -
-

On Linux, check your PHP version with php -v, then install the php-intl package (PHP 5) or php7.x-intl package (PHP 7) via your package manager, e.g. apt-get install php-intl on Ubuntu-based systems or yum install php-intl on CentOS; on shared hosting, ask your provider to install/enable it. On Windows, download the matching PHP version (checking TS/NTS and x86/x64, with thread safety found via php -i | findstr "Thread") from the PHP Downloads page and copy php_intl.dll from its ext folder into your php\ext folder.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.html.twig b/src/Blog/templates/page/blog-resource/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.html.twig deleted file mode 100644 index f6b32979..00000000 --- a/src/Blog/templates/page/blog-resource/phpstorm/code-quality-how-to-setup-phpcs-in-phpstorm.html.twig +++ /dev/null @@ -1,111 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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).

- -
- - - -

Whether you just cloned or you are already working on a project, follow the below guide on how to prepare your environment.

- -

When cloning a project

- -

Step 1:

- -

Windows Terminal: Move to the directory where you want to clone the project

- -

Step 2:

- -

Windows Terminal: Clone the project

- -

Step 3:

- -

PHPStorm: install composer dependencies

- -

Step 4:

- -

PHPStorm: restart

- -

Step 5:

- -

PHPStorm: Open Settings (FIle -> Settings) and go to PHP -> Quality Tools -> PHP_CodeSniffer:

- - - -

For an existing project

- -

Step 1:

- -

PHPStorm: Open Settings (FIle -> Settings) and go to PHP -> Quality Tools -> PHP_CodeSniffer. There, if Coding standard is set to Custom and the field next to it contains the path to your project's phpcs.xml file, then PHPStorm is configured correctly to use phpcs - NO need to continue with the next steps.

- -

Step 2:

- -

PHPStorm: delete (if exists) the vendor directory

- -

Step 3:

- -

PHPStorm: install composer dependencies

- -

Step 4:

- -

PHPStorm: restart

- -

Step 5:

- -

Go to Step 1.

- -

After you have the above configurations, you should start seeing information in the top-right corner of the editor.

- -
- -

Once the PHPStorm has finished analyzing the opened file, you should see either a green tick (meaning no errors)

- -
- -

or a count of all the errors, warnings, typos:

- -
- -

Clicking on them will open a section where you get detailed information on each item, their location and recommendations on how to fix them:

- -
- -

Frequently Asked Questions

- -
-
- What is phpcs and why do you need to configure it in PHPStorm? + -
-

PHP_CodeSniffer (phpcs) is a tool that helps developers maintain a specific standard in the way they write code. In order for it to provide relevant information, it needs to be configured correctly in PHPStorm.

-
-
-
- What are the steps to set up phpcs in PHPStorm when cloning a new project? + -
-

Move to the directory where you want to clone the project, clone the project, install composer dependencies in PHPStorm, restart PHPStorm, then open Settings (File -> Settings) and go to PHP -> Quality Tools -> PHP_CodeSniffer, making sure the inspection button is ON and the Coding standard is set to Custom with the field pointing to your project's phpcs.xml file.

-
-
-
- How do you check whether phpcs is already configured correctly for an existing project? + -
-

Open Settings (File -> Settings) and go to PHP -> Quality Tools -> PHP_CodeSniffer. If Coding standard is already set to Custom and the field points to your project's phpcs.xml file, PHPStorm is configured correctly and there's no need to continue with the next steps.

-
-
-
- What should you do if phpcs is not configured correctly on an existing project? + -
-

Delete the vendor directory if it exists, install composer dependencies, restart PHPStorm, and then go back and repeat the check from Step 1 of the existing-project process.

-
-
-
- How do you know phpcs is working once configured? + -
-

After the above configuration, you should start seeing information in the top-right corner of the editor: a green tick means no errors, while a count indicates errors, warnings, or typos. Clicking on them opens a section with detailed information on each item, its location, and recommendations on how to fix it.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/zend-framework/scienta-zf-debug-bar-a-very-helpfull-zf-debug-tool.html.twig b/src/Blog/templates/page/blog-resource/zend-framework/scienta-zf-debug-bar-a-very-helpfull-zf-debug-tool.html.twig deleted file mode 100644 index 296d0642..00000000 --- a/src/Blog/templates/page/blog-resource/zend-framework/scienta-zf-debug-bar-a-very-helpfull-zf-debug-tool.html.twig +++ /dev/null @@ -1,26 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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. - -

Frequently Asked Questions

- -
-
- What is the Scienta ZF Debug Bar? + -
-

It's a debug tool for Zend Framework that the author found interesting and helpful, available at jokke.dk/software/scientadebugbar.

-
-
-
- Why did Dotkernel switch from its own debug bar to Scienta? + -
-

Dotkernel had been using a very basic debug bar showing queries, time spent, and memory used, but found Scienta to be way more complex and nicer than their internal code, so they switched to it and integrated it into the Dotkernel code base.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/zend-framework/sunsetting-pear-channel-for-zend-framework-1.html.twig b/src/Blog/templates/page/blog-resource/zend-framework/sunsetting-pear-channel-for-zend-framework-1.html.twig deleted file mode 100644 index 5214a3e4..00000000 --- a/src/Blog/templates/page/blog-resource/zend-framework/sunsetting-pear-channel-for-zend-framework-1.html.twig +++ /dev/null @@ -1,44 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

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 .

- -

Another reason for sunsetting is that currently is running on an LXC container with Centos 7 , which does not work on latest Proxmox version, and the upgrade to Amlaminux is too much of a pain.

- -

- -

Thank you PEAR for your contribution to PHP ecosystem, it was a major part of PHP infrastructure .

- -

- -

- -

Frequently Asked Questions

- -
-
- When was the PEAR channel for Zend Framework 1 created? + -
-

The unofficial PEAR channel for Zend Framework 1 (pear.dotkernel.com) was created in 2016, at a time when PEAR was still used a lot.

-
-
-
- Why is the PEAR channel for Zend Framework 1 being sunset? + -
-

Because it's a pain to upgrade PEAR to work with PHP 8, the channel had to be sunset.

-
-
-
- What other reason is given for sunsetting the channel? + -
-

The channel was running on an LXC container with CentOS 7, which doesn't work on the latest Proxmox version, and upgrading to AlmaLinux was too much of a pain.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/zend-framework/wurfl-php-api-libraries-gpl-versions.html.twig b/src/Blog/templates/page/blog-resource/zend-framework/wurfl-php-api-libraries-gpl-versions.html.twig deleted file mode 100644 index 15772968..00000000 --- a/src/Blog/templates/page/blog-resource/zend-framework/wurfl-php-api-libraries-gpl-versions.html.twig +++ /dev/null @@ -1,30 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -GPL versions of WURFL PHP API libraries are ready to be downloaded from here . - -Version 1.1 is the one integrated in Zend Framework Zend_Htpp_UserAgent component. - -Version 1.2.1 is the latest released under GPL license. - -Later Edit: removed because A) AGPL version is available, but you'll need to download it for yourself and B) removed to make Luca Passani a favor :-). - -  - -

Frequently Asked Questions

- -
-
- Which WURFL PHP API library versions were made available under GPL? + -
-

Version 1.1, which is the one integrated in Zend Framework's Zend_Http_UserAgent component, and version 1.2.1, described as the latest released under the GPL license.

-
-
-
- Why was the download later removed, according to the post's edit? + -
-

Per the "Later Edit," it was removed because an AGPL version is available (which readers need to download for themselves), and to make Luca Passani a favor.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-1-12-4-released-with-security-fixes.html.twig b/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-1-12-4-released-with-security-fixes.html.twig deleted file mode 100644 index bdb7e67d..00000000 --- a/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-1-12-4-released-with-security-fixes.html.twig +++ /dev/null @@ -1,43 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -Matthew Weier O'Phinney just announced the release of ZF 1.12.4, 2.1.6, and 2.2.6 m with Security Updates - -For more information, please read the official release announcement: -Zend Framework Released - -Also, the ZF  PEAR channel was updated to latest 1.12.4 release. - -pear upgrade zend/zend - -March 7, 2014 EDIT: - -Zend Framework 1.12.5 was released, in order to fix a backward compatibility issue introduced in 1.12.4 release. - -Release Announcement - -  - -

Frequently Asked Questions

- -
-
- Who announced the release of Zend Framework 1.12.4, and what other versions were released alongside it? + -
-

Matthew Weier O'Phinney announced the release of ZF 1.12.4, along with 2.1.6 and 2.2.6, all with security updates.

-
-
-
- Was the ZF PEAR channel updated for this release? + -
-

Yes, the ZF PEAR channel was updated to the latest 1.12.4 release, upgradable via "pear upgrade zend/zend".

-
-
-
- Was there a follow-up issue after the 1.12.4 release? + -
-

Yes — per the March 7, 2014 edit, Zend Framework 1.12.5 was released in order to fix a backward compatibility issue introduced in the 1.12.4 release.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-1-7-0-released.html.twig b/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-1-7-0-released.html.twig deleted file mode 100644 index 77e7cf8f..00000000 --- a/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-1-7-0-released.html.twig +++ /dev/null @@ -1,26 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -At first glance , the biggest news is AMF support: - -Adobe's Action Message Format protocol to your PHP 5 application - -Download latest  ZF - -

Frequently Asked Questions

- -
-
- What is described as the biggest news in the Zend Framework 1.7.0 release? + -
-

AMF support — bringing Adobe's Action Message Format protocol to PHP 5 applications.

-
-
-
- How can readers get this release? + -
-

Via the "Download latest ZF" link provided in the post, pointing to the official Zend Framework download page.

-
-
-
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-1-end-of-life.html.twig b/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-1-end-of-life.html.twig deleted file mode 100644 index b7581341..00000000 --- a/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-1-end-of-life.html.twig +++ /dev/null @@ -1,8 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

EOL (End-of-Life) term was just announced.

-

Only up until Sept. 28, 2016, Zend Framework 1 will receive security updates.

-

"With the release of Zend Framework 3, it's time to halt development on Zend Framework 1. As such, we hereby announce that Zend Framework 1 reaches its End of Life (EOL) three months from today, on 28 September 2016."

-

Read the official announcement.

-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-as-pear-accessible-repository-on-plesk-server.html.twig b/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-as-pear-accessible-repository-on-plesk-server.html.twig deleted file mode 100644 index e37e5166..00000000 --- a/src/Blog/templates/page/blog-resource/zend-framework/zend-framework-as-pear-accessible-repository-on-plesk-server.html.twig +++ /dev/null @@ -1,20 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -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.  Of course, backward compatibility  can be an issue in future ZF releases ( like 2.0 branch for PHP > 5.3) - -
    -
  1. Install PEAR if is not installed  already .
  2. -
  3. Follow the instructions for ZF PEAR . Then simply use: pear install zend/zend
  4. -
  5. Create a vhosts.conf file in /var/www/vhosts/dotkernel.com/conf  or where is your vhost configuration folder located.
  6. -
  7. In that file, remove the open_basedir :
  8. -
    php_admin_value open_basedir "/var/www/vhosts/dotkernel.com/httpdocs:/tmp:/usr/share/pear"
    -
  9. rebuild all vhosts:  /usr/local/psa/admin/sbin/websrvmng -a
  10. -
  11. Restart httpd
  12. -
  13. Call directly the preloader: -
     require_once 'Zend/Loader/Autoloader.php';
    -
  14. -
-{% endblock %} diff --git a/src/Blog/templates/page/blog-resource/zend-framework/zend-mail-and-zend-http-security-fixes-in-zend-framework-1-12-12.html.twig b/src/Blog/templates/page/blog-resource/zend-framework/zend-mail-and-zend-http-security-fixes-in-zend-framework-1-12-12.html.twig deleted file mode 100644 index 1e3f50f8..00000000 --- a/src/Blog/templates/page/blog-resource/zend-framework/zend-mail-and-zend-http-security-fixes-in-zend-framework-1-12-12.html.twig +++ /dev/null @@ -1,13 +0,0 @@ -{% extends '@layout/blog-post.html.twig' %} - -{% block body %} -

The release of ZF 1.12.12 was just announced, with Security Updates especially on Zend_Mail and Zend_Http components.

-

For more information, please read the official release announcement: Zend Framework 1.12.12 Released

-

Also, the ZF PEAR channel was updated to latest 1.12.12 release.

-

pear upgrade zend/zend

-

We strongly recommend that consumers of the Zend_Http and Zend_Mail components upgrade immediately. Also, Dotkernel Application Framework use intensively the Zend_Mail component.

-

If you cannot, you can download the patch separately and apply it to your ZF install: ZF2015-04 patch for ZF1

-

May 20, 2015 EDIT:

-

Zend Framework 1.12.13 was released, in order to fix a regression issue introduced in 1.12.12 release.

-

Release Announcement

-{% endblock %} diff --git a/src/Blog/templates/page/markdown-article.html.twig b/src/Blog/templates/page/markdown-article.html.twig new file mode 100644 index 00000000..53ce183f --- /dev/null +++ b/src/Blog/templates/page/markdown-article.html.twig @@ -0,0 +1,20 @@ +{% extends '@layout/blog-post.html.twig' %} + +{% block body %} +{{ content|markdown_to_html }} + +{% if faq is not empty %} +

Frequently Asked Questions

+ +
+ {% for item in faq %} +
+ {{ item.question }} + +
+

{{ item.answer }}

+
+
+ {% endfor %} +
+{% endif %} +{% endblock %} diff --git a/test/Unit/App/Factory/MarkdownExtensionFactoryTest.php b/test/Unit/App/Factory/MarkdownExtensionFactoryTest.php new file mode 100644 index 00000000..7100084b --- /dev/null +++ b/test/Unit/App/Factory/MarkdownExtensionFactoryTest.php @@ -0,0 +1,31 @@ +createStub(ContainerInterface::class)); + + $filterNames = array_map( + static fn (TwigFilter $filter): string => $filter->getName(), + $extension->getFilters() + ); + + $this->assertContains('markdown_to_html', $filterNames); + } +} diff --git a/test/Unit/App/Factory/MarkdownRuntimeLoaderFactoryTest.php b/test/Unit/App/Factory/MarkdownRuntimeLoaderFactoryTest.php new file mode 100644 index 00000000..33190363 --- /dev/null +++ b/test/Unit/App/Factory/MarkdownRuntimeLoaderFactoryTest.php @@ -0,0 +1,25 @@ +createStub(ContainerInterface::class)); + + $this->assertInstanceOf(MarkdownRuntime::class, $loader->load(MarkdownRuntime::class)); + $this->assertNull($loader->load('SomeUnknownRuntime')); + } +} diff --git a/test/Unit/App/Service/ArticleBodyCleanerTest.php b/test/Unit/App/Service/ArticleBodyCleanerTest.php new file mode 100644 index 00000000..7abc7b3d --- /dev/null +++ b/test/Unit/App/Service/ArticleBodyCleanerTest.php @@ -0,0 +1,25 @@ +assertSame("## First Section\n\nThe real content.", ArticleBodyCleaner::clean($body)); + } + + public function testCleanOnlyStripsTheTitleWhenThereIsNoTlDrSection(): void + { + $body = "# A Title\n\n## First Section\n\nThe real content."; + + $this->assertSame("## First Section\n\nThe real content.", ArticleBodyCleaner::clean($body)); + } +} diff --git a/test/Unit/App/Service/FaqExtractorTest.php b/test/Unit/App/Service/FaqExtractorTest.php new file mode 100644 index 00000000..fbe57ebb --- /dev/null +++ b/test/Unit/App/Service/FaqExtractorTest.php @@ -0,0 +1,39 @@ +assertSame('Some intro text.', $result['body']); + $this->assertSame( + [ + ['question' => 'What is it?', 'answer' => "It's an example."], + ['question' => 'Why does it matter?', 'answer' => 'Because tests need coverage.'], + ], + $result['faq'] + ); + } + + public function testExtractReturnsAnEmptyFaqWhenThereIsNoFaqSection(): void + { + $body = "Some intro text.\n\nMore content."; + + $result = FaqExtractor::extract($body); + + $this->assertSame([], $result['faq']); + $this->assertSame($body, $result['body']); + } +} diff --git a/test/Unit/App/Service/FrontMatterTest.php b/test/Unit/App/Service/FrontMatterTest.php new file mode 100644 index 00000000..19574e05 --- /dev/null +++ b/test/Unit/App/Service/FrontMatterTest.php @@ -0,0 +1,29 @@ +assertSame(['title' => 'A Title', 'language' => 'en'], $result['meta']); + $this->assertSame("# A Title\n\nSome body content.", $result['body']); + } + + public function testParseReturnsTheWholeContentAsBodyWhenThereIsNoFrontMatter(): void + { + $result = FrontMatter::parse("# A Title\n\nSome body content."); + + $this->assertSame([], $result['meta']); + $this->assertSame("# A Title\n\nSome body content.", $result['body']); + } +} diff --git a/test/Unit/Blog/Handler/GetPostResourceHandlerTest.php b/test/Unit/Blog/Handler/GetPostResourceHandlerTest.php index a5c3cd88..bcd284b1 100644 --- a/test/Unit/Blog/Handler/GetPostResourceHandlerTest.php +++ b/test/Unit/Blog/Handler/GetPostResourceHandlerTest.php @@ -116,6 +116,85 @@ public function testHandleFallsBackToHtmlWhenNoMarkdownFileExistsForTheRequested $this->assertStringContainsString('text/html', $response->getHeaderLine('Content-Type')); } + /** + * @throws Exception + */ + public function testHandleRendersTheTwigTemplateWhenTheArticleIsFlaggedAsTwig(): void + { + $article = $this->createStub(Post::class); + $article->method('getStatus')->willReturn(PostStatusEnum::Published); + $article->method('isTwig')->willReturn(true); + + $postRepository = $this->createStub(PostRepository::class); + $postRepository->method('getArticleResource')->willReturn($article); + $postRepository->method('getAdjacentPosts')->willReturn(['previous' => null, 'next' => null]); + + $categoryRepository = $this->createStub(CategoryRepository::class); + $categoryRepository->method('getCategories')->willReturn([$this->createStub(Category::class)]); + + $template = $this->createMock(TemplateRendererInterface::class); + $template->expects($this->once()) + ->method('render') + ->with('page::blog-resource/a-category/a-slug', $this->anything()) + ->willReturn('twig'); + + $handler = new GetPostResourceHandler( + $template, + $postRepository, + $categoryRepository, + $this->createStub(BlogServiceInterface::class), + ); + + $request = (new ServerRequest())->withAttribute('slug', 'a-slug')->withAttribute('categorySlug', 'a-category'); + $response = $handler->handle($request); + + $this->assertSame(200, $response->getStatusCode()); + } + + /** + * @throws Exception + */ + public function testHandleRendersTheMarkdownArticleForAPublishedPost(): void + { + $markdownFile = sys_get_temp_dir() . '/' . uniqid('dk-article-', true) . '.md'; + file_put_contents($markdownFile, "---\ntitle: \"A Title\"\n---\n\n# A Title\n\nSome body content."); + + $article = $this->createStub(Post::class); + $article->method('getStatus')->willReturn(PostStatusEnum::Published); + $article->method('isTwig')->willReturn(false); + + $postRepository = $this->createStub(PostRepository::class); + $postRepository->method('getArticleResource')->willReturn($article); + $postRepository->method('getAdjacentPosts')->willReturn(['previous' => null, 'next' => null]); + + $categoryRepository = $this->createStub(CategoryRepository::class); + $categoryRepository->method('getCategories')->willReturn([$this->createStub(Category::class)]); + + $template = $this->createMock(TemplateRendererInterface::class); + $template->expects($this->once()) + ->method('render') + ->with( + 'page::markdown-article', + $this->callback( + static fn (array $params): bool => $params['content'] === 'Some body content.' + && $params['faq'] === [] + ) + ) + ->willReturn('md'); + + $blogService = $this->createStub(BlogServiceInterface::class); + $blogService->method('resolveMarkdownFilePath')->willReturn($markdownFile); + + $handler = new GetPostResourceHandler($template, $postRepository, $categoryRepository, $blogService); + + $request = (new ServerRequest())->withAttribute('slug', 'a-slug')->withAttribute('categorySlug', 'a-category'); + $response = $handler->handle($request); + + $this->assertSame(200, $response->getStatusCode()); + + unlink($markdownFile); + } + /** * @throws Exception */