Skip to content

Commit d601874

Browse files
committed
Initial commit
1 parent d49a104 commit d601874

11 files changed

Lines changed: 477 additions & 4 deletions

File tree

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Global
2+
.composer
3+
composer.lock
4+
package-lock.json
5+
vendor/
6+
7+
# OS Generated
8+
.DS_Store*
9+
ehthumbs.db
10+
Icon?
11+
Thumbs.db
12+
*.swp
13+
14+
# phpstorm
15+
.idea/*

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<a name="1.0.0"></a>
2+
# [1.0.0](https://github.com/atomastic/macroable) (2020-12-xx)
3+
* Initial release

LICENSE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
MIT License
1+
The MIT License (MIT)
22

3-
Copyright (c) 2020 ATOMASTIC
3+
Copyright (c) 2020 Sergey Romanenko
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 168 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,168 @@
1-
# macroable
2-
A trait to dynamically add methods to a class.
1+
<h1 align="center">Macroable Component</h1>
2+
<p align="center">
3+
Macroable Component is a trait that, gives you the ability in effect to add new methods to a class at runtime.
4+
</p>
5+
<p align="center">
6+
<a href="https://github.com/atomastic/macroable/releases"><img alt="Version" src="https://img.shields.io/github/release/atomastic/macroable.svg?label=version&color=green"></a> <a href="https://github.com/atomastic/macroable"><img src="https://img.shields.io/badge/license-MIT-blue.svg?color=green" alt="License"></a> <a href="https://packagist.org/packages/atomastic/macroable"><img src="https://poser.pugx.org/atomastic/macroable/downloads" alt="Total downloads"></a> <img src="https://github.com/atomastic/macroable/workflows/Static%20Analysis/badge.svg?branch=dev"> <img src="https://github.com/atomastic/macroable/workflows/Tests/badge.svg">
7+
<a href="https://app.codacy.com/gh/atomastic/macroable?utm_source=github.com&utm_medium=referral&utm_content=atomastic/macroable&utm_campaign=Badge_Grade_Dashboard"><img src="https://api.codacy.com/project/badge/Grade/72b4dc84c20145e1b77dc0004a3c8e3d"></a> <a href="https://codeclimate.com/github/atomastic/macroable/maintainability"><img src="https://api.codeclimate.com/v1/badges/a4c673a4640a3863a9a4/maintainability" /></a> <a href="https://app.fossa.com/projects/git%2Bgithub.com%2Fatomastic%2Fmacroable?ref=badge_shield" alt="FOSSA Status"><img src="https://app.fossa.com/api/projects/git%2Bgithub.com%2Fatomastic%2Fmacroable.svg?type=shield"/></a>
8+
</p>
9+
10+
<br>
11+
12+
* [Installation](#installation)
13+
* [Usage](#usage)
14+
* [Methods](#methods)
15+
* [Tests](#tests)
16+
* [License](#license)
17+
18+
### Installation
19+
20+
#### With [Composer](https://getcomposer.org)
21+
22+
```
23+
composer require atomastic/macroable
24+
```
25+
26+
### Usage
27+
28+
```php
29+
use Atomastic\Macroable\Macroable;
30+
```
31+
32+
### Methods
33+
34+
| Method | Description |
35+
|---|---|
36+
| <a href="#macroable_macro">`macro()`</a> | Register a custom macro. |
37+
| <a href="#macroable_mixin">`mixin()`</a> | Mix another object into the class. |
38+
| <a href="#macroable_hasMacro">`hasMacro()`</a> | Checks if macro is registered. |
39+
40+
#### Methods Details
41+
42+
##### <a name="macroable_macro"></a> Method: `macro()`
43+
44+
```php
45+
/**
46+
* Register a custom macro.
47+
*
48+
* @param string $name Name.
49+
* @param object|callable $macro Macro.
50+
* @return void
51+
*/
52+
public static function macro(string $name, $macro): void
53+
```
54+
55+
##### Example
56+
57+
```php
58+
$macroableClass = new class() {
59+
use Macroable;
60+
};
61+
62+
63+
$macroableClass::macro('concatenate', function(... $strings) {
64+
return implode('-', $strings);
65+
});
66+
67+
$macroableClass::macro('message', function($name) {
68+
return 'Hello ' . $name;
69+
});
70+
71+
echo $macroableClass->concatenate('one', 'two', 'three');
72+
echo $macroableClass->message('Jack');
73+
```
74+
75+
##### The above example will output:
76+
77+
```
78+
one-two-three
79+
Hello Jack
80+
```
81+
82+
##### <a name="macroable_mixin"></a> Method: `mixin()`
83+
84+
```php
85+
/**
86+
* Mix another object into the class.
87+
*
88+
* @param object $mixin Mixin.
89+
* @param bool $replace Replace.
90+
* @return void
91+
*
92+
* @throws ReflectionException
93+
*/
94+
public static function mixin($mixin, bool $replace = true): void
95+
```
96+
97+
##### Example
98+
99+
```php
100+
$mixinClass = new class() {
101+
public function mixinMethod()
102+
{
103+
return function() {
104+
return 'mixinMethod';
105+
};
106+
}
107+
108+
public function anotherMixinMethod()
109+
{
110+
return function() {
111+
return 'anotherMixinMethod';
112+
};
113+
}
114+
};
115+
116+
$macroableClass->mixin($mixin);
117+
118+
$macroableClass->mixinMethod();
119+
$macroableClass->anotherMixinMethod();
120+
```
121+
122+
##### The above example will output:
123+
124+
```
125+
mixinMethod
126+
anotherMixinMethod
127+
```
128+
129+
130+
##### <a name="macroable_hasMacro"></a> Method: `hasMacro()`
131+
132+
```php
133+
/**
134+
* Checks if macro is registered.
135+
*
136+
* @param string $name Name
137+
* @return bool
138+
*/
139+
public static function hasMacro(string $name): bool
140+
```
141+
142+
##### Example
143+
144+
```php
145+
$macroableClass = new class() {
146+
use Macroable;
147+
};
148+
149+
$macroableClass::macro('message', function($name) {
150+
return 'Hello ' . $name;
151+
});
152+
153+
if ($macroableClass::hasMacro('message')) {
154+
// do something...
155+
}
156+
```
157+
158+
### Tests
159+
160+
Run tests
161+
162+
```
163+
./vendor/bin/pest
164+
```
165+
166+
### License
167+
[The MIT License (MIT)](https://github.com/atomastic/macroable/blob/master/LICENSE)
168+
Copyright (c) 2020 [Sergey Romanenko](https://github.com/Awilum)

composer.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "atomastic/macroable",
3+
"description": "Macroable Component is a trait that, gives you the ability in effect to add new methods to a class at runtime.",
4+
"license": "MIT",
5+
"keywords": [
6+
"Macroable", "macroable", "php", "atomastic"
7+
],
8+
"support": {
9+
"source": "https://github.com/atomastic/macroable",
10+
"issues": "https://github.com/atomastic/macroable/issues"
11+
},
12+
"authors": [
13+
{
14+
"name": "Sergey Romanenko",
15+
"email": "sergey.romanenko@flextype.org",
16+
"homepage": "https://github.com/Awilum"
17+
}
18+
],
19+
"require": {
20+
"php": "^7.3 || ^8.0"
21+
},
22+
"autoload":{
23+
"psr-4": {
24+
"Atomastic\\Macroable\\": "src/"
25+
}
26+
},
27+
"require-dev": {
28+
"doctrine/coding-standard": "8.1.0",
29+
"pestphp/pest": "^0.3.3",
30+
"phpstan/phpstan": "^0.12.42"
31+
}
32+
}

phpstan.neon

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
parameters:
2+
level: max
3+
paths:
4+
- %currentWorkingDirectory%/src/
5+
- %currentWorkingDirectory%/tests/
6+
reportUnmatchedIgnoredErrors: false
7+
checkGenericClassInNonGenericObjectType: false
8+
checkMissingIterableValueType: false
9+
ignoreErrors:
10+
# ignore
11+
- '#Unsafe usage of new static\(\)\.#'

phpunit.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
>
7+
<testsuites>
8+
<testsuite name="Test Suite">
9+
<directory suffix="Test.php">./tests</directory>
10+
</testsuite>
11+
</testsuites>
12+
<filter>
13+
<whitelist processUncoveredFilesFromWhitelist="true">
14+
<directory suffix=".php">./app</directory>
15+
<directory suffix=".php">./src</directory>
16+
</whitelist>
17+
</filter>
18+
</phpunit>

src/Macroable.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Atomastic\Macroable;
6+
7+
use BadMethodCallException;
8+
use Closure;
9+
use ReflectionClass;
10+
use ReflectionMethod;
11+
12+
trait Macroable
13+
{
14+
/**
15+
* The registered string macros.
16+
*
17+
* @var array
18+
*/
19+
protected static $macros = [];
20+
21+
/**
22+
* Register a custom macro.
23+
*
24+
* @param string $name Name.
25+
* @param object|callable $macro Macro.
26+
* @return void
27+
*/
28+
public static function macro(string $name, $macro): void
29+
{
30+
static::$macros[$name] = $macro;
31+
}
32+
33+
/**
34+
* Mix another object into the class.
35+
*
36+
* @param object $mixin Mixin.
37+
* @param bool $replace Replace.
38+
* @return void
39+
*
40+
* @throws ReflectionException
41+
*/
42+
public static function mixin($mixin, bool $replace = true): void
43+
{
44+
$methods = (new ReflectionClass($mixin))->getMethods(
45+
ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
46+
);
47+
48+
foreach ($methods as $method) {
49+
if ($replace || ! static::hasMacro($method->name)) {
50+
$method->setAccessible(true);
51+
static::macro($method->name, $method->invoke($mixin));
52+
}
53+
}
54+
}
55+
56+
/**
57+
* Checks if macro is registered.
58+
*
59+
* @param string $name Name
60+
* @return bool
61+
*/
62+
public static function hasMacro(string $name): bool
63+
{
64+
return isset(static::$macros[$name]);
65+
}
66+
67+
/**
68+
* Dynamically handle calls to the class.
69+
*
70+
* @param string $method Method.
71+
* @param array $parameters Parameters.
72+
* @return mixed
73+
*
74+
* @throws BadMethodCallException
75+
*/
76+
public static function __callStatic(string $method, array $parameters)
77+
{
78+
if (! static::hasMacro($method)) {
79+
throw new BadMethodCallException(sprintf(
80+
'Method %s::%s does not exist.', static::class, $method
81+
));
82+
}
83+
84+
$macro = static::$macros[$method];
85+
86+
if ($macro instanceof Closure) {
87+
$macro = $macro->bindTo(null, static::class);
88+
}
89+
90+
return $macro(...$parameters);
91+
}
92+
93+
/**
94+
* Dynamically handle calls to the class.
95+
*
96+
* @param string $method Method.
97+
* @param array $parameters Parameters.
98+
* @return mixed
99+
*
100+
* @throws BadMethodCallException
101+
*/
102+
public function __call(string $method, array $parameters)
103+
{
104+
if (! static::hasMacro($method)) {
105+
throw new BadMethodCallException(sprintf(
106+
'Method %s::%s does not exist.', static::class, $method
107+
));
108+
}
109+
110+
$macro = static::$macros[$method];
111+
112+
if ($macro instanceof Closure) {
113+
$macro = $macro->bindTo($this, static::class);
114+
}
115+
116+
return $macro(...$parameters);
117+
}
118+
}

tests/Helpers.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests;
6+
7+
// ..

0 commit comments

Comments
 (0)