Skip to content

Conversation

@shaedrich
Copy link
Contributor

@shaedrich shaedrich commented Jan 24, 2026

Changes

  • Adds new tap_until() helper with callback that returns the HigherOrderTapProxy as long as the given condition is met, otherwise it returns the value as usual
  • Adds option to tap() to allow the HigherOrderTapProxy to be called a given number of times before returning the value (as shortcut for tap_until())

Examples

Tap n times

class Foo
{
    public function __construct(private int $counter = 0) {}

    public function increment(): void
    {
        $this->counter++;
    }

    public function decrement(): void
    {
        $this->counter--;
    }

    public function getCounter(): int
    {
        return $this->counter;
    }
}

tap(new Foo, 3)->increment()->decrement()->increment()->getCounter(); // 4

Tap until arbitrary condition is met

class Dice
{
    public function __construct(
        private int $spots,
        private int $rolls = 0,
        private array $seed = [5, 1, 4, 3, 6, 2];
    ) {}

    public function roll()
    {
        $this->spots = array_shift($this->seed);
        $this->rolls++;
    }

    public function getSpots(): int
    {
        return $this->spots;
    }

    public function getRolls(): int
    {
        return $this->rolls;
    }
}

tap(new Dice, fn (Dice $dice) => $dice->getSpots() === 3)->roll()->roll()->roll()->roll()->getRolls(); // 4

Infinite tap/unlimited tap (essentially @DarkGhostHunter's multi tap)

class User
{
    use Conditionable;

    private string $givenName;
    private string $familyName;
    private ?int $age;

    public function setGivenName(string $givenName): void
    {
        $this->givenName = $givenName;
    }

    public function setFamilyName(string $familyName): void
    {
        $this->familyName = $familyName;
    }

    public function setAge(int $age): void
    {
        $this->age = $age;
    }

    public function getLabel(): string
    {
        return $this->age !== null
            ? sprintf('%s, %s (%d)', $this->familyName, $this->givenName, $this->age)
            : implode(', ', $this->familyName, $this->givenName);
    }
}

class UserController
{
    public function update(Request $request)
    {
        tap(new User, fn () => true)
            ->setGivenName($request->input('given_name'))
            ->setFamilyName($request->input('family_name'))
            ->when($request->has('birthday'), fn () => $user->setAge($request->date('birthday')->diffInYears()))
            ->target
            ->getLabel(); // "John Doe (58)"
    }
}

Previous art

@github-actions
Copy link

Thanks for submitting a PR!

Note that draft PRs are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface.

Pull requests that are abandoned in draft may be closed due to inactivity.

@shaedrich
Copy link
Contributor Author

The tests are constantly timing out for reasons I don't understand

@Rizky92
Copy link
Contributor

Rizky92 commented Jan 27, 2026

I was thinking something like this is much clearer than having to specify arbitrary number, albeit class must be tappable.

$obj = new Object();
$obj->tap->requestRecords()
    ->tap->applyChanges()
    ->tap->commit()
    ->all();

@shaedrich
Copy link
Contributor Author

shaedrich commented Jan 27, 2026

That works for classes, but not so much for the plain helper on non-tappable objects:

$obj = new Object();
tap(tap(tap($obj->requestRecords())->applyChanges())->commit()->all();

@DarkGhostHunter
Copy link
Contributor

What about this:

$tappable = tap($object);

$results = $tappable->tap->requestRecords()
    ->tap->applyChanges()
    ->tap->commit()
    ->all();

Basically, extend the original proxy object to allow tapping into the object and returning the same tap instance regardless of the result, or getting the tapped object using $tap->object.

public static __call(string $name, array $arguments)
{
    if ($name === 'tap') {
        return new static($this);
    }

    if ($name === 'object') {
        return $this->value;
    }

    // ...
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants