0

I've been trying to understand how static scope works in the context of a trait. A great explanation is here: https://stackoverflow.com/a/56935557/2137316 but it doesn't quite address my concern.

I'm attempting to create a trait for unit tests possessing a property that more or less serves as global flag. The goal is for it to prevent unnecessary reruns of its behavior if a previous test in a given run has already triggered it.

trait CreateDatabase
{
    protected static bool $hasRun = false;  //We don't want to waste time rebuilding the database in each test

    protected runDb(): void
    {
        if (!self::$hasRun) {
            //Do stuff...
        }
        
        self::$hasRun = true;
    }
}
class SomeTestClass
{
    use CreateDatabase;

    /**
     * @test
     **/
    public function canRunSomeTest()
    {
        $this->runDb();

        //Test stuff...
    }
}

The related post talks about trait context and would seem to suggest that self in this context refers to the 'context' of SomeTestClass, meaning SomeOtherTestClass would have no awareness that the database has already been created. The conclusion would then seem to be that the way to achieve the effect I'm going for would be for is to replace

self::$hasRun = true;

with

CreateDatabase::$hasRun = true;

Even though that line is being executed within the trait explicitly being referenced. The purpose being, to talk to the more-global trait context rather than that of the class using it.

The problem is, Php8, via my IDE, is fussing at me over that decision:

Calling static trait member directly is deprecated. It should only be accessed on a class using the trait.

Usually when my tooling resists me, it means there's something flawed about the overall approach, but I'm not seeing it yet. Wondering if anyone has any insight.

kmuenkel
  • 2,659
  • 1
  • 19
  • 20
  • Just name the trait `CreateDatabaseTrait` and use this. But in `runDb()` call the real class `CreateDatabase::runDb()` and do the real database stuff with static flag there. So in the end the Trait is just a proxy. And the real database class is not mixed up with the current class that uses the trait. And the static variable problem is gone. – Foobar Dec 20 '22 at 16:20
  • Short answer: dont use either of them and learn OOP instead. – tereško Jul 13 '23 at 19:27
  • @tereško, what specific OOP design pattern would you suggest instead? I'm thinking Static Class. It's got to be a compositional relationship, because classic inheritance wouldn't be appropriate as it would, in spirit, be an Interface Segregation violation. A Dependency Injection like the Strategy Pattern isn't an option because I don't control the caller. Singleton could work, and if I remember right, that's what I went with a year ago. But the goal at the time was to basically extend on an existing trait and learn what makes them tick. So what facet of OOP would you suggest as a solution? – kmuenkel Jul 15 '23 at 06:16
  • It's hard to tell from your description, but I think your **real** problem comes the original code not sticking to OOP principles. You should be able to isolate the class-under-test from the DB access, if your code had proper separation. And you are currently stuck attempting to "do something with database". You should look at fixing the original code instead of faking the tests ;) – tereško Jul 15 '23 at 22:32

1 Answers1

1

Use trait as proxy here:

class CreateDatabase
{
    protected static bool $hasRun = false;  

    public static function runDb(): void
    {
        if (!self::$hasRun) {
            //Do stuff...
        }
        
        self::$hasRun = true;
    }
}

trait CreateDatabaseTrait
{
     public static function runDb(): void {
         CreateDatabaseTrait::runDb();
     }
}

class SomeTestClass
{
    use CreateDatabaseTrait;

    /**
     * @test
     **/
    public function canRunSomeTest()
    {
        self::runDb();

        //Test stuff...
    }
}
Foobar
  • 769
  • 1
  • 4
  • You've got me wondering if I should bother with a trait at all, and instead just use a static class. – kmuenkel Dec 20 '22 at 16:31
  • 1
    @kmuenkel Also fine. Traits itself should normaly not do so much static stuff in the first place. – Foobar Dec 20 '22 at 16:34
  • @kmuenkel https://stackoverflow.com/questions/7892749/traits-in-php-any-real-world-examples-best-practices – Foobar Dec 20 '22 at 16:36