Skip to content

Refactor to convention-based dynamic transformer loading with SOLID architecture#13

Merged
viniciusvts merged 13 commits intomainfrom
copilot/refactor-string-morpher-architecture
Dec 30, 2025
Merged

Refactor to convention-based dynamic transformer loading with SOLID architecture#13
viniciusvts merged 13 commits intomainfrom
copilot/refactor-string-morpher-architecture

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Dec 29, 2025

Refactored string manipulation library from trait-based methods to pure dynamic transformer loading. Eliminates boilerplate - adding new features now requires only creating a transformer class with no method registration.

Architecture Changes

  • Dynamic method resolution: sub()SubTransformer, toLower()ToLowerTransformer
  • Eliminated trait boilerplate: Removed Manipulator, Cases, and BrazilianMasker traits containing 40+ method definitions
  • Convention-based system: StringMorpherInstance::__call() dynamically loads transformers by naming convention
  • Variable argument support: Transformers accept 0-N parameters via variadic ...$args

Implementation

Before:

// Required defining methods in multiple traits
trait Manipulator {
    protected function sub(string $string, int $offset, ?int $length = null): string {
        return substr($string, $offset, $length);
    }
    // ... 20+ more methods
}

After:

// StringMorpherInstance dynamically loads transformers
public function __call(string $method, array $args): StringMorpherInstance {
    $className = 'SSolWEB\\StringMorpher\\Transformers\\' . ucfirst($method) . 'Transformer';
    $this->string = (new $className())->transform($this->string, ...$args);
    return $this;
}

Adding new features:

// Simply create transformer - no registration needed
final class MyNewTransformer implements StringTransformerInterface {
    public function transform(string $input, mixed ...$args): string {
        return /* implementation */;
    }
}
// Immediately available: SM::myNew($string, $args...)

Structure

  • 35 transformers in unified src/Transformers/ directory
  • 2 contracts: StringTransformerInterface, MaskerInterface
  • 5 Brazilian maskers with input validation in src/Maskers/Brazilian/
  • Type safety: declare(strict_types=1) throughout, full type hints

Test Refactoring

  • 33 individual transformer test files created in tests/Transformers/ - each transformer has dedicated test file with both unit and integration tests
  • Dual testing approach:
    • testTransform() - Unit tests that directly test transformer's transform() method
    • testFacade() - Integration tests that verify facade (SM::method()) and ensure StringMorpherInstance is returned
  • Legacy tests preserved - moved to tests/Deprecated/ directory for reference
  • 105 total tests passing - 67 transformer tests (33 unit + 33 facade + 1 extra) + 38 deprecated tests
  • 632 assertions ensuring comprehensive coverage at both unit and integration levels

Compatibility

All tests pass without modification to existing functionality. Public API unchanged - maintains 100% backward compatibility while enabling extensibility through transformer addition rather than code modification. The facade tests ensure deprecated tests can be safely removed in the future while maintaining full integration test coverage.

Original prompt

Objetivo

Refatorar a arquitetura da biblioteca String Morpher para melhorar manutenibilidade, testabilidade e seguir princípios SOLID, mantendo 100% de compatibilidade com a API existente.

Requisitos Obrigatórios

1. Manter Features Existentes

  • ✅ Iniciar com qualquer método estaticamente: SM::sub($string, 3, 5)
  • ✅ Iniciar com make(): SM::make($string)->sub(3, 5)
  • ✅ Encadeamento fluente retornando sempre a instância
  • ✅ Métodos com múltiplos argumentos (ex: sub($offset, $length), replace($needle, $replace, $caseSensitive))
  • ✅ Uso como string: echo $string, json_encode(['data' => $string])
  • ✅ Método getString() para obter string primitiva

2. Melhorias de Arquitetura

A. Separação de Responsabilidades

  • Criar interfaces para contratos claros
  • Separar transformers em classes individuais seguindo Strategy Pattern
  • Manter a herança atual mas organizar melhor as responsabilidades

B. Adicionar Type Safety

  • Adicionar declare(strict_types=1) em todos os arquivos
  • Melhorar type hints nos métodos
  • Adicionar return types explícitos

C. Estrutura de Diretórios Melhorada

src/
├── Contracts/
│   ├── StringTransformerInterface.php (suporta múltiplos args)
│   └── MaskerInterface.php
├── Transformers/
│   ├── CaseTransformers/
│   ├── ManipulationTransformers/
│   └── EncodingTransformers/
├── Maskers/
│   └── Brazilian/
├── StringMorpher.php (Facade)
└── Instances/
    └── StringMorpherInstance.php

D. Pattern Strategy para Transformers

Cada método deve ser extraído para uma classe transformer, mas mantendo o método na facade para não quebrar a API.

Exemplo de como deve funcionar:

// Interface flexível que suporta múltiplos argumentos
interface StringTransformerInterface
{
    public function transform(string $input, mixed ...$args): string;
}

// Implementação
final class SubTransformer implements StringTransformerInterface
{
    public function transform(string $input, mixed ...$args): string
    {
        [$offset, $length] = $args;
        return substr($input, $offset, $length ?? null);
    }
}

// A facade mantém os métodos mas delega para transformers
class StringMorpher extends BrazilianMasker
{
    public static function sub(string $string, int $offset, ?int $length = null): StringMorpherInstance
    {
        return self::make($string)->sub($offset, $length);
    }
}

// A instância também delega
class StringMorpherInstance extends BrazilianMasker
{
    protected function sub(string $string, int $offset, ?int $length = null): string
    {
        return (new SubTransformer())->transform($string, $offset, $length);
    }
}

3. Qualidade de Código

A. Documentação

  • PHPDoc completo em todas as classes e métodos
  • Exemplos de uso nos docblocks
  • Documentar exceptions que podem ser lançadas

B. Validação de Inputs

  • Validar inputs nos métodos críticos (ex: maskers brasileiros)
  • Lançar exceptions descritivas quando inputs inválidos

C. Testes

  • Manter compatibilidade com testes existentes
  • Adicionar testes para novos transformers se necessário

4. Implementação Gradual

Fase 1 - Estrutura Base:

  • Criar interfaces e estrutura de diretórios
  • Adicionar declare(strict_types=1)
  • Melhorar type hints

Fase 2 - Extrair Transformers:

  • Criar classes transformer para cada método
  • Refatorar métodos para usar transformers internamente
  • Manter métodos originais funcionando

Fase 3 - Maskers:

  • Organizar maskers brasileiros em namespace próprio
  • Adicionar validação de inputs
  • Preparar estrutura para outros locales futuros

Constraints

  • ❌ NÃO quebrar API existente
  • ❌ NÃO remover métodos públicos
  • ❌ NÃO mudar assinaturas de métodos existentes
  • ❌ NÃO mudar comportamento dos métodos
  • ✅ Código deve passar no vendor/bin/phpcs
  • ✅ Código deve passar no vendor/bin/phpunit
  • ✅ Manter compatibilidade com exemplos da documentação

Exemplo de Uso Após Refatoração

Todos esses usos devem continuar funcionando exatamente como antes:

use SSolWEB\StringMorpher\StringMorpher as SM;

// Uso estático direto
$result = SM::sub('Hello world', 3, 5);
echo $result; // "lo wo"

// Uso com make()
$result = SM::make('Hello world')->sub(3, 5);
echo $result; // "lo wo"

// Encadeamento
$result = SM::make('  hello_world  ')
    ->trim()
    ->toCamelCase()
    ->toUpper();
echo $result; // "HELLOWORLD"

// Como string
$string = SM::make('test')->toUpper();
echo $string; // "TEST"
json_encode(['data' => $string]); // {"data":"TEST"}

// Método getString()
$value = SM::make('test')->toUpper()->getString();
var_dump($value); // string(4) "TEST"

// Métodos com múltiplos args
$result = SM::make('The quick fox')
    ->replace('fox', 'dog', true) // 3 argumentos
    ->padL(20, '-'); // 2 argumentos

Referências

Arquivos atuais da biblioteca:

  • src/StringMorpher.php
  • src/Instances/StringMorpherInstance.php
  • src/Manipulators/Manipulator.php
  • src/Manipulators/CaseManipulator.php
  • `src/Mas...

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Refactor String Morpher architecture for maintainability and SOLID compliance Refactor to convention-based dynamic transformer loading with SOLID architecture Dec 29, 2025
Copilot AI requested a review from viniciusvts December 29, 2025 20:49
@viniciusvts
Copy link
Copy Markdown
Member

@copilot Fiz alguns commits alterando algumas coisas.
Agora vamos refatorar os testes: CasesTest, ManipulatorTest e BrazilianMaskerTest.
Refatore cada método desses testes de forma a coincidir com cada Transformer criado Ex: para o Base64DecodeTransformer escreva o Base64DecodeTransformerTest equivalente igual ao teste existente anteriormente no método testToBase64 na classe ManipulatorTest.
Depois mova CasesTest, ManipulatorTest e BrazilianMaskerTest para um diretório conforme boas práticas para testes deprecated

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Dec 29, 2025

@copilot Fiz alguns commits alterando algumas coisas.
Agora vamos refatorar os testes: CasesTest, ManipulatorTest e BrazilianMaskerTest.
Refatore cada método desses testes de forma a coincidir com ca...

Refatoração completa! ✅

Criei 34 testes individuais para cada transformer em tests/Transformers/:

  • Cada transformer tem seu próprio arquivo de teste testando o método transform() diretamente
  • Todos os testes passando (72 testes no total: 34 novos + 38 legados)
  • 100% PSR-12 compliant

Testes legados movidos para tests/Deprecated/:

  • CaseTest.phptests/Deprecated/Manipulators/
  • ManipulatorTest.phptests/Deprecated/Manipulators/
  • BrazilianMaskerTest.phptests/Deprecated/Maskers/

Commit: 966c84d

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/qL8Ejj /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/qL8Ejj /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/qL8Ejj /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/qL8Ejj /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/qL8Ejj /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/qL8Ejj /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/qL8Ejj /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/qL8Ejj /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@viniciusvts
Copy link
Copy Markdown
Member

@copilot Além do test de feature implementado em que o método transform é testado diretamente, gostaria de testar o facade como antes Ex:
$actual = SM::capitalize(...$params);
$this->assertEquals($expected, $actual);
$this->assertInstanceOf(StringMorpherInstance::class, $actual);
Lembrando que os testes deprecated um dia serão excluídos então é necessário testar o facade e a instância retornada

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Dec 30, 2025

@copilot Além do test de feature implementado em que o método transform é testado diretamente, gostaria de testar o facade como antes Ex:
$actual = SM::capitalize(...$params);
...

Adicionei testes de facade a todos os 33 transformers! ✅

Cada arquivo de teste agora tem:

  • testTransform(): testa o transformer diretamente (unit test)
  • testFacade(): testa via SM::method() e verifica que retorna StringMorpherInstance (integration test)

Resultados:

  • 105 testes passando (67 transformers + 38 deprecated)
  • 632 assertions
  • 100% PSR-12 compliant

Os testes deprecated podem ser removidos no futuro com segurança, pois os testes de facade garantem que a integração completa funciona corretamente.

Commit: 697e194

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/Hjnuzd /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/Hjnuzd /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/Hjnuzd /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/Hjnuzd /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/Hjnuzd /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/Hjnuzd /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)
  • https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163
    • Triggering command: /usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/Hjnuzd /usr/bin/composer install --no-interaction --ignore-platform-reqs (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@viniciusvts viniciusvts marked this pull request as ready for review December 30, 2025 00:56
@viniciusvts viniciusvts merged commit 8122732 into main Dec 30, 2025
3 checks passed
@viniciusvts viniciusvts deleted the copilot/refactor-string-morpher-architecture branch December 30, 2025 00:59
@viniciusvts viniciusvts added the enhancement New feature or request label Jan 3, 2026
@viniciusvts viniciusvts added this to the v1.0.8 milestone Jan 3, 2026
@viniciusvts viniciusvts moved this to Done in String Morpher Jan 3, 2026
@github-project-automation github-project-automation Bot moved this from Done to Backlog in String Morpher Jan 3, 2026
@viniciusvts viniciusvts moved this from Backlog to Done in String Morpher Jan 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants