Skip to content

Static analysis: preserve concrete resource return types and expose magic attribute accessors #497

@ChiragAgg5k

Description

@ChiragAgg5k

php-k8s works correctly at runtime, but some of its dynamic behavior is hard for static analyzers to understand. In particular, PHPStan flags valid code because a few APIs are powered by __call() and some resource-returning methods are documented too broadly.

I hit this in a downstream project and had to add local PHPStan stubs as a workaround.

Current behavior

Two patterns are difficult for static analysis:

  1. RunsClusterOperations methods return the same concrete resource instance, but the docs widen them to K8sResource
  2. HasAttributes::__call() provides runtime setX/getX/removeX accessors, but those methods are not visible to PHPStan/IDEs

Examples

1. Concrete resource methods lose type information

In src/Traits/RunsClusterOperations.php, methods like these currently widen the return type:

  • get()
  • create()
  • createOrUpdate()
  • syncWithCluster()
  • refresh()
  • refreshOriginal()

At runtime, if I call these on K8sSecret, K8sPod, etc., I still get that same concrete class back. But static analyzers only see K8sResource, so downstream code loses access to concrete methods.

Example downstream failure:

$secret = $cluster->secret()->setNamespace($ns)->getByName($name);

$cluster->secret()
    ->setName($name)
    ->setNamespace($targetNs)
    ->setData($secret->getData(), false)
    ->setType($secret->getType() ?? '')
    ->createOrUpdate();

PHPStan reports getData()/getType()/setType() as undefined once the type has been widened.

2. Magic attribute setters/getters are valid at runtime but invisible statically

In src/Traits/Resource/HasAttributes.php, __call() maps unknown:

  • setX(...) -> setAttribute('x', ...)
  • getX(...) -> getAttribute('x', ...)
  • removeX(...) -> removeAttribute('x')

So code like this works at runtime:

K8s::container()
    ->setName('sidecar')
    ->setImagePullPolicy('IfNotPresent')
    ->setCommand(['sidecar.php'])
    ->setArgs([json_encode($args)])
    ->setWorkingDir('/usr/code');

But static analyzers flag all of those as undefined methods on Instances\Container.

Suggested fix

Because the package now requires PHP ^8.2, I think the upstream fix can be stronger than just PHPDoc.

A. Preserve concrete resource return types

In src/Traits/RunsClusterOperations.php, update the relevant methods to use static return information, ideally native where possible.

Candidates:

  • getByName()
  • get()
  • create()
  • createOrUpdate()
  • syncWithCluster()
  • refresh()
  • refreshOriginal()

Even if native static is not used everywhere, at minimum the PHPDoc should reflect static/$this consistently instead of K8sResource.

B. Expose common magic methods for analyzers/IDEs

For classes that intentionally rely on HasAttributes::__call(), add @method annotations on the concrete classes for common accessors.

Examples that would help immediately:

src/Instances/Container.php

  • setName(string $name)
  • setImagePullPolicy(string $policy)
  • setCommand(array $command)
  • setArgs(array $args)
  • setWorkingDir(?string $workingDir)

src/Kinds/K8sSecret.php

  • setType(string $type)
  • getType(): ?string

There are likely other classes worth annotating too, but these cover a common real-world path.

C. Optional: ship official PHPStan/Psalm stubs

If you prefer not to add many @method tags inline, another option is to ship stub files for static analysis. That would still let the runtime API stay dynamic while making the intended API visible to tools.

Why this matters

Right now downstream users either:

  • replace readable fluent calls with raw setAttribute(...), or
  • maintain local stub files, or
  • suppress the errors in a PHPStan baseline

It would be better if the package exposed its intended API to static tools directly.

Repro summary

The repro is basically:

  • use a concrete resource returned from get()/getByName()
  • call a concrete-only method like K8sSecret::getData() or getType()
  • use Instances\Container fluent setters that are currently provided only by HasAttributes::__call()

Notes

The runtime behavior already exists in:

  • src/Traits/RunsClusterOperations.php
  • src/Traits/Resource/HasAttributes.php

This issue is specifically about surfacing that behavior to static analysis.

If this direction looks good, I can turn it into a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions