Como substituir a função de característica e chamá-la da função substituída?

370

Cenário:

trait A {
    function calc($v) {
        return $v+1;
    }
}

class MyClass {
    use A;

    function calc($v) {
        $v++;
        return A::calc($v);
    }
}

print (new MyClass())->calc(2); // should print 4

Esse código não funciona e não consigo encontrar uma maneira de chamar uma função de característica como se fosse herdada. Tentei ligar self::calc($v), static::calc($v), parent::calc($v), A::calc($v)e o seguinte:

trait A {
    function calc($v) {
        return $v+1;
    }
}

class MyClass {
    use A {
        calc as traitcalc;
    }

    function calc($v) {
        $v++;
        return traitcalc($v);
    }
}

Nada funciona.

Existe uma maneira de fazê-lo funcionar ou devo substituir completamente a função de característica que é muito mais complexa do que isso :)

Shu
fonte

Respostas:

641

Seu último estava quase lá:

trait A {
    function calc($v) {
        return $v+1;
    }
}

class MyClass {
    use A {
        calc as protected traitcalc;
    }

    function calc($v) {
        $v++;
        return $this->traitcalc($v);
    }
}

A característica não é uma classe. Você não pode acessar seus membros diretamente. É basicamente apenas copiar e colar automaticamente ...

ircmaxell
fonte
20
apenas para esclarecer - uma vez que sua classe define o mesmo método, ela substitui automaticamente as características. A característica preenche o método que o @ircmaxell menciona quando está vazio.
Yehosef
2
@PhillipWhelan seria bom se você pudesse adicionar mais informações sobre o que "não funciona conforme o esperado". Escrito assim, não ajuda muito a entender que tipo de comportamento errado é esperado e não garante que esse não seja um erro temporário da sua parte. Talvez haja alguma pergunta sobre o assunto que você está falando? (Eventualmente) Obrigado.
Kamafeather
11
O problema é que todos os outros métodos na característica não são mais incluídos.
malhal
2
Apenas para referência: Se a sua função característica seria estático você poderia acessar a função chamandoA::calc(1)
velop
4
Como Phillip mencionou (eu acho), como você faria isso por um método de uma característica enquanto ainda incluía todos os outros métodos da mesma característica como o normal? De preferência, sem referenciar explicitamente cada método.
Gannet
14

Se a classe implementar o método diretamente, não usará a versão de características. Talvez o que você está pensando é:

trait A {
    function calc($v) {
        return $v+1;
    }
}

class MyClass {
    function calc($v) {
        return $v+2;
    }
}

class MyChildClass extends MyClass{
}

class MyTraitChildClass extends MyClass{
    use A;
}

print (new MyChildClass())->calc(2); // will print 4

print (new MyTraitChildClass())->calc(2); // will print 3

Como as classes filho não implementam o método diretamente, elas primeiro usarão a característica, caso contrário, a classe pai.

Se você quiser, a característica pode usar o método na classe pai (supondo que você saiba que o método estaria lá), por exemplo

trait A {
    function calc($v) {
        return parent::calc($v*3);
    }
}
// .... other code from above
print (new MyTraitChildClass())->calc(2); // will print 8 (2*3 + 2)

Você também pode fornecer maneiras de substituir, mas ainda acessar o método de característica da seguinte maneira:

trait A {
    function trait_calc($v) {
        return $v*3;
    }
}

class MyClass {
    function calc($v) {
        return $v+2;
    }
}


class MyTraitChildClass extends MyClass{
    use A {
      A::trait_calc as calc;
    }
}


class MySecondTraitChildClass extends MyClass{
    use A {
      A::trait_calc as calc;
    }

    public function calc($v) {
      return $this->trait_calc($v)+.5;
    }
}


print (new MyTraitChildClass())->calc(2); // will print 6
echo "\n";
print (new MySecondTraitChildClass())->calc(2); // will print 6.5

Você pode vê-lo funcionar em http://sandbox.onlinephpfunctions.com/code/e53f6e8f9834aea5e038aec4766ac7e1c19cc2b5

Yehosef
fonte
8

Uma abordagem alternativa, se estiver interessado - com uma classe intermediária extra para usar a via OOO normal. Isso simplifica o uso com parent :: methodname

trait A {
    function calc($v) {
        return $v+1;
    }
}

// an intermediate class that just uses the trait
class IntClass {
    use A;
}

// an extended class from IntClass
class MyClass extends IntClass {
    function calc($v) {
        $v++;
        return parent::calc($v);
    }
}
Kartik V
fonte
6
Essa abordagem reduzirá qualquer vantagem que você tenha usando traits. Como combinação de múltiplos traços em várias classes (por exemplo característica A, B, em uma classe, traço B, C, D na outra classe, característica A, C em outra classe e assim por diante)
Ionuţ Staicu
3
Não, usando essa abordagem, você ainda tem as vantagens de ter uma característica. Você pode usar esse atributo no IntClass, mas também pode usá-lo em muitas outras classes, se desejar. A característica será inútil, se for usada apenas no IntClass. Nesse caso, seria melhor colocar o método calc () diretamente nessa classe.
Marcini
Isso totalmente não funcionaria para mim. ScreenablePerson::save()existe, Candidateusa Validatingcaracterística e se estende ScreenablePerson, e todas as três classes têm save().
Theodore R. Smith
1

Usando outra característica:

trait ATrait {
    function calc($v) {
        return $v+1;
    }
}

class A {
    use ATrait;
}

trait BTrait {
    function calc($v) {
        $v++;
        return parent::calc($v);
    }
}

class B extends A {
    use BTrait;
}

print (new B())->calc(2); // should print 4
Tarkhov
fonte