Cannot redeclare get_urls(): two trees, one function

Содержание
Symptom: 249 identical fatals
Why require_once does not save you
The two copies
Fix: keep Rblock in its own tree
Guard: belt and suspenders
Verify: logs, render, deploy

Symptom: 249 identical fatals

The dev error log filled with 249 copies of one line, all within minutes, referer /qa/robot/:

PHP Fatal error: Cannot redeclare get_urls() (previously declared in /.php/_modules/ads_dict.php:12) in /ao/.php/_modules/ads_dict.php on line 12

A companion case — live page truncated to 30 bytes — covered the same fatal from the symlink angle. This one is about the remaining angle: two engine trees defining one global function name.

Why require_once does not save you

require_once dedupes by resolved file path, not by content and not by function name. Two genuinely different files that happen to define get_urls() load twice, and the second definition fatals — exactly what happened here: /.php/_modules/ads_dict.php plus /ao/.php/_modules/ads_dict.php on one page.

No spelling trick helps; the dedup key (path) differs while the collision key (function name) is identical. Details and a path experiment: require vs include.

The two copies

House rule first: every page may only require modules from its own engine tree — all pages were already scanned for cross-engine calls and none was found. The ads dictionary still exists twice, once per tree, each declaring a global get_urls():

ads_dict.php copies and their guards
/.php/_modules/ads_dict.php guarded with function_exists
/ao/.php/_modules/ads_dict.php guarded with function_exists

Verifying “no page mixes engines” file-by-file misses it: not one file requires two dicts. The mixing happens one level down, in the runtime require graph — the log referer names the page:

/qa/robot/index.php:8 — require root /.php/_modules/ads_dict.php → defines get_urls() the first time — legal, page continues /qa/robot/index.php:18 — boot the shared Page engine Page::displayRblock() → Rblock.php:262 Rblock loads $root/ao/.php/right_blocks/robot.php (rnav: robot) robot.php:13 — require ao dict → PHP meets a second function get_urls() with the same name in the global scope; require_once cannot stop it (different file path), so on any build without the function_exists guard this line is fatal

The shared engine itself reaches into the ao tree: the ao right-block files (robot.php, qa.php, django.php, …) each require the ao dict — while ao content files symmetrically pull shared /.php/_modules/* helpers the other way. The engine boundary holds per file but not per request.

The same audit pattern applies to every shared helper: local_img() is guarded in both trees, img_from_dir() is guarded in both trees, but ao_img() is guarded in none of its four trees — latent today (no page loads two copies), a fatal the day one does.

Fix: keep Rblock in its own tree

The real fix removes the bridge instead of cushioning it: the shared Rblock must load right blocks from the shared /.php/right_blocks/ — never from /ao/.php/right_blocks/. The shared dir covers every ao file, so no page loses its ad block; pages simply stop executing a second engine tree per request. This also matches AdvertTwo, which already resolves $root/.php/right_blocks/$rnav.php.

$right_block_url = "$root/.php/right_blocks/$this->rnav.php";

Guard: belt and suspenders

The guard is not the fix — it is the safety net for the next bridge anyone builds. Wrap every global definition so a second load is a no-op:

if (!function_exists('get_urls')) { function get_urls(...$numbers) { ... } } // function_exists('get_urls')

Warning: the guard hides divergence. Whichever copy loads first wins silently, and the loser never runs — no error, no log line. Our two dicts match today, so either copy behaves the same; the day someone edits only one of them, pages start behaving differently depending on include order, and nothing will tell you. Treat a guard as a tripwire that bought time, not as permission to keep two copies: converge them into one file owned by one tree.

The guard is idempotent and version-proof: on PHP 4/5 the duplicate definition was an uncatchable compile fatal; since PHP 7 it throws a catchable Error — but uncaught it still kills the page, so the guard matters on every version.

Verify: logs, render, deploy

1. Render the previously fatal pages locally (DOCUMENT_ROOT=container/src) and assert the footer marker — the log names file and line outright, so it is always the first place to look.

2. Old line numbers in the log (3 vs 11 vs 12) mean hosting runs older code than the repo: after the fix, shadow-copy to dev, purge OPcache plus recycle the PHP workers (upload alone is not deterministic — stale workers were observed locally), then curl the failing URLs with X-Forwarded-Proto: https and expect 200.

3. Re-download the logs a day later and re-run the analyzer on a fresh window — the old report mixes rotated logs, so old fatals linger and prove nothing.

Article author: Arthur Isaev

Похожие статьи
Development with PHP
PHP errors: case studies
Top menu disappeared
Live page truncated to 30 bytes
Cannot redeclare get_urls()

Поиск по сайту

Канал @aofeed Чат @aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: