Métodos privados e protegidos não deveriam ser acessados fora do escopo permitido de classe ou subclasses. Porém existem algumas situações onde isto é necessário, para mim em alguns contextos de testes automatizados (unitários ou de integração), ou em outros onde estou implementando uma funcionalidade e quero realizar execuções pontuais para verificar comportamentos.

Independente do por quê, e do direcional de boa prática no qual eu afirmaria, não chame métodos privados ou protegidos fora de seus escopos, várias linguagens fornecem suporte a esta possibilidade.

Em PHP e Java, por exemplo, podemos lançar mão de Reflections (rotinas que permitem explorar classes estruturalmente).

Exemplo de acesso à métodos protegidos em PHP#

Vamos verificar como executar um método protegido com visibilidade private ou protected na linguagem PHP (que pode ser usada em frameworks como Laravel e Symphony).

Nossa classe de exemplo é:

class Employee 
{
   private $salary = 1000;

   private function calcBonus() 
   {
      return $this->salary * 0.1;
   }
}

Uma classe Employee com um método privado capaz de calcular o valor do bônus do funcionário.

Usando Reflection com PHP#

Com Reflection vamos criar uma classe reflexo a partir da classe Employee para extrair o método privado calcBonus e executá-la na sequência:

$reflectionClass = new ReflectionClass(Employee::class);
$reflectionMethod = $reflectionClass->getMethod('calcBonus');
$reflectionMethod->setAccessible(true);
$reflectionMethod->invoke(new Employee()); // '100'

Esta estratégia funcionará com métodos private ou protected.

Podemos refatorar este código e acessar com reflection o método desejado sem precisar instanciar uma reflection da classe completa:

$reflectionMethod = new ReflectionMethod(Employee::class, 'calcBonus');
$reflectionMethod->setAccessible(true);
$reflectionMethod->invoke(new Employee()); // '100'

Caso precise passar argumentos para o método, ao invés de invoke utilize ReflectionMethod::invokeArgs.

Vamos refatorar nossa classe para que o método calcBonus receba o fator de cálculo:

class Employee 
{
   private $salary = 1000;

   private function calcBonus($factor) 
   {
      return $this->salary * $factor;
   }
}

Então podemos executar o método definindo os argumentos passando-os como um array, neste caso mesmo passando um único argumento deveremos enviar dentro do array:

$args = [0.1];
$reflectionMethod->invokeArgs(new Employee(), $args);

Helper para executar métodos protegidos#

Por fim, adicione esta função com um helper em sua caixa de ferramentas, facilitando a execução de métodos protegidos e privados em PHP no futuro:

/**
 * Calls object method with arguments.
 *
 * @param object $object
 * @param string $method
 * @param array $args
 * @return mixed
 */
function callProtectedMethod(object $object, string $method, array $args = [])
{
    $reflectionMethod = new ReflectionMethod(get_class($object), $method);
    $reflectionMethod->setAccessible(true);
    return $reflectionMethod->invokeArgs($object, $args);
}

Execute o helper como no exemplo a seguir:

callProtectedMethod(new Employee(), 'calcBonus'); // without arguments

//or

callProtectedMethod(new Employee(), 'calcBonus', [0.2]); // with arguments

Considerações finais#

Se você está testando criar testes para validar métodos privados em classes, é provável que tenha cometido algum engano na aquitetura deste código. Ainda assim, esta possibilidade de não é descartada em uma estratégia que uso no dia a dia: vou construindo métodos privados sendo executados por chamadas diretas para validar minha lógica em complexidades ainda não dominadas da regra de negócio. Ao fazer isso crio a possibilidade de acelerar o desenvolvimento, e posteriormente retirar este código teste do meu codebase final.

Pense na estratégia de uso e aplique sempre que o retorno for maior que o conceito puro da técnica.