Skip to content

Commit 2777f1d

Browse files
committed
init
1 parent 4316e71 commit 2777f1d

92 files changed

Lines changed: 3447 additions & 103 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ vendor/
1313
.phpunit*
1414
*.cache
1515
.vscode/
16-
*core
16+
*.core
1717
*.override.*
1818
composer.lock
1919
*.log

compose.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
services:
2-
app:
2+
morph:
3+
platform: linux/amd64
34
container_name: morph-app
4-
image: php:8.3
5+
image: webdevops/php-dev:8.3
6+
working_dir: /opt/www
57
volumes:
68
- ./:/opt/www
79
environment:
10+
- XDEBUG_MODE=coverage
811
- SONAR_TOKEN=${SONAR_TOKEN:-undefined}
912
entrypoint: [ 'tail', '-f', '/dev/null' ]
1013
restart: on-failure

composer.json

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
},
1313
"files": [
1414
"functions/cast.php",
15-
"functions/notation.php"
15+
"functions/crypt.php",
16+
"functions/json.php",
17+
"functions/notation.php",
18+
"functions/util.php"
1619
]
1720
},
1821
"autoload-dev": {
@@ -23,11 +26,13 @@
2326
"require": {
2427
"php": "^8.3",
2528
"ext-json": "*",
26-
"jawira/case-converter": "^3.5"
29+
"jawira/case-converter": "^3.5",
30+
"visus/cuid2": "^5.1"
2731
},
2832
"require-dev": {
2933
"bnf/phpstan-psr-container": "^1.1",
3034
"deptrac/deptrac": "^3.0",
35+
"fakerphp/faker": "^1.24",
3136
"php-mock/php-mock-phpunit": "^2.12",
3237
"phpmd/phpmd": "^2.15",
3338
"phpstan/phpstan": "^2",
@@ -38,6 +43,32 @@
3843
"squizlabs/php_codesniffer": "^3.11",
3944
"vimeo/psalm": "^5.26"
4045
},
46+
"scripts": {
47+
"test": "vendor/bin/phpunit",
48+
"lint:phpcs": "phpcs --standard=PSR12,phpcs.xml -s src",
49+
"lint:phpstan": "phpstan analyse --memory-limit 512M",
50+
"lint:phpmd": "phpmd src ansi phpmd.xml",
51+
"lint:rector": "rector process --dry-run",
52+
"lint:psalm": "psalm",
53+
"lint": [
54+
"composer lint:phpcs",
55+
"composer lint:phpstan",
56+
"composer lint:phpmd",
57+
"composer lint:rector",
58+
"composer lint:psalm"
59+
],
60+
"ci": [
61+
"composer lint",
62+
"composer test"
63+
],
64+
"fix": [
65+
"rector process",
66+
"php-cs-fixer fix src",
67+
"php-cs-fixer fix bin",
68+
"php-cs-fixer fix config",
69+
"php-cs-fixer fix tests"
70+
]
71+
},
4172
"minimum-stability": "dev",
4273
"prefer-stable": true,
4374
"config": {

functions/cast.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
declare(strict_types=1);
44

5-
namespace Serendipity\Type\Cast;
5+
namespace Morph\Cast;
66

77
use Stringable;
88

functions/crypt.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Morph\Crypt;
6+
7+
use InvalidArgumentException;
8+
use RuntimeException;
9+
use Morph\Support\Set;
10+
11+
use function base64_decode;
12+
use function base64_encode;
13+
use function define;
14+
use function defined;
15+
use function openssl_decrypt;
16+
use function openssl_encrypt;
17+
use function random_bytes;
18+
use function Morph\Cast\arrayify;
19+
use function Morph\Cast\stringify;
20+
use function Morph\Json\decode;
21+
use function Morph\Json\encode;
22+
use function Morph\Util\extractString;
23+
use function sprintf;
24+
25+
if (! defined('DEFAULT_CRYPT_KEY')) {
26+
define('DEFAULT_CRYPT_KEY', base64_encode(stringify(random_bytes(32))));
27+
}
28+
29+
if (! function_exists(__NAMESPACE__ . '\encrypt')) {
30+
function encrypt(string $plaintext, string $key = DEFAULT_CRYPT_KEY): string
31+
{
32+
$salt = random_bytes(16);
33+
$ciphertext = openssl_encrypt($plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $salt);
34+
if ($ciphertext === false) {
35+
throw new RuntimeException(sprintf("Encryption failed: '%s'.", $plaintext));
36+
}
37+
38+
$data = group('aes-256-cbc', base64_encode($salt), base64_encode($ciphertext));
39+
return base64_encode($data);
40+
}
41+
}
42+
43+
if (! function_exists(__NAMESPACE__ . '\decrypt')) {
44+
function decrypt(string $encrypted, string $key = DEFAULT_CRYPT_KEY): string
45+
{
46+
$set = ungroup($encrypted);
47+
if ($set->get('algo') !== 'aes-256-cbc') {
48+
throw new InvalidArgumentException('Unknown encryption algorithm.');
49+
}
50+
51+
$salt = base64_decode(stringify($set->get('salt')));
52+
$ciphertext = base64_decode(stringify($set->get('data')));
53+
54+
$decrypted = openssl_decrypt($ciphertext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $salt);
55+
if ($decrypted === false) {
56+
throw new RuntimeException(sprintf("Decryption failed: '%s'.", $encrypted));
57+
}
58+
return $decrypted;
59+
}
60+
}
61+
62+
if (! function_exists(__NAMESPACE__ . '\group')) {
63+
function group(string $algo, string $salt, string $ciphertext): string
64+
{
65+
$data = [
66+
'algo' => $algo,
67+
'salt' => $salt,
68+
'data' => $ciphertext,
69+
];
70+
$encoded = encode($data);
71+
if ($encoded === null) {
72+
throw new RuntimeException(sprintf("Encryption failed: '%s'.", $ciphertext));
73+
}
74+
return $encoded;
75+
}
76+
}
77+
78+
if (! function_exists(__NAMESPACE__ . '\ungroup')) {
79+
function ungroup(string $encrypted): Set
80+
{
81+
$decoded = arrayify(decode(base64_decode($encrypted)));
82+
$algo = extractString($decoded, 'algo');
83+
$salt = extractString($decoded, 'salt');
84+
$data = extractString($decoded, 'data');
85+
return ($algo && $salt && $data)
86+
? new Set([
87+
'algo' => $algo,
88+
'salt' => $salt,
89+
'data' => $data,
90+
])
91+
: throw new InvalidArgumentException(sprintf("Invalid encrypted format: '%s'.", $encrypted));
92+
}
93+
}

functions/json.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Morph\Json;
6+
7+
use JsonException;
8+
9+
use function Morph\Cast\arrayify;
10+
use function Morph\Cast\stringify;
11+
12+
if (! function_exists(__NAMESPACE__ . '\decode')) {
13+
function decode(string $json): ?array
14+
{
15+
try {
16+
return arrayify(json_decode($json, true, 512, JSON_THROW_ON_ERROR));
17+
} catch (JsonException) {
18+
return null;
19+
}
20+
}
21+
}
22+
23+
if (! function_exists(__NAMESPACE__ . '\encode')) {
24+
function encode(array $data): ?string
25+
{
26+
try {
27+
return stringify(json_encode($data, JSON_THROW_ON_ERROR));
28+
} catch (JsonException) {
29+
return null;
30+
}
31+
}
32+
}

functions/notation.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
declare(strict_types=1);
44

5-
namespace Serendipity\Notation;
5+
namespace Morph\Notation;
66

77
use Jawira\CaseConverter\Convert;
88
use Morph\Support\Reflective\Notation;
99

10-
use function Serendipity\Type\Cast\stringify;
10+
use function Morph\Cast\stringify;
1111

1212
if (! function_exists(__NAMESPACE__ . '\format')) {
1313
function format(string $string, Notation $notation): string

functions/util.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Morph\Util;
6+
7+
if (! function_exists(__NAMESPACE__ . '\extractArray')) {
8+
/**
9+
* @template T
10+
* @template U
11+
* @param array<string, array<T, U>> $array
12+
* @param array<T, U> $default
13+
* @return array<T, U>
14+
*/
15+
function extractArray(array $array, string $property, array $default = []): array
16+
{
17+
$details = $array[$property] ?? null;
18+
if (! is_array($details)) {
19+
return $default;
20+
}
21+
return $details;
22+
}
23+
}
24+
25+
if (! function_exists(__NAMESPACE__ . '\extractString')) {
26+
/**
27+
* @param array<string, mixed> $array
28+
*/
29+
function extractString(array $array, string $property, string $default = ''): string
30+
{
31+
$string = $array[$property] ?? $default;
32+
return is_string($string) ? $string : $default;
33+
}
34+
}
35+
36+
if (! function_exists(__NAMESPACE__ . '\extractInt')) {
37+
/**
38+
* @param array<string, mixed> $array
39+
*/
40+
function extractInt(array $array, string $property, int $default = 0): int
41+
{
42+
$int = $array[$property] ?? $default;
43+
return is_int($int) ? $int : $default;
44+
}
45+
}
46+
47+
if (! function_exists(__NAMESPACE__ . '\extractBool')) {
48+
/**
49+
* @param array<string, mixed> $array
50+
*/
51+
function extractBool(array $array, string $property, bool $default = false): bool
52+
{
53+
$bool = $array[$property] ?? $default;
54+
return is_bool($bool) ? $bool : $default;
55+
}
56+
}
57+
58+
if (! function_exists(__NAMESPACE__ . '\extractNumeric')) {
59+
/**
60+
* @param array<string, mixed> $array
61+
*/
62+
function extractNumeric(array $array, string $property, float|int $default = 0): float
63+
{
64+
$numeric = $array[$property] ?? $default;
65+
return (float) (is_numeric($numeric) ? $numeric : $default);
66+
}
67+
}

makefile

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/make
2+
3+
.DEFAULT_GOAL := help
4+
5+
COMPOSE_RUNNER ?= "docker-compose"
6+
7+
setup: ## Setup the project
8+
@make prune
9+
@make install
10+
@make up
11+
12+
##@ Bash controls
13+
14+
bash: ## Start nginx bash
15+
@make up
16+
@$(COMPOSE_RUNNER) exec morph bash
17+
18+
up: ## Start the project
19+
@$(COMPOSE_RUNNER) up -d
20+
21+
down: ## Stop the project
22+
@$(COMPOSE_RUNNER) down --remove-orphans
23+
24+
prune: ## Prune the project
25+
@$(COMPOSE_RUNNER) down --remove-orphans --volumes
26+
27+
28+
##@ Composer
29+
30+
install: ## Composer install dependencies
31+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph install
32+
33+
dump: ## Run the composer dump
34+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph dump-autoload
35+
36+
37+
##@ Code analysis
38+
39+
lint: ## Perform code style lint
40+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph lint
41+
42+
lint-phpcs: ## Perform code style list using phpcs
43+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph lint:phpcs
44+
45+
lint-phpstan: ## Perform code style list using phpstan
46+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph lint:phpstan
47+
48+
lint-phpmd: ## Perform code style list using phpmd
49+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph lint:phpmd
50+
51+
lint-rector: ## Perform code style list using rector
52+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph lint:rector
53+
54+
lint-psalm: ## Perform code style list using psalm
55+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph lint:psalm
56+
57+
fix: ## Perform code style fix
58+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph fix
59+
60+
##@ Tests
61+
62+
test: ## Execute the tests
63+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph test -- --coverage-html tests/.phpunit/html
64+
65+
66+
##@ CI
67+
68+
ci: ## Execute all analysis as CI does
69+
@$(COMPOSE_RUNNER) run --rm --entrypoint composer morph ci
70+
71+
72+
## Quality
73+
74+
sonar: ## Run the sonar analysis
75+
@$(COMPOSE_RUNNER) run --rm --entrypoint "/bin/sonar-scanner" morph -Dsonar.host.url=https://sonarcloud.io -X
76+
77+
78+
##@ Docs
79+
80+
help: ## Print the makefile help
81+
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

0 commit comments

Comments
 (0)