¡Bienvenidos al maravilloso mundo de Pokémon y PHP! En este artículo, desarrollaremos una Pokédex digital utilizando PHP 8.2, aplicando principios de Programación Orientada a Objetos (POO), siguiendo el estándar PSR-4 y buenas prácticas de desarrollo.
Puedes encontrar una copia de todo el código en mi repositorio personal de GitHub.
Introducción
La Pokédex es una enciclopedia electrónica que registra información sobre los Pokémon que un entrenador encuentra en su viaje. Nuestra meta es desarrollar una versión simplificada utilizando PHP y POO, incorporando conceptos avanzados como interfaces y polimorfismo.
Configuración del proyecto
Empezaremos definiendo nuestro composer.json
para manejar las dependencias y el autoloading según PSR-4.
{
"name": "rodmar/pokedex",
"description": "POO con Pokémon",
"type": "project",
"license": "MIT",
"autoload": {
"psr-4": {
"Rodmar\\Pokedex\\": "src/"
}
},
"authors": [
{
"name": "Rodmar Zavala",
"email": "[email protected]"
}
],
"require": {
"php": "^8.2",
"symfony/console": "^7.1",
"symfony/var-dumper": "^7.1"
},
"require-dev": {
"roave/security-advisories": "dev-latest",
"phpunit/phpunit": "^11.4"
}
}
Puedes ver que elegimos la versión 8.2 como requerimiento mínimo de nuestra app, también estamos instalando dos componentes de Symfony symfony/console
y symfony/var-dumper
.
Ejecutamos composer install
para preparar el autoloading.
Estructura del proyecto
Organizaremos nuestro código en carpetas dentro del directorio src/
:
```
data/ # json con el listado de pokémones
src/
Commands/ # Aplicaciones de línea de comando
Database/ # Clase de conexión con el json
Exceptions/ # Excepciones personalizadas
Models/ # Clases principales
Pokedex.php # Clase del Pokedex
Utils.php # Servicio
tests/ # Tests unitarios
vendor/ # Código de terceros
.gitignore # Directorio de archivos ignorados por git
composer.json # Recetas de composer
composer.lock # Versiones bloqueadas por composer
pokedex # CLI App
README.md
test.php
```
Creando biblioteca Pokémon
Creamos data/pokemon.json
[
{
"nombre": "Bulbasaur",
"tipo": "Planta",
"nivel": 5,
"evolucion": {
"nivel_minimo": 16,
"nombre_evolucion": "Ivysaur"
}
},
{
"nombre": "Charmander",
"tipo": "Fuego",
"nivel": 5,
"evolucion": {
"nivel_minimo": 16,
"nombre_evolucion": "Charmeleon"
}
},
{
"nombre": "Squirtle",
"tipo": "Agua",
"nivel": 5,
"evolucion": {
"nivel_minimo": 16,
"nombre_evolucion": "Wartortle"
}
},
{
"nombre": "Pikachu",
"tipo": "Eléctrico",
"nivel": 12,
"evolucion": {
"nivel_minimo": 16,
"nombre_evolucion": "Raichu"
}
},
{
"nombre": "Jigglypuff",
"tipo": "Normal",
"nivel": 8,
"evolucion": {
"nivel_minimo": 20,
"nombre_evolucion": "Wigglytuff"
}
},
{
"nombre": "Meowth",
"tipo": "Normal",
"nivel": 10,
"evolucion": {
"nivel_minimo": 28,
"nombre_evolucion": "Persian"
}
},
{
"nombre": "Psyduck",
"tipo": "Agua",
"nivel": 14,
"evolucion": {
"nivel_minimo": 33,
"nombre_evolucion": "Golduck"
}
},
{
"nombre": "Machop",
"tipo": "Lucha",
"nivel": 16,
"evolucion": {
"nivel_minimo": 28,
"nombre_evolucion": "Machoke"
}
},
{
"nombre": "Geodude",
"tipo": "Roca",
"nivel": 11,
"evolucion": {
"nivel_minimo": 25,
"nombre_evolucion": "Graveler"
}
},
{
"nombre": "Gastly",
"tipo": "Fantasma",
"nivel": 13,
"evolucion": {
"nivel_minimo": 25,
"nombre_evolucion": "Haunter"
}
},
{
"nombre": "Onix",
"tipo": "Roca",
"nivel": 20
},
{
"nombre": "Cubone",
"tipo": "Tierra",
"nivel": 18,
"evolucion": {
"nivel_minimo": 28,
"nombre_evolucion": "Marowak"
}
},
{
"nombre": "Koffing",
"tipo": "Veneno",
"nivel": 14,
"evolucion": {
"nivel_minimo": 35,
"nombre_evolucion": "Weezing"
}
},
{
"nombre": "Rhyhorn",
"tipo": "Tierra",
"nivel": 22,
"evolucion": {
"nivel_minimo": 42,
"nombre_evolucion": "Rhydon"
}
},
{
"nombre": "Horsea",
"tipo": "Agua",
"nivel": 17,
"evolucion": {
"nivel_minimo": 32,
"nombre_evolucion": "Seadra"
}
},
{
"nombre": "Magikarp",
"tipo": "Agua",
"nivel": 5,
"evolucion": {
"nivel_minimo": 20,
"nombre_evolucion": "Gyarados"
}
},
{
"nombre": "Eevee",
"tipo": "Normal",
"nivel": 20,
"evolucion": {
"nivel_minimo": 20,
"nombre_evolucion": "Vaporeon" // Suponiendo evolución a Vaporeon por simplificación
}
},
{
"nombre": "Snorlax",
"tipo": "Normal",
"nivel": 30
},
{
"nombre": "Dratini",
"tipo": "Dragón",
"nivel": 15,
"evolucion": {
"nivel_minimo": 30,
"nombre_evolucion": "Dragonair"
}
},
{
"nombre": "Mew",
"tipo": "Psíquico",
"nivel": 50
},
{
"nombre": "Mewtwo",
"tipo": "Psíquico",
"nivel": 70
}
]
Definiendo la clase base Pokemon
Creamos src/Models/Pokemon.php
:
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex\Models;
use Rodmar\Pokedex\Exceptions\EvolucionNoPosibleException;
class Pokemon
{
private string $nombre;
private string $tipo;
private int $nivel;
private ?array $evolucion;
public function __construct(
string $nombre,
string $tipo,
int $nivel = 1,
?array $evolucion = null
) {
$this->nombre = $nombre;
$this->tipo = $tipo;
$this->nivel = $nivel;
$this->evolucion = $evolucion;
}
final public function getNombre(): string
{
return $this->nombre;
}
final public function getTipo(): string
{
return $this->tipo;
}
final public function getNivel(): int
{
return $this->nivel;
}
final public function subirNivel(): void
{
$this->nivel++;
}
final public function puedeEvolucionar(): bool
{
if ($this->evolucion === null) {
return false;
}
return $this->nivel >= $this->evolucion['nivel_minimo'];
}
/**
* @throws EvolucionNoPosibleException
*/
final public function evolucionar(): void
{
if ($this->puedeEvolucionar()) {
$this->nombre = $this->evolucion['nombre_evolucion'];
$this->evolucion = null; // Asumimos que solo evoluciona una vez
echo "{$this->getNombre()} ha evolucionado!" . PHP_EOL;
} else {
throw new EvolucionNoPosibleException("El Pokémon {$this->getNombre()} no puede evolucionar.");
}
}
final public function getEvolucion(): ?array
{
return $this->evolucion;
}
}
Manejando excepciones personalizadas
Creamos src/Exceptions/PokemonNoEncontradoException.php
:
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex\Exceptions;
use Exception;
final class PokemonNoEncontradoException extends Exception
{
}
Creamos src/Exceptions/EvolucionNoPosibleException.php
:
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex\Exceptions;
use Exception;
final class EvolucionNoPosibleException extends Exception
{
}
Creamos conexión de base de datos con JSON
Creamos src/Database/JsonDatabase.php
:
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex\Database;
use JsonException;
use Rodmar\Pokedex\Exceptions\PokemonNoEncontradoException;
use RuntimeException;
final class JsonDatabase
{
private string $filePath;
private array $data = [];
public function __construct(string $filePath)
{
$this->filePath = $filePath;
$this->loadData();
}
private function loadData(): void
{
if (!file_exists($this->filePath)) {
throw new RuntimeException("El archivo {$this->filePath} no existe.");
}
$jsonContent = file_get_contents($this->filePath);
try {
$this->data = json_decode($jsonContent, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new RuntimeException("Error al parsear JSON: {$e->getMessage()}");
}
}
/**
* @throws PokemonNoEncontradoException
*/
public function getPokemonData(string $nombre): array
{
foreach ($this->data as $pokemonData) {
if (strcasecmp($pokemonData['nombre'], $nombre) === 0) {
return $pokemonData;
}
}
throw new PokemonNoEncontradoException("Pokémon {$nombre} no encontrado en la base de datos.");
}
/**
* @throws JsonException
*/
public function saveData(array $data): void
{
$jsonContent = json_encode($data, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
file_put_contents($this->filePath, $jsonContent);
}
public function getAllPokemon(): array
{
return $this->data;
}
}
Construyendo el sistema de búsqueda con Pokedex
Creamos src/Pokedex.php
:
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex;
use Rodmar\Pokedex\Models\Pokemon;
use Rodmar\Pokedex\Exceptions\PokemonNoEncontradoException;
use Rodmar\Pokedex\Database\JsonDatabase;
final class Pokedex
{
private array $listaPokemon = [];
private JsonDatabase $database;
public function __construct(JsonDatabase $database)
{
$this->database = $database;
$this->cargarPokemon();
}
private function cargarPokemon(): void
{
$pokemonDataList = $this->database->getAllPokemon();
foreach ($pokemonDataList as $pokemonData) {
$pokemon = new Pokemon(
$pokemonData['nombre'],
$pokemonData['tipo'],
$pokemonData['nivel'],
$pokemonData['evolucion'] ?? null
);
$this->agregarPokemon($pokemon);
}
}
public function agregarPokemon(Pokemon $pokemon): void
{
$this->listaPokemon[] = $pokemon;
}
public function buscarPokemonPorNombre(string $nombre): Pokemon
{
foreach ($this->listaPokemon as $pokemon) {
if (strcasecmp($pokemon->getNombre(), $nombre) === 0) {
return $pokemon;
}
}
throw new PokemonNoEncontradoException("Pokémon {$nombre} no encontrado en la Pokédex.");
}
public function guardarCambios(): void
{
$pokemonDataList = [];
foreach ($this->listaPokemon as $pokemon) {
$pokemonData = [
'nombre' => $pokemon->getNombre(),
'tipo' => $pokemon->getTipo(),
'nivel' => $pokemon->getNivel(),
];
if ($pokemon->getEvolucion()) {
$pokemonData['evolucion'] = $pokemon->getEvolucion();
}
$pokemonDataList[] = $pokemonData;
}
$this->database->saveData($pokemonDataList);
}
public function existePokemon(string $nombre): bool
{
foreach ($this->listaPokemon as $pokemon) {
if (strcasecmp($pokemon->getNombre(), $nombre) === 0) {
return true;
}
}
return false;
}
public function getListaPokemon(): array
{
return $this->listaPokemon;
}
}
Aplicando polimorfismo y manejo de excepciones
Creamos src/Utils.php
para funciones auxiliares:
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex;
use Rodmar\Pokedex\Models\Pokemon;
final class Utils
{
public static function mostrarInfo(Pokemon $pokemon): void
{
echo "Nombre: " . $pokemon->getNombre() . PHP_EOL;
echo "Tipo: " . $pokemon->getTipo() . PHP_EOL;
echo "Nivel: " . $pokemon->getNivel() . PHP_EOL;
if ($pokemon->puedeEvolucionar()) {
echo "Este Pokémon puede evolucionar." . PHP_EOL;
} else {
echo "Este Pokémon no puede evolucionar." . PHP_EOL;
}
}
}
Probando nuestra Pokédex
Creamos test.php
en el directorio raíz:
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Rodmar\Pokedex\Pokedex;
use Rodmar\Pokedex\Utils;
use Rodmar\Pokedex\Exceptions\PokemonNoEncontradoException;
use Rodmar\Pokedex\Database\JsonDatabase;
try {
$database = new JsonDatabase(__DIR__ . '/data/pokemon.json');
$pokedex = new Pokedex($database);
// Probamos con Bulbasaur
$bulbasaur = $pokedex->buscarPokemonPorNombre('Bulbasaur');
Utils::mostrarInfo($bulbasaur);
$bulbasaur->subirNivel();
$bulbasaur->subirNivel();
$bulbasaur->subirNivel();
$bulbasaur->evolucionar();
Utils::mostrarInfo($bulbasaur);
// Probamos con Magikarp
$magikarp = $pokedex->buscarPokemonPorNombre('Magikarp');
Utils::mostrarInfo($magikarp);
while ($magikarp->getNivel() < 20) {
$magikarp->subirNivel();
}
$magikarp->evolucionar();
Utils::mostrarInfo($magikarp);
// Probamos con Mewtwo (no tiene evolución)
$mewtwo = $pokedex->buscarPokemonPorNombre('Mewtwo');
Utils::mostrarInfo($mewtwo);
$mewtwo->evolucionar(); // No debería evolucionar
} catch (PokemonNoEncontradoException $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
} catch (\RuntimeException $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
} catch (\JsonException $e) {
echo 'Error al parsear JSON: ' . $e->getMessage() . PHP_EOL;
}
Al ejecutar php test.php
, veremos cómo Pikachu evoluciona a Raichu tras alcanzar el nivel necesario.
Implementando tests unitarios con PHPUnit
Creamos tests/PokedexTest.php
:
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Rodmar\Pokedex\Pokedex;
use Rodmar\Pokedex\Utils;
use Rodmar\Pokedex\Exceptions\PokemonNoEncontradoException;
use Rodmar\Pokedex\Database\JsonDatabase;
use Rodmar\Pokedex\Models\Pokemon;
final class PokedexTest extends TestCase
{
private Pokedex $pokedex;
protected function setUp(): void
{
$database = new JsonDatabase(__DIR__ . '/../data/pokemon.json');
$this->pokedex = new Pokedex($database);
}
public function testBuscarPokemonExistente(): void
{
$resultado = $this->pokedex->buscarPokemonPorNombre('Eevee');
$this->assertSame('Eevee', $resultado->getNombre());
}
public function testBuscarPokemonInexistente(): void
{
$this->expectException(PokemonNoEncontradoException::class);
$this->pokedex->buscarPokemonPorNombre('Pidgey');
}
public function testEvolucionarPokemonConEvolucion(): void
{
$magikarp = $this->pokedex->buscarPokemonPorNombre('Magikarp');
while ($magikarp->getNivel() < 20) {
$magikarp->subirNivel();
}
$this->assertTrue($magikarp->puedeEvolucionar());
$magikarp->evolucionar();
$this->assertSame('Gyarados', $magikarp->getNombre());
}
public function testEvolucionarPokemonSinEvolucion(): void
{
$onix = $this->pokedex->buscarPokemonPorNombre('Onix');
$this->assertFalse($onix->puedeEvolucionar());
$onix->evolucionar();
$this->assertSame('Onix', $onix->getNombre());
}
}
Para ejecutar los tests, añadimos PHPUnit como dependencia de desarrollo:
vendor/bin/phpunit tests
Muy bien, hasta ahora tenemos la estructura, los datos y nuestros tests, pero y ahora como disfrutamos de usar nuestra Pokedex?
Vamos a crear una aplicación por linea de comando, recuerdas la dependencia de symfony/console
? pues es ahora donde la emepezaremos a usar.
Creando controlador frontal de nuestra aplicación por línea de comando
Creamos en el root de nuestro proyecto el archivo pokedex
(correcto, no lleva ninguna extensión)
#!/usr/bin/env php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Rodmar\Pokedex\Commands\AgregarPokemonCommand;
use Rodmar\Pokedex\Commands\BuscarPokemonCommand;
use Rodmar\Pokedex\Commands\EvolucionarPokemonCommand;
use Rodmar\Pokedex\Commands\SubirNivelPokemonCommand;
use Symfony\Component\Console\Application;
use Rodmar\Pokedex\Commands\ListarPokemonCommand;
$application = new Application('Pokédex CLI', '1.0.0');
$application->add(new ListarPokemonCommand());
$application->add(new BuscarPokemonCommand());
$application->add(new EvolucionarPokemonCommand());
$application->add(new SubirNivelPokemonCommand());
$application->add(new AgregarPokemonCommand());
$application->run();
Y como puedes ver por este archivo, necesitaremos crear los comandos que se ejecutarán para ListarPokemon
, BuscarPokemon
, EvolucionarPokemon
, SubirNivelPokemon
y AgregarPokemon
ListarPokemonCommand
Creamos el archivo src/Commands/ListarPokemonCommand.php
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex\Commands;
use Rodmar\Pokedex\Pokedex;
use Rodmar\Pokedex\Database\JsonDatabase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
final class ListarPokemonCommand extends Command
{
protected function configure(): void
{
$this
->setName('listar')
->setDescription('Lista todos los Pokémon en la Pokédex');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
try {
$database = new JsonDatabase(__DIR__ . '/../../data/pokemon.json');
$pokedex = new Pokedex($database);
$listaPokemon = $pokedex->getListaPokemon();
if (empty($listaPokemon)) {
$io->warning('La Pokédex está vacía.');
return Command::SUCCESS;
}
$tableData = [];
foreach ($listaPokemon as $pokemon) {
$tableData[] = [
$pokemon->getNombre(),
$pokemon->getTipo(),
$pokemon->getNivel(),
];
}
$io->title('Lista de Pokémon en la Pokédex');
$io->table(['Nombre', 'Tipo', 'Nivel'], $tableData);
return Command::SUCCESS;
} catch (\Exception $e) {
$io->error('Error: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
BuscarPokemonCommand
Creamos el archivo src/Commands/BuscarPokemonCommand.php
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex\Commands;
use Rodmar\Pokedex\Pokedex;
use Rodmar\Pokedex\Database\JsonDatabase;
use Rodmar\Pokedex\Utils;
use Rodmar\Pokedex\Exceptions\PokemonNoEncontradoException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
final class BuscarPokemonCommand extends Command
{
protected function configure(): void
{
$this
->setName('buscar')
->setDescription('Busca un Pokémon por su nombre')
->addArgument('nombre', InputArgument::REQUIRED, 'Nombre del Pokémon a buscar');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$nombre = $input->getArgument('nombre');
try {
$database = new JsonDatabase(__DIR__ . '/../../data/pokemon.json');
$pokedex = new Pokedex($database);
$pokemon = $pokedex->buscarPokemonPorNombre($nombre);
$io->success("Pokémon encontrado: {$pokemon->getNombre()}");
$io->section('Información del Pokémon');
$io->listing([
"Nombre: " . $pokemon->getNombre(),
"Tipo: " . $pokemon->getTipo(),
"Nivel: " . $pokemon->getNivel(),
"Puede evolucionar: " . ($pokemon->puedeEvolucionar() ? 'Sí' : 'No'),
]);
return Command::SUCCESS;
} catch (PokemonNoEncontradoException $e) {
$io->error($e->getMessage());
return Command::FAILURE;
} catch (\Exception $e) {
$io->error('Error: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
EvolucionarPokemonCommand
Creamos el archivo src/Commands/EvolucionarPokemonCommand.php
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex\Commands;
use Rodmar\Pokedex\Pokedex;
use Rodmar\Pokedex\Database\JsonDatabase;
use Rodmar\Pokedex\Exceptions\PokemonNoEncontradoException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
final class EvolucionarPokemonCommand extends Command
{
protected function configure(): void
{
$this
->setName('evolucionar')
->setDescription('Evoluciona un Pokémon si es posible')
->addArgument('nombre', InputArgument::REQUIRED, 'Nombre del Pokémon a evolucionar');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$nombre = $input->getArgument('nombre');
try {
$database = new JsonDatabase(__DIR__ . '/../../data/pokemon.json');
$pokedex = new Pokedex($database);
$pokemon = $pokedex->buscarPokemonPorNombre($nombre);
if ($pokemon->puedeEvolucionar()) {
$pokemon->evolucionar();
$io->success("¡{$pokemon->getNombre()} ha evolucionado!");
} else {
$io->warning("{$pokemon->getNombre()} no puede evolucionar en este momento.");
}
$pokedex->guardarCambios();
return Command::SUCCESS;
} catch (PokemonNoEncontradoException $e) {
$io->error($e->getMessage());
return Command::FAILURE;
} catch (\Exception $e) {
$io->error('Error: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
SubirNivelPokemonCommand
Creamos el archivo src/Commands/SubirNivelPokemonCommand.php
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex\Commands;
use Rodmar\Pokedex\Pokedex;
use Rodmar\Pokedex\Database\JsonDatabase;
use Rodmar\Pokedex\Exceptions\PokemonNoEncontradoException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
final class SubirNivelPokemonCommand extends Command
{
protected function configure(): void
{
$this
->setName('subir-nivel')
->setDescription('Sube de nivel a un Pokémon')
->addArgument('nombre', InputArgument::REQUIRED, 'Nombre del Pokémon')
->addOption('niveles', 'l', InputOption::VALUE_OPTIONAL, 'Cantidad de niveles a subir', 1);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$nombre = $input->getArgument('nombre');
$niveles = (int) $input->getOption('niveles');
try {
$database = new JsonDatabase(__DIR__ . '/../../data/pokemon.json');
$pokedex = new Pokedex($database);
$pokemon = $pokedex->buscarPokemonPorNombre($nombre);
for ($i = 0; $i < $niveles; $i++) {
$pokemon->subirNivel();
}
$pokedex->guardarCambios();
$io->success("{$pokemon->getNombre()} ha subido {$niveles} nivel(es). Nivel actual: {$pokemon->getNivel()}");
return Command::SUCCESS;
} catch (PokemonNoEncontradoException $e) {
$io->error($e->getMessage());
return Command::FAILURE;
} catch (\Exception $e) {
$io->error('Error: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
AgregarPokemonCommand
Creamos el archivo src/Commands/AgregarPokemonCommand.php
<?php
declare(strict_types=1);
namespace Rodmar\Pokedex\Commands;
use Rodmar\Pokedex\Pokedex;
use Rodmar\Pokedex\Models\Pokemon;
use Rodmar\Pokedex\Database\JsonDatabase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
final class AgregarPokemonCommand extends Command
{
protected function configure(): void
{
$this
->setName('agregar')
->setDescription('Agrega un nuevo Pokémon a la Pokédex');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$helper = $this->getHelper('question');
try {
$database = new JsonDatabase(__DIR__ . '/../../data/pokemon.json');
$pokedex = new Pokedex($database);
// Solicitar datos al usuario
$io->title('Agregar un nuevo Pokémon a la Pokédex');
// Nombre
$nombreQuestion = new Question('Ingrese el nombre del Pokémon: ');
$nombre = $helper->ask($input, $output, $nombreQuestion);
// Verificar si el Pokémon ya existe
if ($pokedex->existePokemon($nombre)) {
$io->warning("El Pokémon {$nombre} ya existe en la Pokédex.");
return Command::SUCCESS;
}
// Tipo
$tipoQuestion = new Question('Ingrese el tipo del Pokémon: ');
$tipo = $helper->ask($input, $output, $tipoQuestion);
// Nivel
$nivelQuestion = new Question('Ingrese el nivel del Pokémon (por defecto 1): ', '1');
$nivel = (int) $helper->ask($input, $output, $nivelQuestion);
// Evolución
$evolucionRespuesta = $io->confirm('¿Este Pokémon tiene una evolución?', false);
$evolucion = null;
if ($evolucionRespuesta) {
$nombreEvolucionQuestion = new Question('Ingrese el nombre de la evolución: ');
$nombreEvolucion = $helper->ask($input, $output, $nombreEvolucionQuestion);
$nivelMinimoQuestion = new Question('Ingrese el nivel mínimo para evolucionar: ');
$nivelMinimo = (int) $helper->ask($input, $output, $nivelMinimoQuestion);
$evolucion = [
'nivel_minimo' => $nivelMinimo,
'nombre_evolucion' => $nombreEvolucion,
];
}
// Crear el nuevo Pokémon
$nuevoPokemon = new Pokemon($nombre, $tipo, $nivel, $evolucion);
// Agregar el Pokémon a la Pokédex y guardar cambios
$pokedex->agregarPokemon($nuevoPokemon);
$pokedex->guardarCambios();
$io->success("¡El Pokémon {$nombre} ha sido agregado a la Pokédex!");
return Command::SUCCESS;
} catch (\Exception $e) {
$io->error('Error: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
Ahora puedes usar la líena de comandos para interactuar con tu Pokédex de esta forma
php pokedex listar
php pokedex buscar Pikachu
También puedes probar los siguientes comandos:
# Subir de nivel nuestros pokémones
php pokedex subir-nivel Pikachu --niveles=2
# Cuando estén listos, poder evolucionarlos
php pokedex evolucionar Pikachu
# Podremos agregar nuevos pokemones a nuestro Pokédex
php pokedex agregar
Si has llegado hasta aquí, espero te hayas divertido y aprendido junto conmigo a usar POO, Excepciones personalizadas, Try.. Catch, Comandos y Tests
Recuerda que puedesencontrar una copia de todo el código en mi repositorio personal de GitHub.
También puedes dejar un Pull Request para mejorar la Pokedex y también tus comentarios al final de este Post.