Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Changelog

All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project aims to
follow [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added
- PHP **8.4 / 8.5** support and a GitHub Actions CI matrix running PHPUnit,
  PHPStan (level 7) and PSR‑12 strict on both versions.
- Unit + integration test suite (~70% line coverage), including an in‑memory
  SQLite integration harness (`tests/Support/IntegrationCase.php`).
- `composer test`, `composer test-coverage` scripts; reusable CI Docker image
  under `docker/ci/`.
- Open‑source project files: `LICENSE` (GPL‑3.0‑or‑later), `README`, `CONTRIBUTING`,
  this changelog.
- Scheduled retries: `./console reSend [N]` retries `TEMP_ERROR` messages whose
  `queue.retry_at` is due, on a geometric backoff set by the new `retry` config
  (`max_attempts` 5, `first_delay` 900, `max_delay` 86400). **Add `reSend` to
  cron** — without it nothing is retried.
- Migration `Version20260921140000` adds the nullable `queue.retry_at`
  (+ index `ix_queue_status_retry_at`). **Run migrations before deploying the
  code**: without the column every temporary failure becomes a terminal `ERROR`.
  Rows from before the migration keep `retry_at = NULL` and are never retried.
- `Exception\CacheUnavailable` (extends `Exception\Exception`), thrown by
  `Emailer::getCache()` when Redis cannot be reached.
- `sql_log` config: the level the repository layer logs its SQL at
  (`level`, a PSR‑3 `LogLevel` value, default `debug`) and whether the bound
  parameters are appended as JSON (`params`, default `false` →
  `<sql> | <json>`; boolean-like env strings accepted). Validated at
  construction. Defaults keep the previous SQL output. `params` also gates
  the INSERT / UPDATE log context (still `debug`): by default it now carries
  the column names only, the values only with `params: true`. `params: true`
  writes e‑mail addresses and other personal data into the logs.

### Removed
- Dropped the unmaintained `phroute/phroute` dependency; HTTP routing now uses a
  small built‑in `Helper\Router` (same route DSL, so configuration is unchanged).
  This also removes the implicit‑nullable deprecations that would become fatal on PHP 9.
- **BC**: removed the `Emailer::i()` global accessor and the
  `protected static self $instances` singleton store. Callers that previously
  reached `Emailer::i()->getDb()` now go through `AbstractRepository::emailer()`,
  which is wired once by `Emailer::__construct()`. No user‑level code change is
  required if you construct `Emailer` normally; only direct callers of the
  static accessor break.

### Deprecated
- `Xakki\Emailer\Repository\expresion\NullExpresion` (namespace and class name
  both carried typos). Use `Xakki\Emailer\Repository\Expression\NullExpression`
  instead. The old class now extends the new one, so `instanceof` continues to
  work for both names. Removal target: v2.

### Changed
- Queue processing (`send` / `reSend`):
  - SMTP authentication failures and transient exceptions (Redis, DB deadlock /
    lock wait timeout / lost connection) now go through the retry backoff
    instead of failing terminally on the first attempt; other exceptions stay
    terminal `ERROR`.
  - When a transport's SMTP connect, TLS or AUTH step fails (direct-MX mode
    included), the transport is paused for the rest of the run: its other
    messages are left untouched, other transports continue. Rejections after
    login (MAIL FROM / RCPT / DATA) never pause it, even when their text
    mentions authentication. A custom
    transport opts in by setting the protected `$connectionFailure`
    (`AbstractTransport::isConnectionFailure()`). A paused
    transport's messages are excluded by the selection query itself, so its
    backlog adds no query per message to the run.
  - Transport routing ties (several transports of a project with the same rank)
    now go to the lowest transport id; the order was unspecified before.
  - Each message is claimed (`RUN`) in a short transaction and sent outside any
    transaction (no DB lock held during the SMTP dialogue). Delivery is
    **at most once**: a crash or a failed result write leaves the row in `RUN`
    instead of sending it again.
  - The `retry` config is validated when `ConfigService` is constructed
    (integers; `max_attempts >= 1`, `first_delay >= 1`,
    `max_delay >= first_delay`). Integer-valued numeric strings (e.g. raw env
    `"900"`) are cast to int; other strings (`"900.5"`, `"abc"`) are rejected.
  - One `'Run queue'` (debug) line per message at start and one `'Send queue'`
    (info) outcome line with `status`, `retry`, `retry_at`.
- SMTP error classification: the authentication phrases are checked after the
  status table, so a SPAM / INVALID_* verdict wins (e.g. "DMARC authentication failed").
- Migration `Version20260922090000` drops the redundant `ix_queue_status`
  index (covered by `ix_queue_status_retry_at`).
- Minimum PHP requirement raised to **>= 8.4**.
- The whole tool‑chain now targets that floor: the dev image (`docker/php`) and the
  test image (`docker/ci`) build `FROM php:8.4` (`PHP_VERSION` overrides both, e.g.
  `make test-ci PHP_VERSION=8.5`, and the tag follows — `emailer-test:<version>`),
  PHPStan analyses with `phpVersion: 80400`, and Composer resolves against
  `config.platform.php = 8.4.0`.
- `Makefile` reads `.env_dist` and then `.env`, so the container CPU/memory caps and
  `PHP_VERSION` live in the env files; `make VAR=value` still wins over both.
- Migrated from **doctrine/dbal 3 → 4** (and `doctrine/migrations` ^3.8), bumped
  PHPMailer/Monolog, replaced PHPUnit 10 with 11 and PHPStan 1 with 2.
- Tests moved from `src/test/phpunit/` to `tests/` (namespace `Xakki\Emailer\Tests\`,
  loaded via `autoload-dev`) so they no longer ship with the package.
- Fixed the `composer.json` `license` field (was a misspelled `llicense: proprietary`).

### Fixed
- The console `send` / `reSend` summary no longer warns on a SPAM result
  (`TITLE_QUEUE_STATUS` gained `spam`; unknown statuses show as `unknown`).
- A failed `COMMIT` is reported as itself instead of being masked by a
  `NoActiveTransaction` from the follow‑up `rollBack()`.
- `Controller\Mail::initQueue` no longer warns / mis‑parses keys without a `-` separator.
- `Controller\AbstractController::renderImage` reads the MIME type from the file path
  instead of crashing on binary file contents.
- Nullable schema columns (`campaign.params/replacers`, `queue_data.last_error/transport_id`,
  `stats.uri_ref/domain_id`) are now nullable model properties; counter fields default to `0`
  to avoid "typed property accessed before initialization" errors.