PHPackages                             axebear/php-magic - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. axebear/php-magic

ActiveLibrary[Framework](/categories/framework)

axebear/php-magic
=================

PHP micro framework for hooking into magic methods using docblocks and attributes.

1.0.0(1y ago)050MITPHPPHP &gt;=8.2

Since Aug 9Pushed 11mo ago1 watchersCompare

[ Source](https://github.com/AxeBearDev/php-magic)[ Packagist](https://packagist.org/packages/axebear/php-magic)[ RSS](/packages/axebear-php-magic/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (3)Versions (2)Used By (0)

PHP Magic
=========

[](#php-magic)

This PHP package provides utilities for adding magic properties and methods to your classes using custom attributes and docblocks. Highlights include:

- Automagic properties, created with the `@property` tag.
- Automagic cached, calculated properties using the `@property-read` tag and a method with the same name.
- Automagic fluent methods the `@method` tag.
- Easier overloaded methods. Use `@method` and the `#[Overloaded]` attribute to split out the logic for overloaded methods into separate functions. This package will call the correct one based on the arguments.
- Transformed properties, on set or get, with the `#[MagicProperty]` attribute.
- Full access to add more custom handlers using the `Magic` trait.

Motivation
----------

[](#motivation)

I have a love-hate relationship with magic methods in PHP. You can build some impressive things using dynamically handled properties and methods, but it can also be opaque and hard to debug without being disciplined about adding documentation. This package aims to make magic methods more transparent and easier by using PHP's built-in reflection capabilities and docblocks to define class behaviors.

**A Simple Example**

```
use AxeBear\Magic\Traits\MagicProperties;

/**
 * This example class shows how a class comment
 * can be used to define a magic property and method.
 *
 * @property-read int $count Gets the count.
 * @method self count(int $count) Updates the count.
 */
class Counter {
  use MagicProperties;
}

$counter = new Counter();
$counter->count(5);
echo $counter->count; // 5
```

**But wait. There's more.**

```
use AxeBear\Magic\Traits\MagicProperties;
use AxeBear\Magic\Traits\OverloadedMethods;
use AxeBear\Magic\Attributes\Overloaded;

/**
 * This example shows magic properties, calculated properties, magic methods, and overloaded methods.
 *
 * @property string $name
 * @method self name(string $name)
 *
 * @property int $count
 * @method self count(int $count)
 *
 * @property string $repeatedName  This is a calculated property that depends on name and count.
 *
 * @method void update(array $data) Updates the properties from an array.
 * @method void update(string $name, int $count) Updates the properties from values.
 */
class Model {
  use MagicProperties;
  use OverloadedMethods;

  #[Overloaded('update')]
  public function updateFromArray(array $data): void {
    $this->name ??= $data['name'] ?? null;
    $this->count ??= $data['count'] ?? null;
  }

  #[Overloaded('update')]
  public function updateFromValues(string $name, int $count): void {
    $this->name = $name;
    $this->count = $count;
  }

  public function repeatedName(string $name, int $count): string {
    return str_repeat($name, $count);
  }
}

$model = new Model();
$model->name('Axe Bear')->count(1);
$model->update(['name' => 'Axe', 'count' => 2]);
$model->update('Bear', 2);
echo $model->name; // Bear
echo $model->count; // 2
echo $model->repeatedName; // BearBear

$model->count('1'); // Type coercion comes for free (mostly!)
echo $model->count; // 1
```

Installation
------------

[](#installation)

```
composer require axebear/php-magic
```

Scripts
-------

[](#scripts)

- `composer test`: Test with Pest
- `composer cli`: Open a Pysch shell with the package loaded

---

Getting Started
===============

[](#getting-started)

MagicProperties
---------------

[](#magicproperties)

The `MagicProperties` trait inspects your class documentation for `@property`, `@property-read`, and `@property-write` tags and adds the corresponding magic methods to your class so that those properties work. You can optionally add configuration to any of the properties with the `#[MagicProperty]` attribute.

**Calculated Properties**You may provide a backing protected or private method for a `@property-read` property. If you do, that method will be used as the getter for the property. This allows you to create calculated properties. Named parameters for this method are mapped to properties of the class, and the output is cached.

**Type Coercion**Simple type coercion is applied based on the type hint in the property tag. Most [PHPDoc Types that PHPStan supports](https://phpstan.org/writing-php-code/phpdoc-types) are automatically cast for you. If a type isn't supported, you can add custom type coercion by adding a `#[MagicProperty]` attribute to the property and defining `onSet` and `onGet` methods.

**Fluent Getters and Setters**In addition to mapping properties, you can also create magic getter and setter methods using the `@method` tag in your class documentation. This allows you to provide a fluent interface for your class so that you can chain multiple setter calls together.

Digging Deeper
==============

[](#digging-deeper)

`AxeBear\Magic\Traits\Magic`
----------------------------

[](#axebearmagictraitsmagic)

This base trait is a registry for all of the handlers to call when a magic method is needed. The other traits in this package use this one at their core to provide the magic functionality, but it's also available for you to use directly.

**Important Hints**

The [visibility](https://www.php.net/manual/en/language.oop5.visibility.php) of the properties and methods that you use with the `Magic` trait is important. The class members being [overloaded](https://www.php.net/manual/en/language.oop5.overloading.php) should be inaccessible, either `protected` or `private`, so that the magic methods can be called.

Events
------

[](#events)

When a magic method is called, the `Magic` trait will generate a `MagicEvent` instance and pass it to any registered handlers that match the event name (using [fnmatch](https://www.php.net/manual/en/function.fnmatch.php)).

The base `MagicEvent` instanced includes the following properties:

- `name`: The name of the class member being called.
- `stopped`: A boolean value that can be set to `true` to stop the event from being processed by any further handlers.

This class also provides the ability to set an output value that will be returned by the magic method. The `Magic` trait will return this value when processing the magic method. The output can be manipulated by any of the handlers that are registered for the event in turn, which means you can pipe the output value through multiple functions.

- `setOutput(mixed $value): static` Sets the output value for the event.
- `hasOutput(): bool` Checks if the event has an output value set. (This is important because `null` is a valid output value.)
- `getOutput(?Closure $defaultValue = null): mixed` Gets the output value for the event.

### [\_\_get](https://www.php.net/manual/en/language.oop5.overloading.php#object.get)

[](#__get)

- Listener: `onMagicGet(string $name, Closure ...$handlers): static`
- Event: `MagicGetEvent`

```
public __get(string $name): mixed
```

To hook into this event, register one or more handlers using the `$this->onMagicGet($pattern, Closure ...$handlers)` method. The closure should expect a `MagicGetEvent` instance as its parameter.

### [\_\_set](https://www.php.net/manual/en/language.oop5.overloading.php#object.set)

[](#__set)

- Listener: `onMagicSet(string $name, Closure ...$handlers): static`
- Event: `MagicSetEvent`

```
public __set(string $name, mixed $value): void
```

To hook into this event, register one or more handlers using the `$this->onMagicSet($pattern, Closure ...$handlers)` method. The closure should expect a `MagicSetEvent` instance as its parameter. This event includes an additional `value` property that contains the value being set.

### [\_\_call](https://www.php.net/manual/en/language.oop5.overloading.php#object.call)

[](#__call)

- Listener: `onMagicCall(string $name, Closure ...$handlers): static`
- Event: `MagicCallEvent`

```
public __call(string $name, array $arguments): mixed
```

To hook into this event, register one or more handlers using the `$this->onMagicSet($pattern, Closure ...$handlers)` method. The closure should expect a `MagicCallEvent` instance as its parameter. This event includes an additional `arguments` property that contains the arguments being passed to the method.

---

MagicProperties
===============

[](#magicproperties-1)

This trait inspects your class documentation for `@property`, `@property-read`, and `@property-write` tags and adds the corresponding magic methods to your class so that those properties work. You can optionally add configuration to any of the properties with the `#[MagicProperty]` attribute.

Basic Usage
-----------

[](#basic-usage)

At its simplest when you include `@property` tags in your class documentation, the `MagicProperties` trait will add a getter and setter for the property.

If there the class includes a protected or private property of the same name, it will be used as the backing storage for the property. If there is not property with the name, the values will be stored in an `unboundProperties` array defined in the trait.

In either case, you can use the `getRawValue` method to get the raw value of the property, bypassing any transformations that may be applied. (See the section on transforming values for more information.)

```
/**
 * @property string $name
 * @property int $count
 */
class Model {
  use MagicProperties;
}

$model = new Model();

$model->name = 'ernst';
echo $model->name; // ernst

$model->count = 5;
echo $model->count; // 5

// Simple type coercion is applied based on the type hint in the property tag.
$model->count = '6';
echo $model->count; // 6
```

Read-Only and Write-Only Properties
-----------------------------------

[](#read-only-and-write-only-properties)

You can also define read-only and write-only properties with the `@property-read` and `@property-write` tags. These can't be unbound. They'll need a backing property in your class. Otherwise a readonly property won't have an initial value, and a write-only property won't have a place to store the value.

```
/**
 * @property-read string $defaultName
 * @property-write string $newName
 */
class Model {
  use MagicProperties;

  protected string $defaultName = 'leonora';

  protected string $newName;
}

$model = new Model();
echo $model->defaultName; // leonora
$model->newName = 'ernst';
```

Calculated Properties
---------------------

[](#calculated-properties)

You can also define calculated properties by adding a `@property-read` tag to your class documentation and defining a protected or private method with the same name as the property.

If the calculation has any dependencies on other class values, you should add those as parameters to the method. Use the same name as the class members. Output of calculated properties are cached, and any parameters included in the method signature will be used to calculate the cache.

```
/**
 * @property string $name
 * @property int $count
 * @property-read string $repeatedName
 */
class Model {
  use MagicProperties;

  protected string $name;

  protected int $count;

  protected function repeatedName(string $name, int $count): string
  {
    return str_repeat($name, $count);
  }
}

$model = new Model();
$model->name = 'ernst';
$model->count = 3;
echo $model->repeatedName; // ernsternsternst
```

Transforming Values
-------------------

[](#transforming-values)

You can also customize how a property is set or retrieved by adding a `#[MagicProperty]` attribute to the property. The `#[MagicProperty]` attribute accepts `onGet` and `onSet` parameters that allow modifying the value before setting it.

Both `onSet` and `onGet` accept an array of callables that will be called in the order they are defined. The callables should accept the value as the first parameter and return the modified value. You may use either built-in PHP functions or custom class methods that are defined on the class.

```
/**
 * @property string $message
 */
class Model {
  use MagicProperties;

  #[MagicProperty(onSet: ['encode'], onGet: ['decode'])]
  protected string $message;

  protected function encode(string $value): string
  {
      return base64_encode($value);
  }

  protected function decode(string $value): string
  {
      return base64_decode($value);
  }
}
$model = new Model();
$model->message = 'ernst';
echo $model->message; // ernst
echo $model->getRawValue('message'); // ZXJuc3Q=
```

Fluent Getters and Setters
--------------------------

[](#fluent-getters-and-setters)

In addition to mapping properties, you can also create magic getter and setter methods using the `@method` tag in your class documentation. This is useful when you want to provide a fluent interface for your class. The `MagicProperties` trait will automatically add the magic methods to your class when it sees the `@method` tag with either zero or one parameters.

If the `@method` tag includes one parameter, the `MagicProperties` trait will add a setter method. If the `@method` tag includes zero parameters, the `MagicProperties` trait will add a getter method.

```
/**
 * @method string name()
 * @method self name(string $name)
 */
class Model {
  use MagicProperties;
}

$model = new Model();
$model->name('ernst');
echo $model->name(); // ernst
```

---

Overloaded Methods
==================

[](#overloaded-methods)

PHP doesn't yet offer clean syntax for overloading methods. With the `#[Overloaded]` attribute and the `OverloadedMethods` trait, you can split out the logic for overloaded methods into separate methods that are called based on the type of the arguments passed to the method.

Instead of:

```
class Model {
  public function find(...$args) {
    if (count($args) === 1 && is_int($args[0])) {
      return $this->findById($args[0]);
    }
    if (count($args) === 1 && is_string($args[0])) {
      return $this->findBySlug($args[0]);
    }
    if (count($args) === 2 && is_string($args[0]) && is_int($args[1])) {
      return $this->findBySlugAndId($args[0], $args[1]);
    }

    throw new InvalidArgumentException('Invalid arguments');
  }

  protected function findById(int $id) {
    return "id: $id";
  }

  protected function findBySlug(string $slug) {
    return "slug: $slug";
  }

  protected function findBySlugAndId(string $slug, int $id) {
    return "slug: $slug, id: $id";
  }
}
```

You can do this:

```
use AxeBear\Magic\Attributes\Overloaded;
use AxeBear\Magic\Traits\OverloadedMethods;

/**
 * @method string find(int $id) Finds by id.
 * @method string find(string $slug) Finds by slug.
 * @method string find(string $slug, int $id) Finds by slug and id.
 */
class Model {
  use OverloadedMethods;

  #[Overloaded('find')]
  protected function findById(int $id) {
    return "id: $id";
  }

  #[Overloaded('find')]
  protected function findBySlug(string $slug) {
    return "slug: $slug";
  }

  #[Overloaded]('find')
  protected function findBySlugAndId(string $slug, int $id) {
    return "slug: $slug, id: $id";
  }
}
```

###  Health Score

30

—

LowBetter than 64% of packages

Maintenance44

Moderate activity, may be stable

Popularity9

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

646d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/0fa8156d6559914a5a62ba9e6852aae3c2cbd312087fdf2923d6fa53eaa81dd3?d=identicon)[spleenboy](/maintainers/spleenboy)

---

Top Contributors

[![spleenboy](https://avatars.githubusercontent.com/u/1503272?v=4)](https://github.com/spleenboy "spleenboy (2 commits)")

---

Tags

attributedocblockmagic-methodsphpphp-library

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/axebear-php-magic/health.svg)

```
[![Health](https://phpackages.com/badges/axebear-php-magic/health.svg)](https://phpackages.com/packages/axebear-php-magic)
```

###  Alternatives

[laravel/wayfinder

Generate TypeScript representations of your Laravel actions and routes.

1.7k4.1M69](/packages/laravel-wayfinder)[laravel/prompts

Add beautiful and user-friendly forms to your command-line applications.

708181.8M596](/packages/laravel-prompts)[laravel/pail

Easily delve into your Laravel application's log files directly from the command line.

91245.3M590](/packages/laravel-pail)[nette/bootstrap

🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.

68435.8M592](/packages/nette-bootstrap)[cakephp/cakephp-codesniffer

CakePHP CodeSniffer Standards

23711.0M658](/packages/cakephp-cakephp-codesniffer)[spryker/code-sniffer

Spryker Code Sniffer Standards

374.1M2.3k](/packages/spryker-code-sniffer)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
