wexample-wex-addon-dev-php 12.0.3__tar.gz → 12.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/PKG-INFO +8 -8
  2. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/README.md +4 -4
  3. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/pyproject.toml +7 -4
  4. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/resources/design_rules/php_code.md +11 -0
  5. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/ai/agents/editor/agent.yml +1 -1
  6. wexample_wex_addon_dev_php-12.2.0/src/wexample_wex_addon_dev_php/services/symfony/knowledge/entity.md +80 -0
  7. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/workdir/php_package_workdir.py +37 -2
  8. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/workdir/php_symfony_workdir.py +167 -0
  9. wexample_wex_addon_dev_php-12.2.0/tests/workdir/__init__.py +0 -0
  10. wexample_wex_addon_dev_php-12.2.0/tests/workdir/test_php_package_workdir.py +54 -0
  11. wexample_wex_addon_dev_php-12.0.3/src/wexample_wex_addon_dev_php/services/symfony/knowledge/entity.md +0 -4
  12. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/__init__.py +0 -0
  13. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/__pycache__/__init__.py +0 -0
  14. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/config_value/__init__.py +0 -0
  15. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/config_value/php_package_readme_config_value.py +0 -0
  16. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/const/__init__.py +0 -0
  17. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/const/tags.py +0 -0
  18. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/file/__init__.py +0 -0
  19. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/file/php_composer_json_file.py +0 -0
  20. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/formatter/__init__.py +0 -0
  21. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/formatter/php_code_formatter.py +0 -0
  22. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/php_addon_manager.py +0 -0
  23. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/py.typed +0 -0
  24. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/resources/.wex.yml +0 -0
  25. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/resources/docker/Dockerfile.roave +0 -0
  26. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/resources/writing_rules/php_code.md +0 -0
  27. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/__init__.py +0 -0
  28. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/composer/__init__.py +0 -0
  29. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/composer/commands/__init__.py +0 -0
  30. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/composer/commands/service/__init__.py +0 -0
  31. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/composer/commands/service/install_local.py +0 -0
  32. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/composer/commands/service/refresh_lock.py +0 -0
  33. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/composer/docker/.wex.yml +0 -0
  34. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/composer/docker/docker-compose.yml +0 -0
  35. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/composer/service.yml +0 -0
  36. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/__init__.py +0 -0
  37. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/docker/.wex.yml +0 -0
  38. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/docker/docker-compose.yml +0 -0
  39. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/.wex.yml +0 -0
  40. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/caddy/.wex.yml +0 -0
  41. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/caddy/Caddyfile +0 -0
  42. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/docker/.wex.yml +0 -0
  43. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/docker/docker-compose.yml +0 -0
  44. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/env/dev/docker/.wex.yml +0 -0
  45. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/env/dev/docker/docker-compose.yml +0 -0
  46. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/env/local/docker/.wex.yml +0 -0
  47. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/env/local/docker/docker-compose.yml +0 -0
  48. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/env/prod/docker/.wex.yml +0 -0
  49. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/env/prod/docker/docker-compose.yml +0 -0
  50. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/php/.wex.yml +0 -0
  51. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/samples/php/web.ini +0 -0
  52. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/frankenphp/service.yml +0 -0
  53. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/__init__.py +0 -0
  54. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/app_service.py +0 -0
  55. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/commands/__init__.py +0 -0
  56. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/commands/db/__init__.py +0 -0
  57. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/commands/db/migrate.yml +0 -0
  58. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/commands/maintenance/__init__.py +0 -0
  59. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/commands/maintenance/disable.yml +0 -0
  60. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/commands/maintenance/enable.yml +0 -0
  61. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/commands/service/__init__.py +0 -0
  62. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/commands/service/install_local.py +0 -0
  63. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/commands/service/refresh_lock.py +0 -0
  64. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/docker/.wex.yml +0 -0
  65. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/docker/docker-compose.yml +0 -0
  66. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/samples/.wex.yml +0 -0
  67. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/samples/docker/docker-compose.yml +0 -0
  68. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/samples/env/dev/docker/docker-compose.yml +0 -0
  69. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/samples/env/prod/docker/docker-compose.yml +0 -0
  70. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/laravel/service.yml +0 -0
  71. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/__init__.py +0 -0
  72. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/ai/__init__.py +0 -0
  73. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/ai/shared/__init__.py +0 -0
  74. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/ai/shared/context.j2 +0 -0
  75. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/ai/shared/php.j2 +0 -0
  76. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/docker/.wex.yml +0 -0
  77. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/docker/docker-compose.yml +0 -0
  78. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/.wex.yml +0 -0
  79. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/caddy/.wex.yml +0 -0
  80. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/caddy/Caddyfile +0 -0
  81. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/docker/.wex.yml +0 -0
  82. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/docker/docker-compose.yml +0 -0
  83. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/env/dev/docker/.wex.yml +0 -0
  84. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/env/dev/docker/docker-compose.yml +0 -0
  85. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/env/local/docker/.wex.yml +0 -0
  86. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/env/local/docker/docker-compose.yml +0 -0
  87. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/env/prod/docker/.wex.yml +0 -0
  88. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/env/prod/docker/docker-compose.yml +0 -0
  89. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/php/.wex.yml +0 -0
  90. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/samples/php/web.ini +0 -0
  91. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/php/service.yml +0 -0
  92. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/phpmyadmin/__init__.py +0 -0
  93. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/phpmyadmin/commands/__init__.py +0 -0
  94. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/phpmyadmin/commands/runtime/__init__.py +0 -0
  95. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/phpmyadmin/docker/.wex.yml +0 -0
  96. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/phpmyadmin/docker/docker-compose.yml +0 -0
  97. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/phpmyadmin/env/.wex.yml +0 -0
  98. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/phpmyadmin/env/local/docker/docker-compose.yml +0 -0
  99. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/phpmyadmin/service.yml +0 -0
  100. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/__init__.py +0 -0
  101. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/ai/__init__.py +0 -0
  102. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/ai/agents/__init__.py +0 -0
  103. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/ai/agents/editor/__init__.py +0 -0
  104. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/ai/agents/editor/context.j2 +0 -0
  105. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/ai/shared/__init__.py +0 -0
  106. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/ai/shared/context.j2 +0 -0
  107. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/app_service.py +0 -0
  108. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/commands/__init__.py +0 -0
  109. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/commands/service/__init__.py +0 -0
  110. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/commands/service/deploy.py +0 -0
  111. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/commands/service/install_local.py +0 -0
  112. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/commands/service/watch.py +0 -0
  113. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/docker/.wex.yml +0 -0
  114. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/docker/docker-compose.yml +0 -0
  115. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/knowledge/__init__.py +0 -0
  116. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/.wex.yml +0 -0
  117. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/docker/.wex.yml +0 -0
  118. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/docker/docker-compose.yml +0 -0
  119. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/docker/env.build +0 -0
  120. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/docker/images/.wex.yml +0 -0
  121. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/docker/images/Dockerfile.dev +0 -0
  122. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/docker/images/Dockerfile.release +0 -0
  123. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/env/dev/docker/.wex.yml +0 -0
  124. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/env/dev/docker/docker-compose.yml +0 -0
  125. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/env/local/docker/.wex.yml +0 -0
  126. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/env/local/docker/docker-compose.yml +0 -0
  127. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/env/prod/docker/.wex.yml +0 -0
  128. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/samples/env/prod/docker/docker-compose.yml +0 -0
  129. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/symfony/service.yml +0 -0
  130. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/__init__.py +0 -0
  131. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/commands/__init__.py +0 -0
  132. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/commands/service/__init__.py +0 -0
  133. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/commands/url/__init__.py +0 -0
  134. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/commands/url/replace.py +0 -0
  135. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/docker/.wex.yml +0 -0
  136. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/docker/docker-compose.yml +0 -0
  137. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/samples/.wex.yml +0 -0
  138. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/samples/docker/docker-compose-cli.yml +0 -0
  139. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/samples/docker/docker-compose.yml +0 -0
  140. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/samples/env/dev/docker/docker-compose.yml +0 -0
  141. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/samples/env/local/wp-cli/.gitkeep +0 -0
  142. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/samples/env/prod/docker/docker-compose.yml +0 -0
  143. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/services/wordpress/service.yml +0 -0
  144. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/workdir/__init__.py +0 -0
  145. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/workdir/php_packages_suite_workdir.py +0 -0
  146. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/src/wexample_wex_addon_dev_php/workdir/php_workdir.py +0 -0
  147. {wexample_wex_addon_dev_php-12.0.3 → wexample_wex_addon_dev_php-12.2.0}/tests/.gitkeep +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wexample-wex-addon-dev-php
3
- Version: 12.0.3
3
+ Version: 12.2.0
4
4
  Summary: Extends wex with PHP-specific development commands for Composer, Laravel, Symfony, and WordPress services running in Docker containers
5
5
  Author-Email: weeger <contact@wexample.com>
6
6
  License: MIT
@@ -12,9 +12,9 @@ Requires-Python: >=3.10
12
12
  Requires-Dist: attrs>=23.1.0
13
13
  Requires-Dist: cattrs>=23.1.0
14
14
  Requires-Dist: wexample-filestate-php>=6.4.0
15
- Requires-Dist: wexample-filestate-symfony>=1.1.0
16
- Requires-Dist: wexample-wex-addon-ai>=14.3.0
17
- Requires-Dist: wexample-wex-addon-app>=31.0.0
15
+ Requires-Dist: wexample-filestate-symfony>=2.0.0
16
+ Requires-Dist: wexample-wex-addon-ai>=15.0.0
17
+ Requires-Dist: wexample-wex-addon-app>=31.1.0
18
18
  Requires-Dist: wexample-wex-addon-dev-javascript>=8.1.0
19
19
  Provides-Extra: dev
20
20
  Requires-Dist: pytest; extra == "dev"
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
23
23
 
24
24
  # wex_addon_dev_php
25
25
 
26
- Version: 12.0.3
26
+ Version: 12.2.0
27
27
 
28
28
  `wex_addon_dev_php` extends the wex CLI with PHP-specific development commands for Composer, Laravel, Symfony, and WordPress services running in Docker containers. It is aimed at PHP developers who manage their apps through wex and need framework-aware automation — dependency wiring, migration runs, lock-file refreshes — executed directly inside the running service containers. The addon registers four workdir types (`php`, `php-package`, `php-packages-suite`, `php-symfony`) and contributes its commands through the standard `PhpAddonManager` plugin point.
29
29
 
@@ -274,9 +274,9 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
274
274
  - attrs: >=23.1.0
275
275
  - cattrs: >=23.1.0
276
276
  - wexample-filestate-php: >=6.4.0
277
- - wexample-filestate-symfony: >=1.1.0
278
- - wexample-wex-addon-ai: >=14.3.0
279
- - wexample-wex-addon-app: >=31.0.0
277
+ - wexample-filestate-symfony: >=2.0.0
278
+ - wexample-wex-addon-ai: >=15.0.0
279
+ - wexample-wex-addon-app: >=31.1.0
280
280
  - wexample-wex-addon-dev-javascript: >=8.1.0
281
281
 
282
282
  ## Versioning & Compatibility Policy
@@ -1,6 +1,6 @@
1
1
  # wex_addon_dev_php
2
2
 
3
- Version: 12.0.3
3
+ Version: 12.2.0
4
4
 
5
5
  `wex_addon_dev_php` extends the wex CLI with PHP-specific development commands for Composer, Laravel, Symfony, and WordPress services running in Docker containers. It is aimed at PHP developers who manage their apps through wex and need framework-aware automation — dependency wiring, migration runs, lock-file refreshes — executed directly inside the running service containers. The addon registers four workdir types (`php`, `php-package`, `php-packages-suite`, `php-symfony`) and contributes its commands through the standard `PhpAddonManager` plugin point.
6
6
 
@@ -251,9 +251,9 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
251
251
  - attrs: >=23.1.0
252
252
  - cattrs: >=23.1.0
253
253
  - wexample-filestate-php: >=6.4.0
254
- - wexample-filestate-symfony: >=1.1.0
255
- - wexample-wex-addon-ai: >=14.3.0
256
- - wexample-wex-addon-app: >=31.0.0
254
+ - wexample-filestate-symfony: >=2.0.0
255
+ - wexample-wex-addon-ai: >=15.0.0
256
+ - wexample-wex-addon-app: >=31.1.0
257
257
  - wexample-wex-addon-dev-javascript: >=8.1.0
258
258
 
259
259
  ## Versioning & Compatibility Policy
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "wexample-wex-addon-dev-php"
9
- version = "12.0.3"
9
+ version = "12.2.0"
10
10
  description = "Extends wex with PHP-specific development commands for Composer, Laravel, Symfony, and WordPress services running in Docker containers"
11
11
  authors = [
12
12
  { name = "weeger", email = "contact@wexample.com" },
@@ -21,9 +21,9 @@ dependencies = [
21
21
  "attrs>=23.1.0",
22
22
  "cattrs>=23.1.0",
23
23
  "wexample-filestate-php>=6.4.0",
24
- "wexample-filestate-symfony>=1.1.0",
25
- "wexample-wex-addon-ai>=14.3.0",
26
- "wexample-wex-addon-app>=31.0.0",
24
+ "wexample-filestate-symfony>=2.0.0",
25
+ "wexample-wex-addon-ai>=15.0.0",
26
+ "wexample-wex-addon-app>=31.1.0",
27
27
  "wexample-wex-addon-dev-javascript>=8.1.0",
28
28
  ]
29
29
 
@@ -43,6 +43,9 @@ dev = [
43
43
  "pytest-cov",
44
44
  ]
45
45
 
46
+ [project.entry-points."wex.addons"]
47
+ php = "wexample_wex_addon_dev_php.php_addon_manager:PhpAddonManager"
48
+
46
49
  [tool.setuptools.packages.find]
47
50
  include = [
48
51
  "*",
@@ -15,6 +15,17 @@ is already a decision made.
15
15
  - Suffix the name with the kind where the kind is the whole point: `ArrayHelper`,
16
16
  `WithDomId` for a trait, `AbstractFromArrayObject` for a base class. The reader picks
17
17
  up what a class is for from its name before opening it.
18
+ - A route says who it is for in its **path**, never in its name. A path opening on an
19
+ underscore — `/_system/component`, `/_forms/submit`, `/_live/subscribe-info` — is
20
+ reached by a program; one without is a page someone opens and reads. Route *names*
21
+ never carry that underscore, whichever kind they are: `system_component_render`, not
22
+ `_system_component_render`. The rule reads in one direction only, so a name can be
23
+ renamed without moving a url and a page can be turned into an endpoint without
24
+ touching every `path()` call. Symfony's own `_profiler` and `_wdt` carry it on both
25
+ and are left alone.
26
+ The one exception is `/api`, which holds machine endpoints and, at `/api/`, the page
27
+ documenting them. Two natures under one prefix: moving it to `/_api` would rewrite the
28
+ `baseUrl` of every client for a gain nobody reads, so it stays as it is.
18
29
  - Helper classes are `final`-shaped by use: only `public static` methods, no state, no
19
30
  instantiation. A helper that needs a constructor has stopped being a helper and belongs
20
31
  in `Class/`.
@@ -1,5 +1,5 @@
1
1
  description: Edits this app's Symfony code, following the conventions of this stack
2
- model: claude:sonnet-5
2
+ model: claude-sonnet-5
3
3
  modes: talk
4
4
  # `reset` until extraction exists would drop a conversation before anything took its
5
5
  # value out. Switch back once `/session-extract` runs ahead of it.
@@ -0,0 +1,80 @@
1
+ A Symfony entity must extend, directly or indirectly, the abstract class
2
+ `Wexample\SymfonyHelpers\Entity\AbstractEntity`. It provides the `id` column, the
3
+ `BaseEntityTrait` and the entity naming/path helpers the rest of the framework relies
4
+ on — an entity that does not extend it is not wired into those conventions.
5
+
6
+ Writing that class is one step of six. The five others are what turn a Doctrine row into
7
+ something the API and the front can address, and each is carried by a package that
8
+ documents it under its own `.wex/knowledge/`.
9
+
10
+ | # | Step | Produces |
11
+ |---|---|---|
12
+ | 1 | Write the class and its attributes | `src/Entity/{Entity}.php` |
13
+ | 2 | `wex app::state/rectify` | manipulator trait, repository, and one artefact family per attribute declared |
14
+ | 3 | Write the DTO and return it from the scaffolded normalizer | `src/Api/Dto/{Entity}Dto.php` |
15
+ | 4 | `php bin/console pseudocode:generate:pseudocode` | `pseudocode/entity/{entity}.yml` |
16
+ | 5 | `php bin/console api:export:entities`, then js-api's `generate-entities` and `generate-repositories` | the json schema, the TypeScript entity and its repository |
17
+ | 6 | Consume it from the front | — |
18
+
19
+ Where each one is described:
20
+
21
+ - steps 1 and 2 — `wexample-filestate-symfony`, `cookbook/create-an-entity`. `#[ApiEntity]`,
22
+ `#[EntityForm]`, `#[LinkableEntity]`, `#[ImportableEntity]` and `#[ApiEndpoint]` each
23
+ scaffold their own family; nothing existing is ever rewritten, so a second pass is
24
+ harmless and the generated body is a starting point rather than a contract.
25
+ - step 4 — `wexample/symfony-pseudocode`, `usage/overview`.
26
+ - step 5 — `wexample/symfony-api`, `usage/entity-export-pipeline`. The three commands are
27
+ run by hand from the app, because the first needs a booted kernel to scan. An entity
28
+ belonging to a bundle still has its artefacts belonging to the bundle, which is a matter
29
+ of path arguments — and this is the only part of the chain with traps, listed there.
30
+ - step 6 — `@wexample/js-api`, `usage/overview`.
31
+
32
+ Step 3 is the one piece nothing scaffolds, because the serialized shape is a choice and not
33
+ a deduction. The DTO extends `AbstractEntityDto`, and the generated normalizer returns
34
+ `{Entity}Dto::fromEntity($entity)` instead of the generic array, its return type widened to
35
+ `NormalizableDataInterface`. A DTO property is named after the *entity* property rather than
36
+ the column: that name is the one the step-5 schema makes the browser check.
37
+
38
+ ## Nothing creates an entity but its repository
39
+
40
+ `new {Entity}()` written outside the repository is not allowed. `AbstractRepository`
41
+ resolves `createNew{Entity}()` and `saveNew{Entity}()` magically: both call the
42
+ `createNew{Entity}()` the repository declares, and `saveNew` persists and flushes what it
43
+ returns. So the repository carries one hand-written method, and the caller only ever sees
44
+ the magic one — declared in the class docblock, which is what makes the IDE and static
45
+ analysis follow it:
46
+
47
+ ```php
48
+ /**
49
+ * @method Message saveNewMessage(Session $session, string $type, string $body)
50
+ */
51
+ ```
52
+
53
+ The magic matches the entity's short name and nothing else, so a special case is written
54
+ out in full as `createNew{Entity}From{Source}()` and called by that name. Being an ordinary
55
+ method it persists nothing unless it says so — `MessageRepository::saveNewMessageAndPushEvent()`
56
+ in `wexample/symfony-ai` is that shape, delegating to `saveNewMessage()` and dispatching
57
+ only once the row is written.
58
+
59
+ ## Posted data comes from a DTO, never from the request
60
+
61
+ `$request->getPayload()` is not read. A POST body is declared as a DTO and validated before
62
+ the controller method runs:
63
+
64
+ ```php
65
+ #[ValidateRequestContent(dto: CreateMessageDto::class, attributeName: 'createMessageDto')]
66
+ public function create(CreateMessageDto $createMessageDto, ...): ApiResponse
67
+ ```
68
+
69
+ The DTO extends `AbstractDto`, sits in `Api\Dto\Entity\{Entity}\`, and carries its
70
+ constraints on its properties — `#[Type]`, `#[NotBlank]`, `#[RequiredDtoProperty]`. It is
71
+ modelled on the entity it feeds, one property per field written, and modelled on it rather
72
+ than copied from it: what a caller may set is not what the entity holds. `CreateMessageDto`
73
+ asks for `session` and `body` and not for `type`, because a caller allowed to name the
74
+ author of a turn would be allowed to write the answers.
75
+
76
+ This is not the family of step 3: that DTO extends `AbstractEntityDto` and goes out through
77
+ the normalizer, this one comes in.
78
+
79
+ Forms branch off the same rectify and go nowhere near pseudocode or js-api —
80
+ `wexample/symfony-forms`, `cookbook/create-a-form`.
@@ -27,6 +27,16 @@ if TYPE_CHECKING:
27
27
 
28
28
  @base_class
29
29
  class PhpPackageWorkdir(WithRunnerWorkdirMixin, PhpWorkdir):
30
+ @staticmethod
31
+ def line_opens_a_comment(line: str) -> bool:
32
+ """Whether a line is a comment, judged on the characters it opens with.
33
+
34
+ A line-level test and not a parse: it catches the docblock body and the `//`
35
+ line, which is where a class gets *mentioned* rather than used. A name trailing
36
+ real code after a `//` on the same line stays reported — rarer, and for a
37
+ dependency check erring toward reporting is the right way round.
38
+ """
39
+ return line.lstrip().startswith(("*", "//", "#", "/*"))
30
40
  def get_package_import_name(self) -> str:
31
41
  """Get the full package import name with vendor prefix."""
32
42
  return f"{string_to_pascal_case(self.get_vendor_name())}\\{string_to_pascal_case(self.get_project_name())}"
@@ -60,12 +70,37 @@ class PhpPackageWorkdir(WithRunnerWorkdirMixin, PhpWorkdir):
60
70
  def search_imports_in_codebase(
61
71
  self, searched_package: PhpPackageWorkdir
62
72
  ) -> list[SearchResult]:
63
- """Find PHP `use`/qualified references to the given package."""
73
+ """Find PHP `use`/qualified references to the given package.
74
+
75
+ Two shapes count as an import: the `use` statement, and the fully qualified
76
+ name written inline (`new \\Wexample\\Foo\\Bar()`). The second is why matches
77
+ have to be sifted afterwards — a fully qualified name reads the same inside a
78
+ docblock, and an `@deprecated` note pointing at the class that replaces this
79
+ one is documentation, not a dependency.
80
+
81
+ The `use` branch needs no sifting: it is anchored on `^\\s*use`, which a
82
+ docblock's leading `*` already prevents from matching.
83
+ """
64
84
  import re
65
85
 
66
86
  pkg = re.escape(searched_package.get_package_import_name())
67
87
  pattern = rf"(?m)^\s*use\s+{pkg}(?:\\\\[\w]+)*\s*;|{pkg}(?:\\\\[\w]+)*(?!\w)"
68
- return self.search_in_codebase(pattern, regex=True, flags=re.MULTILINE)
88
+ results = self.search_in_codebase(pattern, regex=True, flags=re.MULTILINE)
89
+
90
+ lines_by_path: dict[str, list[str]] = {}
91
+ kept: list[SearchResult] = []
92
+
93
+ for result in results:
94
+ path = str(result.item.get_path())
95
+ if path not in lines_by_path:
96
+ lines_by_path[path] = result.item.read_text().splitlines()
97
+
98
+ lines = lines_by_path[path]
99
+ line = lines[result.line - 1] if result.line <= len(lines) else ""
100
+ if not self.line_opens_a_comment(line):
101
+ kept.append(result)
102
+
103
+ return kept
69
104
 
70
105
  def search_in_codebase(
71
106
  self, string: str, *, regex: bool = False, flags: int = 0
@@ -7,6 +7,9 @@ from wexample_helpers.decorator.base_class import base_class
7
7
  from wexample_wex_addon_dev_php.workdir.php_workdir import PhpWorkdir
8
8
 
9
9
  if TYPE_CHECKING:
10
+ from collections.abc import Iterator
11
+ from pathlib import Path
12
+
10
13
  from wexample_config.const.types import DictConfig
11
14
  from wexample_config.options_provider.abstract_options_provider import (
12
15
  AbstractOptionsProvider,
@@ -19,6 +22,40 @@ if TYPE_CHECKING:
19
22
 
20
23
  @base_class
21
24
  class PhpSymfonyWorkdir(PhpWorkdir):
25
+ @staticmethod
26
+ def _create_install_nodes(leaves: dict[str, dict]) -> list[dict]:
27
+ from wexample_filestate.const.disk import DiskItemType
28
+
29
+ roots: list[dict] = []
30
+ directories: dict[str, dict] = {}
31
+
32
+ for destination in sorted(leaves):
33
+ *parents, filename = destination.split("/")
34
+ siblings = roots
35
+ prefix = ""
36
+
37
+ for part in parents:
38
+ prefix = f"{prefix}/{part}" if prefix else part
39
+ node = directories.get(prefix)
40
+ if node is None:
41
+ node = {
42
+ "name": part,
43
+ "type": DiskItemType.DIRECTORY,
44
+ "children": [],
45
+ }
46
+ directories[prefix] = node
47
+ siblings.append(node)
48
+ siblings = node["children"]
49
+
50
+ siblings.append(
51
+ {
52
+ "name": filename,
53
+ "type": DiskItemType.FILE,
54
+ **leaves[destination],
55
+ }
56
+ )
57
+
58
+ return roots
22
59
  def get_options_providers(self) -> list[type[AbstractOptionsProvider]]:
23
60
  from wexample_filestate_symfony.options_provider.symfony_options_provider import (
24
61
  SymfonyOptionsProvider,
@@ -129,6 +166,14 @@ class PhpSymfonyWorkdir(PhpWorkdir):
129
166
  src_excluded.append("Repository")
130
167
  src_dir["children"].append(repository_dir)
131
168
 
169
+ # Only the entity that asked for a provider of its own. `#[Searchable]`
170
+ # alone is served by the generic provider of wexample/symfony-search and
171
+ # needs no class, so this subtree stays empty in most applications.
172
+ search_dir = self._create_search_subtree(entity_dir)
173
+ if search_dir is not None:
174
+ src_excluded.append("Search")
175
+ src_dir["children"].append(search_dir)
176
+
132
177
  # Same reserve as the repository subtree: no entity asking for a form
133
178
  # means no src/Form and no src/Service/FormProcessor to bring into
134
179
  # being.
@@ -149,6 +194,9 @@ class PhpSymfonyWorkdir(PhpWorkdir):
149
194
  if services_subtree is not None:
150
195
  src_dir["children"].append(services_subtree)
151
196
 
197
+ # What packages ask for lands beside src/, not inside it.
198
+ raw_value["children"] += self._create_install_subtrees()
199
+
152
200
  # An app without src/Api is not an API app: nothing to scaffold, and
153
201
  # declaring the subtree would create the directories out of thin air.
154
202
  if (self.get_path() / "src" / "Api").is_dir():
@@ -560,6 +608,50 @@ class PhpSymfonyWorkdir(PhpWorkdir):
560
608
  ],
561
609
  }
562
610
 
611
+ def _create_install_subtrees(self) -> list[dict]:
612
+ """The nodes installed packages ask for through `extra.install`.
613
+
614
+ Three things a package may need from the application that hosts it: its
615
+ bundle listed in `config/bundles.php`, a configuration file dropped once
616
+ and then left to the reader, and a block kept up to date inside a file
617
+ the reader owns. Everything is declared in the package's composer.json,
618
+ so installing it is enough for `rectify` to know.
619
+ """
620
+ # An application file only: a bundle has no bundle list of its own.
621
+ if not (self.get_path() / "config" / "bundles.php").is_file():
622
+ return []
623
+
624
+ from wexample_filestate_symfony.option.symfony.bundles_registration_option import (
625
+ BundlesRegistrationOption,
626
+ )
627
+
628
+ leaves: dict[str, dict] = {
629
+ "config/bundles.php": {
630
+ "symfony": [BundlesRegistrationOption.get_name()],
631
+ }
632
+ }
633
+
634
+ for name, directory, install in self._declared_installs():
635
+ for source, destination in install.get("files", {}).items():
636
+ template = directory / source
637
+ if template.is_file():
638
+ leaves.setdefault(destination, {}).update(
639
+ {
640
+ "should_exist": True,
641
+ "default_content": template.read_text(),
642
+ }
643
+ )
644
+
645
+ for source, destination in install.get("blocks", {}).items():
646
+ template = directory / source
647
+ if template.is_file():
648
+ blocks = leaves.setdefault(destination, {}).setdefault(
649
+ "managed_blocks", {}
650
+ )
651
+ blocks[name] = template.read_text()
652
+
653
+ return self._create_install_nodes(leaves)
654
+
563
655
  def _create_php_file_children_filter(self) -> ChildrenFileFactoryOption:
564
656
  from wexample_filestate_symfony.option.symfony.entity_extend_option import (
565
657
  EntityExtendOption,
@@ -646,6 +738,73 @@ class PhpSymfonyWorkdir(PhpWorkdir):
646
738
  ],
647
739
  }
648
740
 
741
+ def _create_search_subtree(self, entity_dir: dict) -> dict | None:
742
+ """The providers written by hand for entities that reason.
743
+
744
+ `#[Searchable]` declares a field list and the generic provider searches
745
+ it — that is the ordinary case and it produces no file. `provider: true`
746
+ is the entity whose answer depends on who is asking, and that one needs
747
+ a class.
748
+ """
749
+ from wexample_filestate.const.disk import DiskItemType
750
+ from wexample_filestate.option.sidecar_of_option import SidecarOfOption
751
+ from wexample_filestate_symfony.file.symfony_entity_search_provider_file import (
752
+ SymfonyEntitySearchProviderFile,
753
+ )
754
+ from wexample_filestate_symfony.file.symfony_entity_search_scoring_file import (
755
+ SymfonyEntitySearchScoringFile,
756
+ )
757
+ from wexample_filestate_symfony.helper.entity import (
758
+ entity_attribute_flag,
759
+ entity_attribute_names_argument,
760
+ )
761
+
762
+ search_path = self.get_path() / "src" / "Search"
763
+
764
+ def _is_missing_provider(path) -> bool:
765
+ if not entity_attribute_flag(path, "Searchable", "provider"):
766
+ return False
767
+ # An existing provider is declared by the sweep beside; declaring it
768
+ # here too would build two items for the one path.
769
+ return not (search_path / f"{path.stem}SearchProvider.php").exists()
770
+
771
+ # `scoring: InvoiceSearchScoring::class` asks for a class of that name;
772
+ # the sidecar writes it under the name the convention gives, which is
773
+ # the one the attribute is expected to have used.
774
+ def _is_missing_scoring(path) -> bool:
775
+ if not entity_attribute_names_argument(path, "Searchable", "scoring"):
776
+ return False
777
+ return not (search_path / f"{path.stem}SearchScoring.php").exists()
778
+
779
+ entity_paths = list((self.get_path() / "src" / "Entity").glob("*.php"))
780
+ if (
781
+ not search_path.is_dir()
782
+ and not any(map(_is_missing_provider, entity_paths))
783
+ and not any(map(_is_missing_scoring, entity_paths))
784
+ ):
785
+ return None
786
+
787
+ return {
788
+ "name": "Search",
789
+ "type": DiskItemType.DIRECTORY,
790
+ "should_exist": True,
791
+ "children": [
792
+ self._create_php_file_children_filter(),
793
+ SidecarOfOption(
794
+ primary=entity_dir,
795
+ filename="{stem}SearchProvider.php",
796
+ cls=SymfonyEntitySearchProviderFile,
797
+ filter=_is_missing_provider,
798
+ ),
799
+ SidecarOfOption(
800
+ primary=entity_dir,
801
+ filename="{stem}SearchScoring.php",
802
+ cls=SymfonyEntitySearchScoringFile,
803
+ filter=_is_missing_scoring,
804
+ ),
805
+ ],
806
+ }
807
+
649
808
  def _create_services_subtree(self) -> dict | None:
650
809
  from wexample_filestate.const.disk import DiskItemType
651
810
  from wexample_filestate_symfony.option.symfony.services_resource_option import (
@@ -690,3 +849,11 @@ class PhpSymfonyWorkdir(PhpWorkdir):
690
849
  runner.workdir = runner.rebase_path(self.get_path())
691
850
 
692
851
  return runner
852
+
853
+ def _declared_installs(self) -> Iterator[tuple[str, Path, dict]]:
854
+ from wexample_filestate_symfony.helper.composer import installed_packages
855
+
856
+ for name, directory, manifest in installed_packages(self.get_path()):
857
+ install = manifest.get("extra", {}).get("install", {})
858
+ if install:
859
+ yield name, directory, install
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ # The line that set this off: an `@deprecated` note naming the class that replaces
6
+ # this one. Verbatim from symfony-helpers' SystemParameter.php.
7
+ DEPRECATION_NOTE = (
8
+ " * @deprecated use `Wexample\\SymfonyPlatform\\Entity\\Configuration`, which holds a"
9
+ )
10
+
11
+
12
+ def test_a_docblock_body_is_a_comment() -> None:
13
+ assert _line_opens_a_comment(DEPRECATION_NOTE)
14
+
15
+
16
+ def test_a_name_trailing_real_code_stays_reported() -> None:
17
+ """Documented limit: only the line's opening decides, so this is still an import."""
18
+ assert not _line_opens_a_comment(
19
+ "$c = new \\Wexample\\SymfonyPlatform\\Entity\\Configuration(); // built here"
20
+ )
21
+
22
+
23
+ @pytest.mark.parametrize(
24
+ "line",
25
+ [
26
+ "// use Wexample\\SymfonyPlatform\\Entity\\Configuration;",
27
+ "# Wexample\\SymfonyPlatform\\Entity\\Configuration",
28
+ "/* Wexample\\SymfonyPlatform\\Entity\\Configuration */",
29
+ " * @see Wexample\\SymfonyPlatform\\Entity\\Configuration",
30
+ ],
31
+ )
32
+ def test_every_comment_opener_is_recognised(line: str) -> None:
33
+ assert _line_opens_a_comment(line)
34
+
35
+
36
+ @pytest.mark.parametrize(
37
+ "line",
38
+ [
39
+ "use Wexample\\SymfonyPlatform\\Entity\\Configuration;",
40
+ " use Wexample\\SymfonyPlatform\\Entity\\Configuration;",
41
+ " $c = new \\Wexample\\SymfonyPlatform\\Entity\\Configuration();",
42
+ "class SystemParameter extends AbstractEntity",
43
+ ],
44
+ )
45
+ def test_real_code_is_not_a_comment(line: str) -> None:
46
+ assert not _line_opens_a_comment(line)
47
+
48
+
49
+ def _line_opens_a_comment(line: str) -> bool:
50
+ from wexample_wex_addon_dev_php.workdir.php_package_workdir import (
51
+ PhpPackageWorkdir,
52
+ )
53
+
54
+ return PhpPackageWorkdir.line_opens_a_comment(line)
@@ -1,4 +0,0 @@
1
- A Symfony entity must extend, directly or indirectly, the abstract class
2
- `Wexample\SymfonyHelpers\Entity\AbstractEntity`. It provides the `id` column, the
3
- `BaseEntityTrait` and the entity naming/path helpers the rest of the framework relies
4
- on — an entity that does not extend it is not wired into those conventions.