{"id":15486782,"url":"https://github.com/conejerock/medium-fruit-bundle","last_synced_at":"2025-03-28T16:14:21.850Z","repository":{"id":217113860,"uuid":"743120452","full_name":"conejerock/medium-fruit-bundle","owner":"conejerock","description":"Como crear un bundle de Symfony desde cero añadiendo frutas a las HTTP Response","archived":false,"fork":false,"pushed_at":"2024-01-14T11:57:01.000Z","size":22,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-19T07:16:24.355Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/conejerock.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-14T11:54:51.000Z","updated_at":"2024-01-14T11:57:05.000Z","dependencies_parsed_at":null,"dependency_job_id":"32c5ffe9-a4fa-4956-ba24-fd286d0998e6","html_url":"https://github.com/conejerock/medium-fruit-bundle","commit_stats":{"total_commits":2,"total_committers":1,"mean_commits":2.0,"dds":0.0,"last_synced_commit":"a1be75ea5288d54733bd99c452768c00dc2ad607"},"previous_names":["conejerock/medium-fruit-bundle"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conejerock%2Fmedium-fruit-bundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conejerock%2Fmedium-fruit-bundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conejerock%2Fmedium-fruit-bundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conejerock%2Fmedium-fruit-bundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/conejerock","download_url":"https://codeload.github.com/conejerock/medium-fruit-bundle/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246059336,"owners_count":20717085,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T06:09:40.724Z","updated_at":"2025-03-28T16:14:21.810Z","avatar_url":"https://github.com/conejerock.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"Creando un bundle de Symfony desde Cero\n========\n\n\u003e Artículo completo en: https://medium.com/@conejerock/creando-un-bundle-de-symfony-desde-cero-5afa7669f830\n\nHace poco, trabajando en un proyecto que tiene dos instancias Symfony, se dio la necesidad crear un Bundle para tener aislada una funcionalidad y que fuera compartida por las dos instancias.\n\nAsí que me puse a leer [documentación oficial de Symfony](https://symfony.com/doc/current/bundles/best_practices.html) y me dispuse a montarlo. Como cometí algunos errores y algunos líos mentales mientras lo creaba, he aquí mi tutorial en Español a quien le pueda servir de ayuda.\n\nPara no liar la madeja, vamos a crear un Bundle que inserte en cada _Response HTTP,_ una cabecera con una **fruta aleatoria** (sí, ya sé que no es muy útil. Pero es lo primero que se me ha ocurrido). El Bundle se llamará **FruitBundle** (y sí, soy muy original)\n\nRequesitos\n==========\n\n*   [PHP](https://php.watch/articles/php-8.3-install-upgrade-on-fedora-rhel-el#quick-start) instalado (v8.3)\n*   [Composer](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-macos) instalado\n*   [Symfony CLI](https://symfony.com/download#step-1-install-symfony-cli) instalado\n\nCreando la estructura\n=====================\n\nVamos a suponer que tenemos el siguiente árbol de ficheros de nuestro proyecto Symfony:\n\n```\nbase  \n  ├── symfony-project  \n  │          ├── bin  \n  │          ├── composer.json  \n  │          ├── composer.lock  \n  │          ├── config  \n  │          ├── public  \n  │          ├── src  \n  │          ├── symfony.lock  \n  │          └── vendor  \n  └── symfony-project-2  \n             ├── bin  \n             ├── composer.json  \n             ├── composer.lock  \n             ├── config  \n             ├── public  \n             ├── src  \n             ├── symfony.lock  \n             └── vendor\n```\n\nEn mi caso, como quería que el Bundle fuera **compartido por ambos proyectos** (_symfony-project_ y _symfony-project-2_) cree el Bundle en un directorio paralelo a estos dos. No obstante, si tu Bundle sólo va a ser utilizado por un proyecto, puedes crearlo junto al resto de carpetas (_bin, config, public, src…_).\n\nSituándonos en la carpeta `base` haremos lo siguiente:\n\n```bash\nsymfony new --debug --php=8.3 FruitBundle\n```\n\nVale y dirás… ¡si esto crea otro proyecto de Symfony!  \n¡Exacto! Un Bundle no deja de ser un **miniproyecto** de Symfony que fusiona las funcionalidades del Bundle con el proyecto principal.\n\nAhora bien, se han creado algunas cosas que no nos interesan en el Bundle. Vamos a **borrar** aquello que no nos haga falta y a **cambiar la rama de Git**:\n\n```bash\ncd FruitBundle  \nrm -rf bin public config/\\* src/\\*  \ngit branch -M main\n```\n\nAhora mismo nuestra estructura de ficheros tendría que ser algo tal que así:\n\n```\nbase  \n  ├── symfony-project  \n  ├── symfony-project-2  \n  └── FruitBundle       \u003c---- Nuestro bundle  \n      ├── composer.json  \n      ├── composer.lock  \n      ├── config  \n      ├── src  \n      ├── symfony.lock  \n      ├── var  \n      └── vendor\n```\n\nAhora, crearemos el **archivo de entrada del Bundle**. Crearemos un archivo `FruitBundle.php`en la carpeta `src`. Ya lo configuraremos más adelante:\n\n```php\n// base/FruitBundle/src/FruitBundle.php  \n\u003c?php  \ndeclare(strict\\_types=1);  \n  \nnamespace BasketFruit\\FruitBundle;  \n  \nuse Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle;  \n  \nclass FruitBundle extends AbstractBundle  \n{  \n  \n}  \n\n```\n\nComo véis, he escogido `BasketFruit` como **namespace**, para poder seguir la convención [PSR-4](https://www.php-fig.org/psr/psr-4/) que es la que nos recomienda Symfony.\n\nY, finalmente, cambiaremos el `composer.json` del Bundle para, principalmente, dos cosas:\n\n*   Que configure nuestro **_miniproyecto_ como Bundle**(ahora mismo es un proyecto de Symfony normal).\n*   Que sepa como se **llaman los namespace y donde buscarlos.**\n\n```json5\n// base/FruitBundle/composer.json  \n\"type\": \"symfony-bundle\",  \n\"name\": \"basket-fruit/fruit-bundle\", // ¡¡Este nombre será el que use Composer para instalar el bundle!!  \n\"description\": \"Add random fruit to HTTP Response\",  \n\"version\": \"1.0.0\",  \n  \n// \"replace\": {  \n//    \"symfony/polyfill-php72\": \"\\*\",  \n// ...  \n// },  \n  \n// ...  \n  \n\"autoload\": {  \n    \"psr-4\": {  \n        \"BasketFruit\\\\FruitBundle\\\\|\": \"src/\"  \n    }  \n},  \n\"autoload-dev\": {  \n    \"psr-4\": {  \n        \"BasketFruit\\\\Tests\\\\FruitBundle\\\": \"tests/\"  \n    }  \n}\n```\n\nLe he dado el nombre de `basket-fruit/fruit-bundle` al módulo. Este nombre será el que utilice **Composer** para instalarlo.\n\n**OJO: Elimina el atributo** `**replace**`**de** `**composer.json**` **o no te dejará instalar el bundle en el proyecto principal**\n\n\u003e Yeah! Bundle creado\n\nConfigurando el proyecto principal\n==================================\n\nCon nuestro Bundle creado, sólo nos queda instalarlo en nuestro proyecto principal.\n\nPuesto que Composer busca los paquetes para instalar en [https://packagist.org/](https://packagist.org/) (y nuestro Bundle aún no está publicado), debemos indicar que además busque en otro sitio (concretamente en el directorio `/FruitBundle`)  \nEn mi caso, en `composer.json`del proyecto `symfony-project` incluiremos lo siguiente:\n\n```json5\n// base/symfony-project/composer.json  \n\"autoload\": {  \n    \"psr-4\": {  \n        \"App\\\\\": \"src/\", //Tu aplicación principal  \n        \"BasketFruit\\\\FruitBundle\\\\\": \"../FruitBundle/src/\"  \n    }  \n},  \n\"repositories\": [  \n    {  \n      \"type\": \"path\",  \n      \"url\": \"../FruitBundle\",  \n      \"canonical\": true  \n    }  \n  ]\n```\n\nUna vez hecho esto, procedemos a instalar nuestro Bundle:\n\n```bash\ncd ../symfony-project  \ncomposer require basket-fruit/fruit-bundle:1.0.0\n```\n\nUna vez instalado, añadimos el Bundle a nuestra lista de Bundles en `symfony-project/config/bundles.php` :\n\n```php\n//config/bundles.php  \n\u003c?php  \n  \nreturn [  \n    Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle::class =\u003e ['all' =\u003e true],  \n    Symfony\\Bundle\\TwigBundle\\TwigBundle::class =\u003e ['all' =\u003e true],  \n    Symfony\\Bundle\\WebProfilerBundle\\WebProfilerBundle::class =\u003e ['dev' =\u003e true, 'test' =\u003e true],  \n    Symfony\\Bundle\\MakerBundle\\MakerBundle::class =\u003e ['dev' =\u003e true],  \n    // nuestro FruitBundle  \n    BasketFruit\\FruitBundle::class =\u003e ['all' =\u003e true],  \n];\n```\n\nAhora sí, nuestro bundle está integrado con el proyecto principal de Symfony. Si ejecutamos el siguiente comando en la consola de nuestro proyecto principal:\n\n```bash\n./bin/console config:dump-reference \n```\n\nVeremos lo siguiente (te saldrán otros Bundles si ya lo tienes intalados):\n\n```\n ------------------- -----------------   \n  Bundle name         Extension alias    \n ------------------- -----------------   \n  FrameworkBundle     framework          \n  FruitBundle         fruit            \u003c-- Nuestro Bundle integrado  \n  MakerBundle         maker              \n  TwigBundle          twig               \n  WebProfilerBundle   web\\_profiler       \n ------------------- -----------------\n```\n\nAñadiendo frutas a las respuestas\n=================================\n\nVamos al turrón. Hemos dicho que queremos añadir una fruta aleatoria como cabecera a las _Responses HTTP_ que ofrece Symfony. Vamos allá:\n\nAñadiendo listeners\n-------------------\n\nPara añadir esta simple cabecera, bastará con crear un listener que escuche el evento Response, y añadirle la cabecera antes de enviarla.\n\nSegún la documentación oficial, para seguir una buena estructura del Bundle, los _listeners_ deben ir en la carpeta `src/EventListener` de nuestro bundle.\n\nAsí bien, creamos la siguiente estructura de carpetas dentro de **FruitBundle**:\n\n```\nFruitBundle  \n      ├── src  \n      │   ├── EventListener \u003c----------------------- NEW  \n      │   │   └── AddFruitResponseListener.php  \n      │   └── FruitBundle.php  \n      └── tests\n```\n\nEl contenido del Listener es muy simple:\n\n```php\n// ./FruitBundle/src/EventListener/AddFruitResponseListener.php  \n\u003c?php  \ndeclare(strict\\_types=1);  \n  \nnamespace BasketFruit\\FruitBundle\\EventListener;  \n  \nuse Symfony\\Component\\HttpKernel\\Event\\ResponseEvent;  \n  \nclass AddFruitResponseListener  \n{  \n    const HEADER_KEY = 'X-Random-Fruit';  \n    const FRUITS = [  \n        \"apple\", \"banana\", \"orange\",  \n        \"grapes\", \"strawberry\", \"watermelon\",  \n        \"pineapple\", \"mango\", \"blueberry\"  \n    ];  \n  \n    public function __invoke(ResponseEvent $event): void  \n    {  \n        $response = $event-\u003egetResponse();  \n        $response-\u003eheaders-\u003eset(self::HEADER_KEY, self::FRUITS[array_rand(self::FRUITS)]);  \n    }  \n}\n```\n\nConfigurando el listener\n------------------------\n\nPara que Symfony fusione nuestro listener del bundle con el proyecto principal, hay que indicarle que nuestra clase es un listener y que se lanza con el evento _ResponseEvent (valga la redundancia):_\n\nAsí, crearemos el archivo `FruitBundle/config/services.yaml` con el siguiente contenido:\n\n```yaml\n# ./FruitBundle/config/services.yaml  \nservices:  \n  BasketFruit\\FruitBundle\\EventListener\\AddFruitResponseListener:  \n    tags:  \n      - { name: kernel.event\\_listener, event: kernel.response }\n```\n\nFinalmente, esta configuración se tiene que cargar desde algún sitio. ¿Recordáis nuestra clase `FruitBundle.php`? Es la entrada principal del Bundle. En esa misma clase crearemos la función `loadExtension` que hereda de AbstractBundle, y es la encargada de cargar el archivo de configuración de nuestro bundle.\n\n```php\n// FruitBundle/src/FruitBundle.php  \n\u003c?php  \ndeclare(strict\\_types=1);  \n  \nnamespace BasketFruit\\FruitBundle;  \n  \nuse Symfony\\Component\\DependencyInjection\\ContainerBuilder;  \nuse Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator;  \nuse Symfony\\Component\\HttpKernel\\Bundle\\AbstractBundle;  \n  \nclass FruitBundle extends AbstractBundle  \n{  \n    public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void  \n    {  \n        $container-\u003eimport('../config/services.yaml');  \n    }  \n  \n}\n```\n\n¡Listo! Ya deberíamos tener nuestro Bundle, añadiendo una cabecera `X-Random-Fruit` a las respuestas de nuestras peticiones al proyecto principal.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconejerock%2Fmedium-fruit-bundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconejerock%2Fmedium-fruit-bundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconejerock%2Fmedium-fruit-bundle/lists"}