Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
---
title: "[FIX] Installing PEAR packages with PHP 7.2"
title: "FIX: Installing PEAR packages with PHP 7.2"
description: "How to fix the 'Cannot use result of built-in function in write context' PEAR error on PHP 7.2 by patching Archive_Tar's func_get_args() call, and how to reinstall the affected packages afterward."
author: "Gabi DJ"
date_published: "2018-05-18"
Expand All@@ -8,7 +8,7 @@ category: "PHP Troubleshooting"
language: "en"
---

# [FIX] Installing PEAR packages with PHP 7.2
# FIX: Installing PEAR packages with PHP 7.2

## TL;DR

Expand Down
8 changes: 4 additions & 4 deletions src/App/src/Fixture/articles_cleaned.json
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
[
{
"name": "Dotkernel",
"name": "Core Components",
"slug": "dotkernel",
"isVisible": true,
"articles": [
Expand DownExpand Up@@ -1169,7 +1169,7 @@
{
"post_title": "Zend Framework 1 End-of-Life",
"post_date": "2016-06-28 11:22:06",
"post_status": "archived",
"post_status": "published",
"author": {
"display_name": "arhimede",
"github": "arhimede"
Expand DownExpand Up@@ -1238,7 +1238,7 @@
{
"post_title": "Sunsetting PEAR Channel for Zend Framework 1",
"post_date": "2022-11-11 12:55:22",
"post_status": "archived",
"post_status": "published",
"author": {
"display_name": "arhimede",
"github": "arhimede"
Expand DownExpand Up@@ -1951,7 +1951,7 @@
]
},
{
"post_title": "[FIX] Installing PEAR packages with PHP 7.2",
"post_title": "FIX: Installing PEAR packages with PHP 7.2",
"post_date": "2018-05-18 11:19:49",
"post_status": "published",
"author": {
Expand Down
121 changes: 95 additions & 26 deletions src/App/src/Service/LlmsGenerator.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,7 @@
use RuntimeException;

use function array_key_exists;
use function array_merge;
use function basename;
use function count;
use function explode;
Expand All@@ -28,16 +29,16 @@ class LlmsGenerator
{
/** @var list<non-empty-string> */
private const CATEGORY_ORDER = [
'dotkernel',
'php-development',
'dotkernel-api',
'dotkernel3',
'headless-platform',
'middleware',
'dotkernel-api',
'architecture',
'design-pattern',
'how-to',
'middleware',
'best-practice',
'design-pattern',
'dotkernel3',
'dotkernel',
'php-development',
];

/**
Expand All@@ -50,11 +51,40 @@ class LlmsGenerator
'php-troubleshooting',
'licensing',
'phpstorm',
'zend-framework',
];

/**
* Individual posts demoted to `## Optional`, keyed by category slug - for a single article
* that doesn't warrant moving its whole category into `OPTIONAL_CATEGORIES`.
*
* @var array<non-empty-string, list<non-empty-string>>
*/
private const array OPTIONAL_POSTS = [
'dotkernel' => [
'commitment-to-php-new-zend-certified-engineers-zce-in-our-team',
'adding-composer-support-in-your-dotkernel-project',
'using-dotkernel-with-composer-dependencies',
'forcing-utf8-connections-and-character-set-in-mysql',
'adding-a-cors-implementation-to-zend-expressive',
],
'best-practice' => [
'insert-update-delete-statements-with-zend-db',
'sql-queries-using-zend-db-select',
'subqueries-with-zend-db',
'using-like-wildcards-with-zend-db',
'what-are-returning-the-fetch-functions-from-zend-db',
],
'php-development' => [
'almalinux-9-in-wsl2-install-php-apache-mariadb-composer-phpmyadmin',
'mezzio-app-development-in-wsl2',
],
];

/** @var array<non-empty-string, non-empty-string> */
private const CATEGORY_BLURBS = [
'dotkernel' => 'the core framework - releases, caching, sessions, auth, WURFL/device detection',
'dotkernel' => 'the core framework - dot-* components (caching, logging, mail, dependency injection),'
. ' Doctrine integration, and getting started with Dotkernel Light',
'php-development' => 'general PHP tooling, environments, IDEs, security',
'how-to' => 'practical guides - migrations, CORS, PSR-7, routing',
'best-practice' => 'coding standards and database access patterns',
Expand DownExpand Up@@ -89,6 +119,15 @@ class LlmsGenerator
*/
private const string OSS_LIFECYCLE_SLUG = 'dotkernel-packages-oss-lifecycle';

/**
* The Contact page has no `public/contact.md` counterpart to source a title/description
* from, so it is hardcoded directly into `## Pages` instead.
*/
private const string CONTACT_TITLE = 'Contact';
private const string CONTACT_PATH = 'contact/';
private const string CONTACT_DESCRIPTION = 'Get in touch, report a security issue, or find where to ask a '
. 'question and contribute to the project.';

public function __construct(
private readonly PostRepository $postRepository,
private readonly string $sourceDir,
Expand All@@ -108,16 +147,20 @@ public function write(): int
{
$postsByCategory = $this->groupPublishedPostsByCategory();

$sections = [];
$sections = [];
$optionalLines = [];
foreach (self::CATEGORY_ORDER as $slug) {
if (! isset($postsByCategory[$slug])) {
continue;
}
$sections[] = $this->buildCategorySection($slug, $postsByCategory[$slug]);
[$entries, $demoted] = $this->splitOptionalPosts($slug, $postsByCategory[$slug]);
$optionalLines = array_merge($optionalLines, $demoted);
if ($entries !== []) {
$sections[] = $this->buildCategorySection($slug, $entries);
}
unset($postsByCategory[$slug]);
}

$optionalLines = [];
$optionalCategoryCount = 0;
foreach (self::OPTIONAL_CATEGORIES as $slug) {
if (! isset($postsByCategory[$slug])) {
Expand All@@ -132,7 +175,11 @@ public function write(): int

uasort($postsByCategory, static fn (array $a, array $b): int => count($b) - count($a));
foreach ($postsByCategory as $slug => $posts) {
$sections[] = $this->buildCategorySection($slug, $posts);
[$entries, $demoted] = $this->splitOptionalPosts($slug, $posts);
$optionalLines = array_merge($optionalLines, $demoted);
if ($entries !== []) {
$sections[] = $this->buildCategorySection($slug, $entries);
}
}

$written = $this->buildHeader()
Expand DownExpand Up@@ -174,17 +221,40 @@ private function groupPublishedPostsByCategory(): array
return $byCategory;
}

/**
* @param list<array{post: Post, title: string, description: string}> $entries
* @return array{0: list<array{post: Post, title: string, description: string}>, 1: list<string>}
*/
private function splitOptionalPosts(string $slug, array $entries): array
{
$optionalSlugs = self::OPTIONAL_POSTS[$slug] ?? [];
if ($optionalSlugs === []) {
return [$entries, []];
}

$kept = [];
$optional = [];
foreach ($entries as $entry) {
if (in_array($entry['post']->getSlug(), $optionalSlugs, true)) {
$optional[] = $this->buildOptionalPostLink($slug, $entry);
continue;
}

$kept[] = $entry;
}

return [$kept, $optional];
}

/**
* @param list<array{post: Post, title: string, description: string}> $entries
*/
private function buildCategorySection(string $slug, array $entries): string
{
$category = $entries[0]['post']->getCategory();
$heading = sprintf(
'## %s (%d post%s)',
$category->getName(),
count($entries),
count($entries) === 1 ? '' : 's',
'## %s',
$category->getName()
);

$lines = [$heading, ''];
Expand DownExpand Up@@ -256,7 +326,13 @@ private function buildPagesSection(): string
return '';
}

$pages = [];
$pages = [
[
'title' => self::CONTACT_TITLE,
'path' => self::CONTACT_PATH,
'description' => self::CONTACT_DESCRIPTION,
],
];
foreach (glob($this->pagesDir . '/*.md') ?: [] as $path) {
$slug = basename($path, '.md');
if ($slug === self::OSS_LIFECYCLE_SLUG || in_array($slug, self::DOCS_PAGES, true)) {
Expand All@@ -276,24 +352,20 @@ private function buildPagesSection(): string

$pages[] = [
'title' => trim(explode('|', $title, 2)[0]),
'slug' => $slug,
'path' => $slug . '.md',
'description' => $description,
];
}

if ($pages === []) {
return '';
}

usort($pages, static fn (array $a, array $b): int => strcasecmp($a['title'], $b['title']));

$lines = ['## Pages', ''];
foreach ($pages as $page) {
$lines[] = sprintf(
'- [%s](%s/%s.md): %s',
'- [%s](%s/%s): %s',
$page['title'],
$this->baseUrl,
$page['slug'],
$page['path'],
$page['description'],
);
}
Expand DownExpand Up@@ -358,9 +430,6 @@ private function buildHeader(): string
. 'by category and attributed to an author; URLs follow the pattern `/{category-slug}/{post-slug}/`. '
. 'Each post also has a Markdown version at `/{category-slug}/{post-slug}.md`.';

$about = 'the team behind Dotkernel - how the team works, its commitment to open source and the PHP '
. 'community, and how it uses AI under guardrails';

return "# {$this->title}\n\n"
. $intro . "\n\n"
. $body . "\n\n"
Expand Down
37 changes: 23 additions & 14 deletions test/Unit/App/Service/LlmsGeneratorTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -117,8 +117,8 @@ public function testWriteOrdersCategorySectionsAccordingToTheCuratedOrder(): voi
$this->createGenerator($posts)->write();

$output = $this->writtenOutput();
$dotkernelAt = mb_strpos($output, '## Dotkernel (');
$phpDevAt = mb_strpos($output, '## PHP Development (');
$dotkernelAt = mb_strpos($output, '## Dotkernel');
$phpDevAt = mb_strpos($output, '## PHP Development');

$this->assertNotFalse($dotkernelAt);
$this->assertNotFalse($phpDevAt);
Expand All@@ -140,9 +140,9 @@ public function testWriteAppendsCategoriesNotInTheCuratedOrderAfterTheCuratedOne
$this->createGenerator($posts)->write();

$output = $this->writtenOutput();
$dotkernelAt = mb_strpos($output, '## Dotkernel (');
$bigAt = mb_strpos($output, '## Extra Big (2 posts)');
$smallAt = mb_strpos($output, '## Extra Small (1 post)');
$dotkernelAt = mb_strpos($output, '## Dotkernel');
$bigAt = mb_strpos($output, '## Extra Big');
$smallAt = mb_strpos($output, '## Extra Small');

$this->assertNotFalse($dotkernelAt);
$this->assertNotFalse($bigAt);
Expand All@@ -164,7 +164,7 @@ public function testWriteMovesAnOptionalCategoryPostToATrailingOptionalSectionWi
$this->createGenerator($posts)->write();

$output = $this->writtenOutput();
$dotkernelAt = mb_strpos($output, '## Dotkernel (');
$dotkernelAt = mb_strpos($output, '## Dotkernel');
$optionalAt = mb_strpos($output, '## Optional');
$jsPostAt = mb_strpos($output, '[JS post]');

Expand DownExpand Up@@ -254,7 +254,7 @@ public function testWriteIncludesTheBlurbLineForACuratedCategory(): void
$this->createGenerator($posts)->write();

$this->assertStringContainsString(
"## Dotkernel (1 post)\n\n*the core framework",
"## Dotkernel\n\n*the core framework",
$this->writtenOutput()
);
}
Expand All@@ -269,22 +269,24 @@ public function testWriteOmitsTheBlurbLineForAnUncuratedCategory(): void
$this->createGenerator($posts)->write();

$this->assertStringContainsString(
"## Unlisted (1 post)\n\n- [A post]",
"## Unlisted\n\n- [A post]",
$this->writtenOutput()
);
}

/**
* Category headings no longer carry a post count, regardless of how many posts they hold.
*
* @throws Exception
*/
public function testWriteUsesSingularPostWordingForACategoryWithExactlyOnePost(): void
public function testWriteCategoryHeadingHasNoPostCount(): void
{
$posts = [$this->createPost('A post', 'a-post', 'dotkernel', 'Dotkernel')];

$this->createGenerator($posts)->write();

$this->assertStringContainsString('## Dotkernel (1 post)', $this->writtenOutput());
$this->assertStringNotContainsString('(1 posts)', $this->writtenOutput());
$this->assertStringContainsString("## Dotkernel\n", $this->writtenOutput());
$this->assertStringNotContainsString('## Dotkernel (', $this->writtenOutput());
}

/**
Expand DownExpand Up@@ -360,13 +362,20 @@ public function testWriteOmitsThePagesSectionWhenNoPagesDirectoryIsConfigured():
}

/**
* The hardcoded Contact entry has no source file, so it still renders even when the
* directory otherwise has nothing to glob - and nothing else does, in that case.
*
* @throws Exception
*/
public function testWriteOmitsThePagesSectionWhenThePagesDirectoryIsEmpty(): void
public function testWritePagesSectionContainsOnlyContactWhenThePagesDirectoryIsEmpty(): void
{
$this->createGenerator()->write();

$this->assertStringNotContainsString('## Pages', $this->writtenOutput());
$this->assertStringContainsString(
"## Pages\n\n- [Contact](https://example.test/contact/): Get in touch, report a security issue, "
. "or find where to ask a question and contribute to the project.\n\n",
$this->writtenOutput()
);
}

/**
Expand DownExpand Up@@ -433,7 +442,7 @@ public function testWritePlacesThePagesSectionBetweenDocsAndTheFirstCategorySect
$output = $this->writtenOutput();
$docsAt = mb_strpos($output, '## Docs');
$pagesAt = mb_strpos($output, '## Pages');
$dotkernelAt = mb_strpos($output, '## Dotkernel (');
$dotkernelAt = mb_strpos($output, '## Dotkernel');

$this->assertNotFalse($docsAt);
$this->assertNotFalse($pagesAt);
Expand Down