@@ -0,0 +1,49 @@ | |||||
APP_NAME=Trendplays | |||||
APP_ENV=local | |||||
APP_KEY=base64:qEf11CH7LBSuw7W1mm11PcvrTA3kz6AwfEXqk3p6e3s= | |||||
APP_DEBUG=true | |||||
APP_URL=http://localhost | |||||
LOG_CHANNEL=stack | |||||
LOG_LEVEL=debug | |||||
DB_CONNECTION=mysql | |||||
DB_HOST=127.0.0.1 | |||||
DB_PORT=3306 | |||||
DB_DATABASE=trendplays | |||||
DB_USERNAME=trendplays | |||||
DB_PASSWORD=testpassword123 | |||||
BROADCAST_DRIVER=log | |||||
CACHE_DRIVER=file | |||||
QUEUE_CONNECTION=sync | |||||
SESSION_DRIVER=file | |||||
SESSION_LIFETIME=120 | |||||
MEMCACHED_HOST=127.0.0.1 | |||||
REDIS_HOST=127.0.0.1 | |||||
REDIS_PASSWORD=null | |||||
REDIS_PORT=6379 | |||||
MAIL_MAILER=smtp | |||||
MAIL_HOST=mailhog | |||||
MAIL_PORT=1025 | |||||
MAIL_USERNAME=null | |||||
MAIL_PASSWORD=null | |||||
MAIL_ENCRYPTION=null | |||||
MAIL_FROM_ADDRESS=null | |||||
MAIL_FROM_NAME="${APP_NAME}" | |||||
AWS_ACCESS_KEY_ID= | |||||
AWS_SECRET_ACCESS_KEY= | |||||
AWS_DEFAULT_REGION=us-east-1 | |||||
AWS_BUCKET= | |||||
PUSHER_APP_ID= | |||||
PUSHER_APP_KEY= | |||||
PUSHER_APP_SECRET= | |||||
PUSHER_APP_CLUSTER=mt1 | |||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" | |||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" |
@@ -1,19 +1,25 @@ | |||||
*.sw[po] | |||||
/node_modules | |||||
/public/hot | |||||
/public/storage | |||||
/storage/*.key | |||||
/vendor | |||||
*.css | |||||
*.css.map | |||||
.env | |||||
.env.backup | |||||
.phpunit.result.cache | |||||
docker-compose.override.yml | |||||
Homestead.json | |||||
Homestead.yaml | |||||
npm-debug.log | |||||
yarn-error.log | |||||
package-lock.json | |||||
.DS_Store | |||||
node_modules | |||||
/dist | |||||
ses | ses | ||||
/public/* | |||||
resources/assets | |||||
# local env files | |||||
.env.local | |||||
.env.*.local | |||||
# Log files | |||||
npm-debug.log* | |||||
yarn-debug.log* | |||||
yarn-error.log* | |||||
pnpm-debug.log* | |||||
# Editor directories and files | |||||
.idea | |||||
.vscode | |||||
*.suo | |||||
*.ntvs* | |||||
*.njsproj | |||||
*.sln | |||||
*.sw? |
@@ -1,8 +1,24 @@ | |||||
# | |||||
## Deployment | |||||
- Make sure directories like storage have appropriate file permissions | |||||
## Project setup | |||||
``` | |||||
npm install | |||||
``` | |||||
## License | |||||
This is a private closed source project. | |||||
### Compiles and hot-reloads for development | |||||
``` | |||||
npm run serve | |||||
``` | |||||
## TODO | |||||
### Compiles and minifies for production | |||||
``` | |||||
npm run build | |||||
``` | |||||
### Lints and fixes files | |||||
``` | |||||
npm run lint | |||||
``` | |||||
### Customize configuration | |||||
See [Configuration Reference](https://cli.vuejs.org/config/). |
@@ -28,9 +28,6 @@ class UserController extends Controller | |||||
$user->save(); | $user->save(); | ||||
event(new Registered($user)); | event(new Registered($user)); | ||||
return response()->json([ | |||||
"status" => "success" | |||||
]); | |||||
} | } | ||||
public function forgotPassword(Request $request) { | public function forgotPassword(Request $request) { | ||||
@@ -44,8 +41,6 @@ class UserController extends Controller | |||||
return response()->json([ | return response()->json([ | ||||
"status" => "success" | "status" => "success" | ||||
]); | ]); | ||||
} else { | |||||
return response()->json($errors->all()); | |||||
} | } | ||||
} | } | ||||
@@ -68,8 +63,6 @@ class UserController extends Controller | |||||
return response()->json([ | return response()->json([ | ||||
"status" => "success" | "status" => "success" | ||||
]); | ]); | ||||
} else { | |||||
return response()->json([__($status)]); | |||||
} | } | ||||
} | } | ||||
@@ -82,7 +75,7 @@ class UserController extends Controller | |||||
return response()->json(["status" => "success"]); | return response()->json(["status" => "success"]); | ||||
} | } | ||||
return response()->json(["status" => "error"]); | |||||
/* return response()->json(["status" => "error"]); */ | |||||
} | } | ||||
@@ -0,0 +1,5 @@ | |||||
module.exports = { | |||||
presets: [ | |||||
'@vue/cli-plugin-babel/preset' | |||||
] | |||||
} |
@@ -0,0 +1,11 @@ | |||||
const vuePlugin = require('esbuild-vue'); | |||||
require('esbuild').build({ | |||||
entryPoints: ['resources/js/main.js'], | |||||
bundle: true, | |||||
outfile: 'public/main.js', | |||||
plugins: [vuePlugin()], | |||||
define: { | |||||
"process.env.NODE_ENV": JSON.stringify("development"), | |||||
}, | |||||
}); |
@@ -8,7 +8,7 @@ watch() { | |||||
sass --load-path=sass --watch --error-css $project/resources/scss:$project/public & | sass --load-path=sass --watch --error-css $project/resources/scss:$project/public & | ||||
pid="$pid $!" | pid="$pid $!" | ||||
trap "kill -TERM $pid" 0 1 2 15 | trap "kill -TERM $pid" 0 1 2 15 | ||||
find $project/resources/js -type f | entr -n esbuild --sourcemap --outdir=$project/public $project/resources/js/main.js & | |||||
find $project/resources/js -type f | entr -n node build.js & | |||||
pid="$pid $!" | pid="$pid $!" | ||||
trap "kill -TERM $pid" 0 1 2 15 | trap "kill -TERM $pid" 0 1 2 15 | ||||
wait | wait | ||||
@@ -1,21 +1,61 @@ | |||||
{ | { | ||||
"private": true, | |||||
"scripts": { | |||||
"dev": "npm run development", | |||||
"development": "mix", | |||||
"watch": "mix watch", | |||||
"watch-poll": "mix watch -- --watch-options-poll=1000", | |||||
"hot": "mix watch --hot", | |||||
"prod": "npm run production", | |||||
"production": "mix --production" | |||||
"name": "", | |||||
"version": "", | |||||
"private": true, | |||||
"scripts": { | |||||
"serve": "vue-cli-service serve", | |||||
"build": "vue-cli-service build resources/js/main.js", | |||||
"lint": "vue-cli-service lint", | |||||
"dev": "npm run development", | |||||
"development": "mix", | |||||
"hot": "mix watch --hot", | |||||
"prod": "npm run production", | |||||
"production": "mix --production", | |||||
"watch": "browser-sync start --proxy 'trendplays.test' -w --files resources", | |||||
"watch-poll": "mix watch -- --watch-options-poll=1000" | |||||
}, | |||||
"dependencies": { | |||||
"browser-sync": "^2.26.14", | |||||
"browsersync": "0.0.1-security", | |||||
"core-js": "^3.6.5", | |||||
"esbuild-vue": "^0.3.0", | |||||
"sass-loader": "^10.0.1", | |||||
"vue": "^3.0.11", | |||||
"webpack": "^4.36.2" | |||||
}, | |||||
"devDependencies": { | |||||
"@vue/cli-plugin-babel": "~4.5.0", | |||||
"@vue/cli-plugin-eslint": "~4.5.0", | |||||
"@vue/cli-service": "~4.5.0", | |||||
"@vue/compiler-sfc": "^3.0.0", | |||||
"axios": "^0.21", | |||||
"babel-eslint": "^10.1.0", | |||||
"esbuild": "^0.11.18", | |||||
"eslint": "^6.7.2", | |||||
"eslint-plugin-vue": "^7.0.0", | |||||
"laravel-mix": "^6.0.6", | |||||
"lodash": "^4.17.19", | |||||
"postcss": "^8.2.13" | |||||
}, | |||||
"eslintConfig": { | |||||
"root": true, | |||||
"env": { | |||||
"node": true | |||||
}, | }, | ||||
"devDependencies": { | |||||
"axios": "^0.21", | |||||
"laravel-mix": "^6.0.6", | |||||
"lodash": "^4.17.19", | |||||
"postcss": "^8.1.14" | |||||
"extends": [ | |||||
"plugin:vue/vue3-essential", | |||||
"eslint:recommended" | |||||
], | |||||
"parserOptions": { | |||||
"parser": "babel-eslint" | |||||
}, | }, | ||||
"dependencies": { | |||||
"browsersync": "0.0.1-security" | |||||
} | |||||
"rules": {} | |||||
}, | |||||
"browserslist": [ | |||||
"> 1%", | |||||
"last 2 versions", | |||||
"not dead" | |||||
], | |||||
"_id": "@", | |||||
"readme": "ERROR: No README data found!" | |||||
} | } |
@@ -1,21 +0,0 @@ | |||||
<IfModule mod_rewrite.c> | |||||
<IfModule mod_negotiation.c> | |||||
Options -MultiViews -Indexes | |||||
</IfModule> | |||||
RewriteEngine On | |||||
# Handle Authorization Header | |||||
RewriteCond %{HTTP:Authorization} . | |||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] | |||||
# Redirect Trailing Slashes If Not A Folder... | |||||
RewriteCond %{REQUEST_FILENAME} !-d | |||||
RewriteCond %{REQUEST_URI} (.+)/$ | |||||
RewriteRule ^ %1 [L,R=301] | |||||
# Send Requests To Front Controller... | |||||
RewriteCond %{REQUEST_FILENAME} !-d | |||||
RewriteCond %{REQUEST_FILENAME} !-f | |||||
RewriteRule ^ index.php [L] | |||||
</IfModule> |
@@ -1,94 +0,0 @@ | |||||
let heroText = document.querySelectorAll(".landing-hero h2,.landing-hero p"); | |||||
let registerToggles = document.querySelectorAll(".register-btn, .register-area .cancel-button, .services-cards button"); | |||||
let token = getCookie("XSRF-TOKEN"); | |||||
function getToken() { | |||||
fetch("/sanctum/csrf-cookie", { | |||||
method: "GET" | |||||
}); | |||||
token = getCookie("XSRF-TOKEN"); | |||||
} | |||||
function register(event) { | |||||
fetch("/register", { | |||||
method: "POST", | |||||
headers: { | |||||
"Content-Type": "application/json", | |||||
"X-XSRF-TOKEN": token | |||||
}, | |||||
body: JSON.stringify({ | |||||
name: document.getElementById("register-name").value, | |||||
email: document.getElementById("register-email").value, | |||||
password: document.getElementById("register-password").value | |||||
}), | |||||
password_confirmation: document.getElementById("confirm-password").value | |||||
}).then((response) => { | |||||
console.log(response.json()); | |||||
console.log(response.ok); | |||||
console.log(response.message); | |||||
}); | |||||
event.preventDefault(); | |||||
} | |||||
function getCookie(name) { | |||||
var re = new RegExp(name + "=([^;]+)"); | |||||
var value = re.exec(document.cookie); | |||||
return value != null ? unescape(value[1]) : null; | |||||
} | |||||
function checkPasswords() { | |||||
if (passInput.value != passInput2.value) { | |||||
passInput2.setCustomValidity("Passwords must be matching"); | |||||
} else { | |||||
passInput2.setCustomValidity(""); | |||||
} | |||||
} | |||||
function login(event) { | |||||
fetch("/login", { | |||||
method: "POST", | |||||
headers: { | |||||
"Content-Type": "application/json", | |||||
"X-XSRF-TOKEN": token | |||||
}, | |||||
body: JSON.stringify({ | |||||
email: document.getElementById("login_email").value, | |||||
password: document.getElementById("login_password").value | |||||
}) | |||||
}).then((response) => { | |||||
console.log(response.json()); | |||||
console.log(response.ok); | |||||
console.log(response.message); | |||||
}); | |||||
event.preventDefault(); | |||||
} | |||||
function resendLink(event) { | |||||
console.log("clicked"); | |||||
event.preventDefault(); | |||||
} | |||||
function toggleNav() { | |||||
heroText.forEach((item) => { | |||||
item.classList.toggle("hidden"); | |||||
}); | |||||
document.querySelector("nav form.login").classList.toggle("active"); | |||||
this.classList.toggle("toggled"); | |||||
} | |||||
if (!token) { | |||||
getToken(); | |||||
} | |||||
if (window.location.pathname == "/") { | |||||
document.getElementById("nav_toggle").addEventListener("click", toggleNav); | |||||
document.getElementById("register-form").addEventListener("submit", register); | |||||
document.getElementById("login_form").addEventListener("submit", login); | |||||
for (i = 0; i < registerToggles.length; i++) { | |||||
registerToggles[i].addEventListener("click", function() { | |||||
document.querySelector(".register-area").classList.toggle("active"); | |||||
}); | |||||
} | |||||
let cols = document.getElementsByClassName("collapsible"); | |||||
for (i = 0; i < cols.length; i++) { | |||||
cols[i].addEventListener("click", function() { | |||||
this.classList.toggle("active"); | |||||
}); | |||||
} | |||||
document.getElementById("register-password").oninput = checkPasswords; | |||||
document.getElementById("confirm-password").oninput = checkPasswords; | |||||
} else if (window.location.pathname == "/verify-email") { | |||||
document.getElementById("resend_verification").addEventListener("click", resendLink); | |||||
} | |||||
//# sourceMappingURL=main.js.map |
@@ -1,2 +0,0 @@ | |||||
User-agent: * | |||||
Disallow: |
@@ -1,28 +0,0 @@ | |||||
<!-- | |||||
Rewrites requires Microsoft URL Rewrite Module for IIS | |||||
Download: https://www.iis.net/downloads/microsoft/url-rewrite | |||||
Debug Help: https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-failed-request-tracing-to-trace-rewrite-rules | |||||
--> | |||||
<configuration> | |||||
<system.webServer> | |||||
<rewrite> | |||||
<rules> | |||||
<rule name="Imported Rule 1" stopProcessing="true"> | |||||
<match url="^(.*)/$" ignoreCase="false" /> | |||||
<conditions> | |||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" /> | |||||
</conditions> | |||||
<action type="Redirect" redirectType="Permanent" url="/{R:1}" /> | |||||
</rule> | |||||
<rule name="Imported Rule 2" stopProcessing="true"> | |||||
<match url="^" ignoreCase="false" /> | |||||
<conditions> | |||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" /> | |||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" /> | |||||
</conditions> | |||||
<action type="Rewrite" url="index.php" /> | |||||
</rule> | |||||
</rules> | |||||
</rewrite> | |||||
</system.webServer> | |||||
</configuration> |
@@ -0,0 +1,55 @@ | |||||
<?php | |||||
use Illuminate\Contracts\Http\Kernel; | |||||
use Illuminate\Http\Request; | |||||
define('LARAVEL_START', microtime(true)); | |||||
/* | |||||
|-------------------------------------------------------------------------- | |||||
| Check If The Application Is Under Maintenance | |||||
|-------------------------------------------------------------------------- | |||||
| | |||||
| If the application is in maintenance / demo mode via the "down" command | |||||
| we will load this file so that any pre-rendered content can be shown | |||||
| instead of starting the framework, which could cause an exception. | |||||
| | |||||
*/ | |||||
if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) { | |||||
require __DIR__.'/../storage/framework/maintenance.php'; | |||||
} | |||||
/* | |||||
|-------------------------------------------------------------------------- | |||||
| Register The Auto Loader | |||||
|-------------------------------------------------------------------------- | |||||
| | |||||
| Composer provides a convenient, automatically generated class loader for | |||||
| this application. We just need to utilize it! We'll simply require it | |||||
| into the script here so we don't need to manually load our classes. | |||||
| | |||||
*/ | |||||
require __DIR__.'/../vendor/autoload.php'; | |||||
/* | |||||
|-------------------------------------------------------------------------- | |||||
| Run The Application | |||||
|-------------------------------------------------------------------------- | |||||
| | |||||
| Once we have the application, we can handle the incoming request using | |||||
| the application's HTTP kernel. Then, we will send the response back | |||||
| to this client's browser, allowing them to enjoy our application. | |||||
| | |||||
*/ | |||||
$app = require_once __DIR__.'/../bootstrap/app.php'; | |||||
$kernel = $app->make(Kernel::class); | |||||
$response = tap($kernel->handle( | |||||
$request = Request::capture() | |||||
))->send(); | |||||
$kernel->terminate($request, $response); |
@@ -1,39 +1,38 @@ | |||||
import RegisterForm from './register-area/register-form.vue' | |||||
import '../scss/main.scss' | |||||
import { createApp } from 'vue' | |||||
let heroText = document.querySelectorAll(".landing-hero h2,.landing-hero p") | let heroText = document.querySelectorAll(".landing-hero h2,.landing-hero p") | ||||
let registerToggles = document.querySelectorAll(".register-btn, .register-area\ | let registerToggles = document.querySelectorAll(".register-btn, .register-area\ | ||||
.cancel-button, .services-cards button") | .cancel-button, .services-cards button") | ||||
let token = getCookie('XSRF-TOKEN') | let token = getCookie('XSRF-TOKEN') | ||||
window.token = token | |||||
const app = createApp(RegisterForm) | |||||
// app.component('register-form', { | |||||
// template: registerForm | |||||
// }) | |||||
app.mount('#app') | |||||
function getCookie(name) { | |||||
var re = new RegExp(name + "=([^;]+)") | |||||
var value = re.exec(document.cookie) | |||||
return (value != null) ? unescape(value[1]) : null | |||||
} | |||||
function getToken() { | function getToken() { | ||||
fetch("/sanctum/csrf-cookie", { | fetch("/sanctum/csrf-cookie", { | ||||
method: 'GET' | method: 'GET' | ||||
}).then( () => { | |||||
token = getCookie('XSRF-TOKEN') | |||||
}) | }) | ||||
token = getCookie('XSRF-TOKEN') | |||||
} | } | ||||
function register(event) { | |||||
fetch("/register", { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'X-XSRF-TOKEN': token}, | |||||
body: JSON.stringify({"name": document.getElementById("register-name").value, | |||||
"email": document.getElementById("register-email").value, | |||||
"password": document.getElementById("register-password").value}), | |||||
"password_confirmation": document.getElementById("confirm-password").value}) | |||||
.then(response => { | |||||
console.log(response.json()) | |||||
console.log(response.ok) | |||||
console.log(response.message) | |||||
}); | |||||
event.preventDefault(); | |||||
} | |||||
function getCookie(name) { | |||||
var re = new RegExp(name + "=([^;]+)"); | |||||
var value = re.exec(document.cookie); | |||||
return (value != null) ? unescape(value[1]) : null; | |||||
} | |||||
function checkPasswords() { | function checkPasswords() { | ||||
let passInput = document.getElementById('register-password') | |||||
let passInput2 = document.getElementById('confirm-password') | |||||
if (passInput.value != passInput2.value) { | if (passInput.value != passInput2.value) { | ||||
passInput2.setCustomValidity('Passwords must be matching') | passInput2.setCustomValidity('Passwords must be matching') | ||||
} else { | } else { | ||||
@@ -46,12 +45,14 @@ function login(event) { | |||||
method: 'POST', | method: 'POST', | ||||
headers: {'Content-Type': 'application/json', | headers: {'Content-Type': 'application/json', | ||||
'X-XSRF-TOKEN': token}, | 'X-XSRF-TOKEN': token}, | ||||
body: JSON.stringify({"email": document.getElementById("login_email").value, | |||||
body: JSON.stringify({"email": | |||||
document.getElementById("login_email").value, | |||||
"password": document.getElementById("login_password").value}), | "password": document.getElementById("login_password").value}), | ||||
}).then(response => { | }).then(response => { | ||||
console.log(response.json()) | console.log(response.json()) | ||||
console.log(response.ok) | console.log(response.ok) | ||||
console.log(response.message) | |||||
console.log(response.status) | |||||
console.log(response.statusText) | |||||
}) | }) | ||||
event.preventDefault(); | event.preventDefault(); | ||||
} | } | ||||
@@ -75,11 +76,10 @@ if (!token) {getToken()} | |||||
if (window.location.pathname == '/') { | if (window.location.pathname == '/') { | ||||
document.getElementById('nav_toggle').addEventListener('click', toggleNav) | document.getElementById('nav_toggle').addEventListener('click', toggleNav) | ||||
document.getElementById('register-form').addEventListener('submit', register) | |||||
document.getElementById('login_form').addEventListener('submit', login) | document.getElementById('login_form').addEventListener('submit', login) | ||||
//Triggers for registration menu | //Triggers for registration menu | ||||
for (i = 0; i < registerToggles.length; i++) { | |||||
for (let i = 0; i < registerToggles.length; i++) { | |||||
registerToggles[i].addEventListener("click", function() { | registerToggles[i].addEventListener("click", function() { | ||||
document.querySelector(".register-area").classList.toggle("active") | document.querySelector(".register-area").classList.toggle("active") | ||||
}); | }); | ||||
@@ -88,7 +88,7 @@ if (window.location.pathname == '/') { | |||||
//FAQ collapsibles | //FAQ collapsibles | ||||
let cols = document.getElementsByClassName("collapsible"); | let cols = document.getElementsByClassName("collapsible"); | ||||
for (i = 0; i < cols.length; i++) { | |||||
for (let i = 0; i < cols.length; i++) { | |||||
cols[i].addEventListener("click", function() { | cols[i].addEventListener("click", function() { | ||||
this.classList.toggle("active"); | this.classList.toggle("active"); | ||||
}); | }); | ||||
@@ -0,0 +1,53 @@ | |||||
<template> | |||||
<form v-on:submit="register" id="register-form" class="login" action=""> | |||||
<h3>Registration</h3> | |||||
<div> | |||||
<label for='sender_name'>Name</label> | |||||
<input id='register-name' required type='name' name='sender_name' placeholder='' | |||||
spellcheck='false'> | |||||
</div> | |||||
<div> | |||||
<label for='sender_email'>Email</label> | |||||
<input id='register-email' required type='email' name='sender_email' placeholder='' | |||||
spellcheck='false'> | |||||
</div> | |||||
<div> | |||||
<label for='sender_password'>Password</label> | |||||
<input id='register-password' required type='password' name='sender_password' | |||||
placeholder='' spellcheck='false'> | |||||
</div> | |||||
<div> | |||||
<label for='sender_password'>Confirm Password</label> | |||||
<input id='confirm-password' required type='password' | |||||
name='sender_password' placeholder='' spellcheck='false'> | |||||
</div> | |||||
<button class="submit-btn" type="submit">Submit</button> | |||||
<div class="cancel-button"></div> | |||||
</form> | |||||
</template> | |||||
<script> | |||||
function register(event) { | |||||
fetch("/register", { | |||||
method: 'POST', | |||||
headers: {'Content-Type': 'application/json', | |||||
'X-XSRF-TOKEN': window.token}, | |||||
body: JSON.stringify({"name": document.getElementById("register-name").value, | |||||
"email": document.getElementById("register-email").value, | |||||
"password": document.getElementById("register-password").value}), | |||||
"password_confirmation": document.getElementById("confirm-password").value}) | |||||
.then(response => { | |||||
console.log(response.json()) | |||||
console.log(response.ok) | |||||
console.log(response.message) | |||||
}); | |||||
event.preventDefault(); | |||||
} | |||||
/* document.getElementById('register-form').addEventListener('submit', register) */ | |||||
/* document.getElementById('register-form').addEventListener('submit', register) */ | |||||
module.exports = { | |||||
methods: { | |||||
register | |||||
} | |||||
} | |||||
</script> |
@@ -2,14 +2,7 @@ | |||||
@font-face { | @font-face { | ||||
font-family: "PatuaOne"; | font-family: "PatuaOne"; | ||||
src: url("PatuaOne-Regular.ttf") format("opentype"); | |||||
font-style: normal; | |||||
font-weight: normal; | |||||
} | |||||
@font-face { | |||||
font-family: "FreeSans"; | |||||
src: url("FreeSans.otf") format("opentype"); | |||||
src: url("../PatuaOne-Regular.ttf") format("opentype"); | |||||
font-style: normal; | font-style: normal; | ||||
font-weight: normal; | font-weight: normal; | ||||
} | } | ||||
@@ -0,0 +1 @@ | |||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Vue App</title><link href="/css/app.a9fd8694.css" rel="preload" as="style"><link href="/js/app.0225a575.js" rel="preload" as="script"><link href="/js/chunk-vendors.b6c5b7d4.js" rel="preload" as="script"><link href="/css/app.a9fd8694.css" rel="stylesheet"></head><body><div id="app"></div><script src="/js/chunk-vendors.b6c5b7d4.js"></script><script src="/js/app.0225a575.js"></script></body></html> |
@@ -4,41 +4,16 @@ | |||||
@section('head-metas') | @section('head-metas') | ||||
@parent | @parent | ||||
<link rel="stylesheet" href="{{asset('main.css')}}"> | |||||
<link rel="stylesheet" href="css/app.css"> | |||||
@endsection | @endsection | ||||
@section('content') | @section('content') | ||||
@parent | @parent | ||||
<main> | <main> | ||||
<div class="register-area"> | |||||
<h3>Registration</h3> | |||||
<form id="register-form" class="login" action=""> | |||||
@csrf | |||||
<div> | |||||
<label for='sender_name'>Name</label> | |||||
<input id='register-name' required type='name' name='sender_name' placeholder='' | |||||
spellcheck='false'> | |||||
</div> | |||||
<div> | |||||
<label for='sender_email'>Email</label> | |||||
<input id='register-email' required type='email' name='sender_email' placeholder='' | |||||
spellcheck='false'> | |||||
</div> | |||||
<div> | |||||
<label for='sender_password'>Password</label> | |||||
<input id='register-password' required type='password' name='sender_password' | |||||
placeholder='' spellcheck='false'> | |||||
</div> | |||||
<div> | |||||
<label for='sender_password'>Confirm Password</label> | |||||
<input id='confirm-password' required type='password' | |||||
name='sender_password' placeholder='' spellcheck='false'> | |||||
</div> | |||||
<button class="submit-btn" type="submit">Submit</button> | |||||
<div class="cancel-button"></div> | |||||
<object type="image/svg+xml" class="loading-icon" data="images/loading.svg" alt=""></object> | |||||
<svg data-set="loaders" data-loading="lazy" width="30px" height="30px" data-src="https://s2.svgbox.net/loaders.svg?ic=oval" data-icon="oval" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" color="" data-attributes-set="viewBox,xmlns,stroke,color" data-rendered="true"><g transform="translate(1 1)" stroke-width="2" fill="none" fill-rule="evenodd"><circle stroke-opacity=".5" cx="18" cy="18" r="18"></circle><path d="M36 18c0-9.94-8.06-18-18-18"><animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"></animateTransform></path></g></svg> | |||||
</form></div> | |||||
<div id="app" class="register-area"> | |||||
<Register-Form></Register-Form> | |||||
<img v-if="loading" type="image/svg+xml" class="loading-icon" src="images/loading.svg" alt=""/> | |||||
</div> | |||||
<div class='landing-hero'> | <div class='landing-hero'> | ||||
<div class='hero-filter'> | <div class='hero-filter'> | ||||
<h2>The web's #1 content promoter</h2> | <h2>The web's #1 content promoter</h2> | ||||
@@ -202,5 +177,7 @@ | |||||
@section('scripts') | @section('scripts') | ||||
@parent | @parent | ||||
<script src="main.js"></script> | |||||
<script src="js/app.js"></script> | |||||
<script src="js/chunk-vendors.js"></script> | |||||
<!-- <script src="main.js"></script> --> | |||||
@endsection | @endsection |
@@ -0,0 +1,19 @@ | |||||
Copyright (c) 2013-2017 Alexander <iam.asm89@gmail.com> | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is furnished | |||||
to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
THE SOFTWARE. |
@@ -0,0 +1,83 @@ | |||||
# Stack/Cors | |||||
Library and middleware enabling cross-origin resource sharing for your | |||||
http-{foundation,kernel} using application. It attempts to implement the | |||||
[W3C Recommendation] for cross-origin resource sharing. | |||||
[W3C Recommendation]: http://www.w3.org/TR/cors/ | |||||
Build status: ![.github/workflows/run-tests.yml](https://github.com/asm89/stack-cors/workflows/.github/workflows/run-tests.yml/badge.svg) | |||||
## Installation | |||||
Require `asm89/stack-cors` using composer. | |||||
## Usage | |||||
This package can be used as a library or as [stack middleware]. | |||||
[stack middleware]: http://stackphp.com/ | |||||
### Options | |||||
| Option | Description | Default value | | |||||
|------------------------|------------------------------------------------------------|---------------| | |||||
| allowedMethods | Matches the request method. | `array()` | | |||||
| allowedOrigins | Matches the request origin. | `array()` | | |||||
| allowedOriginsPatterns | Matches the request origin with `preg_match`. | `array()` | | |||||
| allowedHeaders | Sets the Access-Control-Allow-Headers response header. | `array()` | | |||||
| exposedHeaders | Sets the Access-Control-Expose-Headers response header. | `false` | | |||||
| maxAge | Sets the Access-Control-Max-Age response header. | `false` | | |||||
| supportsCredentials | Sets the Access-Control-Allow-Credentials header. | `false` | | |||||
The _allowedMethods_ and _allowedHeaders_ options are case-insensitive. | |||||
You don't need to provide both _allowedOrigins_ and _allowedOriginsPatterns_. If one of the strings passed matches, it is considered a valid origin. | |||||
If `array('*')` is provided to _allowedMethods_, _allowedOrigins_ or _allowedHeaders_ all methods / origins / headers are allowed. | |||||
### Example: using the library | |||||
```php | |||||
<?php | |||||
use Asm89\Stack\CorsService; | |||||
$cors = new CorsService(array( | |||||
'allowedHeaders' => array('x-allowed-header', 'x-other-allowed-header'), | |||||
'allowedMethods' => array('DELETE', 'GET', 'POST', 'PUT'), | |||||
'allowedOrigins' => array('http://localhost'), | |||||
'allowedOriginsPatterns' => array('/localhost:\d/'), | |||||
'exposedHeaders' => false, | |||||
'maxAge' => false, | |||||
'supportsCredentials' => false, | |||||
)); | |||||
$cors->addActualRequestHeaders(Response $response, $origin); | |||||
$cors->handlePreflightRequest(Request $request); | |||||
$cors->isActualRequestAllowed(Request $request); | |||||
$cors->isCorsRequest(Request $request); | |||||
$cors->isPreflightRequest(Request $request); | |||||
``` | |||||
## Example: using the stack middleware | |||||
```php | |||||
<?php | |||||
use Asm89\Stack\Cors; | |||||
$app = new Cors($app, array( | |||||
// you can use array('*') to allow any headers | |||||
'allowedHeaders' => array('x-allowed-header', 'x-other-allowed-header'), | |||||
// you can use array('*') to allow any methods | |||||
'allowedMethods' => array('DELETE', 'GET', 'POST', 'PUT'), | |||||
// you can use array('*') to allow requests from any origin | |||||
'allowedOrigins' => array('localhost'), | |||||
// you can enter regexes that are matched to the origin request header | |||||
'allowedOriginsPatterns' => array('/localhost:\d/'), | |||||
'exposedHeaders' => false, | |||||
'maxAge' => false, | |||||
'supportsCredentials' => false, | |||||
)); | |||||
``` |
@@ -0,0 +1,43 @@ | |||||
{ | |||||
"name": "asm89/stack-cors", | |||||
"description": "Cross-origin resource sharing library and stack middleware", | |||||
"keywords": ["stack", "cors"], | |||||
"homepage": "https://github.com/asm89/stack-cors", | |||||
"type": "library", | |||||
"license": "MIT", | |||||
"authors": [ | |||||
{ | |||||
"name": "Alexander", | |||||
"email": "iam.asm89@gmail.com" | |||||
} | |||||
], | |||||
"require": { | |||||
"php": "^7.0|^8.0", | |||||
"symfony/http-foundation": "~2.7|~3.0|~4.0|~5.0", | |||||
"symfony/http-kernel": "~2.7|~3.0|~4.0|~5.0" | |||||
}, | |||||
"require-dev": { | |||||
"phpunit/phpunit": "^6|^7|^8|^9", | |||||
"squizlabs/php_codesniffer": "^3.5" | |||||
}, | |||||
"autoload": { | |||||
"psr-4": { | |||||
"Asm89\\Stack\\": "src/" | |||||
} | |||||
}, | |||||
"autoload-dev": { | |||||
"psr-4": { | |||||
"Asm89\\Stack\\Tests\\": "tests/" | |||||
} | |||||
}, | |||||
"scripts": { | |||||
"test": "phpunit", | |||||
"check-style": "phpcs -p --standard=PSR12 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src", | |||||
"fix-style": "phpcbf -p --standard=PSR12 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src" | |||||
}, | |||||
"extra": { | |||||
"branch-alias": { | |||||
"dev-master": "2.0-dev" | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,61 @@ | |||||
<?php | |||||
/* | |||||
* This file is part of asm89/stack-cors. | |||||
* | |||||
* (c) Alexander <iam.asm89@gmail.com> | |||||
* | |||||
* For the full copyright and license information, please view the LICENSE | |||||
* file that was distributed with this source code. | |||||
*/ | |||||
namespace Asm89\Stack; | |||||
use Symfony\Component\HttpKernel\HttpKernelInterface; | |||||
use Symfony\Component\HttpFoundation\Request; | |||||
use Symfony\Component\HttpFoundation\Response; | |||||
class Cors implements HttpKernelInterface | |||||
{ | |||||
/** | |||||
* @var \Symfony\Component\HttpKernel\HttpKernelInterface | |||||
*/ | |||||
private $app; | |||||
/** | |||||
* @var \Asm89\Stack\CorsService | |||||
*/ | |||||
private $cors; | |||||
private $defaultOptions = array( | |||||
'allowedHeaders' => array(), | |||||
'allowedMethods' => array(), | |||||
'allowedOrigins' => array(), | |||||
'allowedOriginsPatterns' => array(), | |||||
'exposedHeaders' => array(), | |||||
'maxAge' => 0, | |||||
'supportsCredentials' => false, | |||||
); | |||||
public function __construct(HttpKernelInterface $app, array $options = array()) | |||||
{ | |||||
$this->app = $app; | |||||
$this->cors = new CorsService(array_merge($this->defaultOptions, $options)); | |||||
} | |||||
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) | |||||
{ | |||||
if ($this->cors->isPreflightRequest($request)) { | |||||
$response = $this->cors->handlePreflightRequest($request); | |||||
return $this->cors->varyHeader($response, 'Access-Control-Request-Method'); | |||||
} | |||||
$response = $this->app->handle($request, $type, $catch); | |||||
if ($request->getMethod() === 'OPTIONS') { | |||||
$this->cors->varyHeader($response, 'Access-Control-Request-Method'); | |||||
} | |||||
return $this->cors->addActualRequestHeaders($response, $request); | |||||
} | |||||
} |
@@ -0,0 +1,225 @@ | |||||
<?php | |||||
/* | |||||
* This file is part of asm89/stack-cors. | |||||
* | |||||
* (c) Alexander <iam.asm89@gmail.com> | |||||
* | |||||
* For the full copyright and license information, please view the LICENSE | |||||
* file that was distributed with this source code. | |||||
*/ | |||||
namespace Asm89\Stack; | |||||
use Symfony\Component\HttpFoundation\Request; | |||||
use Symfony\Component\HttpFoundation\Response; | |||||
class CorsService | |||||
{ | |||||
private $options; | |||||
public function __construct(array $options = array()) | |||||
{ | |||||
$this->options = $this->normalizeOptions($options); | |||||
} | |||||
private function normalizeOptions(array $options = array()): array | |||||
{ | |||||
$options += array( | |||||
'allowedOrigins' => array(), | |||||
'allowedOriginsPatterns' => array(), | |||||
'supportsCredentials' => false, | |||||
'allowedHeaders' => array(), | |||||
'exposedHeaders' => array(), | |||||
'allowedMethods' => array(), | |||||
'maxAge' => 0, | |||||
); | |||||
// normalize array('*') to true | |||||
if (in_array('*', $options['allowedOrigins'])) { | |||||
$options['allowedOrigins'] = true; | |||||
} | |||||
if (in_array('*', $options['allowedHeaders'])) { | |||||
$options['allowedHeaders'] = true; | |||||
} else { | |||||
$options['allowedHeaders'] = array_map('strtolower', $options['allowedHeaders']); | |||||
} | |||||
if (in_array('*', $options['allowedMethods'])) { | |||||
$options['allowedMethods'] = true; | |||||
} else { | |||||
$options['allowedMethods'] = array_map('strtoupper', $options['allowedMethods']); | |||||
} | |||||
return $options; | |||||
} | |||||
/** | |||||
* @deprecated use isOriginAllowed | |||||
*/ | |||||
public function isActualRequestAllowed(Request $request): bool | |||||
{ | |||||
return $this->isOriginAllowed($request); | |||||
} | |||||
public function isCorsRequest(Request $request): bool | |||||
{ | |||||
return $request->headers->has('Origin'); | |||||
} | |||||
public function isPreflightRequest(Request $request): bool | |||||
{ | |||||
return $request->getMethod() === 'OPTIONS' && $request->headers->has('Access-Control-Request-Method'); | |||||
} | |||||
public function handlePreflightRequest(Request $request): Response | |||||
{ | |||||
$response = new Response(); | |||||
$response->setStatusCode(204); | |||||
return $this->addPreflightRequestHeaders($response, $request); | |||||
} | |||||
public function addPreflightRequestHeaders(Response $response, Request $request): Response | |||||
{ | |||||
$this->configureAllowedOrigin($response, $request); | |||||
if ($response->headers->has('Access-Control-Allow-Origin')) { | |||||
$this->configureAllowCredentials($response, $request); | |||||
$this->configureAllowedMethods($response, $request); | |||||
$this->configureAllowedHeaders($response, $request); | |||||
$this->configureMaxAge($response, $request); | |||||
} | |||||
return $response; | |||||
} | |||||
public function isOriginAllowed(Request $request): bool | |||||
{ | |||||
if ($this->options['allowedOrigins'] === true) { | |||||
return true; | |||||
} | |||||
if (!$request->headers->has('Origin')) { | |||||
return false; | |||||
} | |||||
$origin = $request->headers->get('Origin'); | |||||
if (in_array($origin, $this->options['allowedOrigins'])) { | |||||
return true; | |||||
} | |||||
foreach ($this->options['allowedOriginsPatterns'] as $pattern) { | |||||
if (preg_match($pattern, $origin)) { | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
public function addActualRequestHeaders(Response $response, Request $request): Response | |||||
{ | |||||
$this->configureAllowedOrigin($response, $request); | |||||
if ($response->headers->has('Access-Control-Allow-Origin')) { | |||||
$this->configureAllowCredentials($response, $request); | |||||
$this->configureExposedHeaders($response, $request); | |||||
} | |||||
return $response; | |||||
} | |||||
private function configureAllowedOrigin(Response $response, Request $request) | |||||
{ | |||||
if ($this->options['allowedOrigins'] === true && !$this->options['supportsCredentials']) { | |||||
// Safe+cacheable, allow everything | |||||
$response->headers->set('Access-Control-Allow-Origin', '*'); | |||||
} elseif ($this->isSingleOriginAllowed()) { | |||||
// Single origins can be safely set | |||||
$response->headers->set('Access-Control-Allow-Origin', array_values($this->options['allowedOrigins'])[0]); | |||||
} else { | |||||
// For dynamic headers, check the origin first | |||||
if ($request->headers->has('Origin') && $this->isOriginAllowed($request)) { | |||||
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin')); | |||||
} | |||||
$this->varyHeader($response, 'Origin'); | |||||
} | |||||
} | |||||
private function isSingleOriginAllowed(): bool | |||||
{ | |||||
if ($this->options['allowedOrigins'] === true || !empty($this->options['allowedOriginsPatterns'])) { | |||||
return false; | |||||
} | |||||
return count($this->options['allowedOrigins']) === 1; | |||||
} | |||||
private function configureAllowedMethods(Response $response, Request $request) | |||||
{ | |||||
if ($this->options['allowedMethods'] === true) { | |||||
$allowMethods = strtoupper($request->headers->get('Access-Control-Request-Method')); | |||||
$this->varyHeader($response, 'Access-Control-Request-Method'); | |||||
} else { | |||||
$allowMethods = implode(', ', $this->options['allowedMethods']); | |||||
} | |||||
$response->headers->set('Access-Control-Allow-Methods', $allowMethods); | |||||
} | |||||
private function configureAllowedHeaders(Response $response, Request $request) | |||||
{ | |||||
if ($this->options['allowedHeaders'] === true) { | |||||
$allowHeaders = $request->headers->get('Access-Control-Request-Headers'); | |||||
$this->varyHeader($response, 'Access-Control-Request-Headers'); | |||||
} else { | |||||
$allowHeaders = implode(', ', $this->options['allowedHeaders']); | |||||
} | |||||
$response->headers->set('Access-Control-Allow-Headers', $allowHeaders); | |||||
} | |||||
private function configureAllowCredentials(Response $response, Request $request) | |||||
{ | |||||
if ($this->options['supportsCredentials']) { | |||||
$response->headers->set('Access-Control-Allow-Credentials', 'true'); | |||||
} | |||||
} | |||||
private function configureExposedHeaders(Response $response, Request $request) | |||||
{ | |||||
if ($this->options['exposedHeaders']) { | |||||
$response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->options['exposedHeaders'])); | |||||
} | |||||
} | |||||
private function configureMaxAge(Response $response, Request $request) | |||||
{ | |||||
if ($this->options['maxAge'] !== null) { | |||||
$response->headers->set('Access-Control-Max-Age', (int) $this->options['maxAge']); | |||||
} | |||||
} | |||||
public function varyHeader(Response $response, $header): Response | |||||
{ | |||||
if (!$response->headers->has('Vary')) { | |||||
$response->headers->set('Vary', $header); | |||||
} elseif (!in_array($header, explode(', ', $response->headers->get('Vary')))) { | |||||
$response->headers->set('Vary', $response->headers->get('Vary') . ', ' . $header); | |||||
} | |||||
return $response; | |||||
} | |||||
private function isSameHost(Request $request): bool | |||||
{ | |||||
return $request->headers->get('Origin') === $request->getSchemeAndHttpHost(); | |||||
} | |||||
} |
@@ -0,0 +1,7 @@ | |||||
<?php | |||||
// autoload.php @generated by Composer | |||||
require_once __DIR__ . '/composer/autoload_real.php'; | |||||
return ComposerAutoloaderInit8efbab2422760e769b32f8bfe0e3a518::getLoader(); |
@@ -0,0 +1 @@ | |||||
../nesbot/carbon/bin/carbon |
@@ -0,0 +1 @@ | |||||
../league/commonmark/bin/commonmark |
@@ -0,0 +1 @@ | |||||
../nikic/php-parser/bin/php-parse |
@@ -0,0 +1 @@ | |||||
../phpunit/phpunit/phpunit |
@@ -0,0 +1 @@ | |||||
../psy/psysh/bin/psysh |
@@ -0,0 +1 @@ | |||||
../laravel/sail/bin/sail |
@@ -0,0 +1 @@ | |||||
../symfony/var-dumper/Resources/bin/var-dump-server |
@@ -0,0 +1,403 @@ | |||||
# Changelog | |||||
All notable changes to this project will be documented in this file. | |||||
## [0.9.1](https://github.com/brick/math/releases/tag/0.9.1) - 2020-08-19 | |||||
✨ New features | |||||
- `BigInteger::not()` returns the bitwise `NOT` value | |||||
🐛 **Bug fixes** | |||||
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers | |||||
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available | |||||
## [0.9.0](https://github.com/brick/math/releases/tag/0.9.0) - 2020-08-18 | |||||
👌 **Improvements** | |||||
- `BigNumber::of()` now accepts `.123` and `123.` formats, both of which return a `BigDecimal` | |||||
💥 **Breaking changes** | |||||
- Deprecated method `BigInteger::powerMod()` has been removed - use `modPow()` instead | |||||
- Deprecated method `BigInteger::parse()` has been removed - use `fromBase()` instead | |||||
## [0.8.9](https://github.com/brick/math/releases/tag/0.8.9) - 2020-01-08 | |||||
⚡️ **Performance improvements** | |||||
A few additional optimizations in `BigInteger` and `BigDecimal` when one of the operands can be returned as is. Thanks to @tomtomsen in #24. | |||||
## [0.8.8](https://github.com/brick/math/releases/tag/0.8.8) - 2019-04-25 | |||||
🐛 **Bug fixes** | |||||
- `BigInteger::toBase()` could return an empty string for zero values (BCMath & Native calculators only, GMP calculator unaffected) | |||||
✨ **New features** | |||||
- `BigInteger::toArbitraryBase()` converts a number to an arbitrary base, using a custom alphabet | |||||
- `BigInteger::fromArbitraryBase()` converts a string in an arbitrary base, using a custom alphabet, back to a number | |||||
These methods can be used as the foundation to convert strings between different bases/alphabets, using BigInteger as an intermediate representation. | |||||
💩 **Deprecations** | |||||
- `BigInteger::parse()` is now deprecated in favour of `fromBase()` | |||||
`BigInteger::fromBase()` works the same way as `parse()`, with 2 minor differences: | |||||
- the `$base` parameter is required, it does not default to `10` | |||||
- it throws a `NumberFormatException` instead of an `InvalidArgumentException` when the number is malformed | |||||
## [0.8.7](https://github.com/brick/math/releases/tag/0.8.7) - 2019-04-20 | |||||
**Improvements** | |||||
- Safer conversion from `float` when using custom locales | |||||
- **Much faster** `NativeCalculator` implementation 🚀 | |||||
You can expect **at least a 3x performance improvement** for common arithmetic operations when using the library on systems without GMP or BCMath; it gets exponentially faster on multiplications with a high number of digits. This is due to calculations now being performed on whole blocks of digits (the block size depending on the platform, 32-bit or 64-bit) instead of digit-by-digit as before. | |||||
## [0.8.6](https://github.com/brick/math/releases/tag/0.8.6) - 2019-04-11 | |||||
**New method** | |||||
`BigNumber::sum()` returns the sum of one or more numbers. | |||||
## [0.8.5](https://github.com/brick/math/releases/tag/0.8.5) - 2019-02-12 | |||||
**Bug fix**: `of()` factory methods could fail when passing a `float` in environments using a `LC_NUMERIC` locale with a decimal separator other than `'.'` (#20). | |||||
Thanks @manowark 👍 | |||||
## [0.8.4](https://github.com/brick/math/releases/tag/0.8.4) - 2018-12-07 | |||||
**New method** | |||||
`BigDecimal::sqrt()` calculates the square root of a decimal number, to a given scale. | |||||
## [0.8.3](https://github.com/brick/math/releases/tag/0.8.3) - 2018-12-06 | |||||
**New method** | |||||
`BigInteger::sqrt()` calculates the square root of a number (thanks @peter279k). | |||||
**New exception** | |||||
`NegativeNumberException` is thrown when calling `sqrt()` on a negative number. | |||||
## [0.8.2](https://github.com/brick/math/releases/tag/0.8.2) - 2018-11-08 | |||||
**Performance update** | |||||
- Further improvement of `toInt()` performance | |||||
- `NativeCalculator` can now perform some multiplications more efficiently | |||||
## [0.8.17](https://github.com/brick/math/releases/tag/0.8.17) - 2020-08-19 | |||||
🐛 **Bug fix** | |||||
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers | |||||
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available | |||||
## [0.8.16](https://github.com/brick/math/releases/tag/0.8.16) - 2020-08-18 | |||||
🚑 **Critical fix** | |||||
- This version reintroduces the deprecated `BigInteger::parse()` method, that has been removed by mistake in version `0.8.9` and should have lasted for the whole `0.8` release cycle. | |||||
✨ **New features** | |||||
- `BigInteger::modInverse()` calculates a modular multiplicative inverse | |||||
- `BigInteger::fromBytes()` creates a `BigInteger` from a byte string | |||||
- `BigInteger::toBytes()` converts a `BigInteger` to a byte string | |||||
- `BigInteger::randomBits()` creates a pseudo-random `BigInteger` of a given bit length | |||||
- `BigInteger::randomRange()` creates a pseudo-random `BigInteger` between two bounds | |||||
💩 **Deprecations** | |||||
- `BigInteger::powerMod()` is now deprecated in favour of `modPow()` | |||||
## [0.8.15](https://github.com/brick/math/releases/tag/0.8.15) - 2020-04-15 | |||||
🐛 **Fixes** | |||||
- added missing `ext-json` requirement, due to `BigNumber` implementing `JsonSerializable` | |||||
⚡️ **Optimizations** | |||||
- additional optimization in `BigInteger::remainder()` | |||||
## [0.8.14](https://github.com/brick/math/releases/tag/0.8.14) - 2020-02-18 | |||||
✨ **New features** | |||||
- `BigInteger::getLowestSetBit()` returns the index of the rightmost one bit | |||||
## [0.8.13](https://github.com/brick/math/releases/tag/0.8.13) - 2020-02-16 | |||||
✨ **New features** | |||||
- `BigInteger::isEven()` tests whether the number is even | |||||
- `BigInteger::isOdd()` tests whether the number is odd | |||||
- `BigInteger::testBit()` tests if a bit is set | |||||
- `BigInteger::getBitLength()` returns the number of bits in the minimal representation of the number | |||||
## [0.8.12](https://github.com/brick/math/releases/tag/0.8.12) - 2020-02-03 | |||||
🛠️ **Maintenance release** | |||||
Classes are now annotated for better static analysis with [psalm](https://psalm.dev/). | |||||
This is a maintenance release: no bug fixes, no new features, no breaking changes. | |||||
## [0.8.11](https://github.com/brick/math/releases/tag/0.8.11) - 2020-01-23 | |||||
✨ **New feature** | |||||
`BigInteger::powerMod()` performs a power-with-modulo operation. Useful for crypto. | |||||
## [0.8.10](https://github.com/brick/math/releases/tag/0.8.10) - 2020-01-21 | |||||
✨ **New feature** | |||||
`BigInteger::mod()` returns the **modulo** of two numbers. The *modulo* differs from the *remainder* when the signs of the operands are different. | |||||
## [0.8.1](https://github.com/brick/math/releases/tag/0.8.1) - 2018-11-07 | |||||
Performance optimization of `toInt()` methods. | |||||
## [0.8.0](https://github.com/brick/math/releases/tag/0.8.0) - 2018-10-13 | |||||
**Breaking changes** | |||||
The following deprecated methods have been removed. Use the new method name instead: | |||||
| Method removed | Replacement method | | |||||
| --- | --- | | |||||
| `BigDecimal::getIntegral()` | `BigDecimal::getIntegralPart()` | | |||||
| `BigDecimal::getFraction()` | `BigDecimal::getFractionalPart()` | | |||||
--- | |||||
**New features** | |||||
`BigInteger` has been augmented with 5 new methods for bitwise operations: | |||||
| New method | Description | | |||||
| --- | --- | | |||||
| `and()` | performs a bitwise `AND` operation on two numbers | | |||||
| `or()` | performs a bitwise `OR` operation on two numbers | | |||||
| `xor()` | performs a bitwise `XOR` operation on two numbers | | |||||
| `shiftedLeft()` | returns the number shifted left by a number of bits | | |||||
| `shiftedRight()` | returns the number shifted right by a number of bits | | |||||
Thanks to @DASPRiD 👍 | |||||
## [0.7.3](https://github.com/brick/math/releases/tag/0.7.3) - 2018-08-20 | |||||
**New method:** `BigDecimal::hasNonZeroFractionalPart()` | |||||
**Renamed/deprecated methods:** | |||||
- `BigDecimal::getIntegral()` has been renamed to `getIntegralPart()` and is now deprecated | |||||
- `BigDecimal::getFraction()` has been renamed to `getFractionalPart()` and is now deprecated | |||||
## [0.7.2](https://github.com/brick/math/releases/tag/0.7.2) - 2018-07-21 | |||||
**Performance update** | |||||
`BigInteger::parse()` and `toBase()` now use GMP's built-in base conversion features when available. | |||||
## [0.7.1](https://github.com/brick/math/releases/tag/0.7.1) - 2018-03-01 | |||||
This is a maintenance release, no code has been changed. | |||||
- When installed with `--no-dev`, the autoloader does not autoload tests anymore | |||||
- Tests and other files unnecessary for production are excluded from the dist package | |||||
This will help make installations more compact. | |||||
## [0.7.0](https://github.com/brick/math/releases/tag/0.7.0) - 2017-10-02 | |||||
Methods renamed: | |||||
- `BigNumber:sign()` has been renamed to `getSign()` | |||||
- `BigDecimal::unscaledValue()` has been renamed to `getUnscaledValue()` | |||||
- `BigDecimal::scale()` has been renamed to `getScale()` | |||||
- `BigDecimal::integral()` has been renamed to `getIntegral()` | |||||
- `BigDecimal::fraction()` has been renamed to `getFraction()` | |||||
- `BigRational::numerator()` has been renamed to `getNumerator()` | |||||
- `BigRational::denominator()` has been renamed to `getDenominator()` | |||||
Classes renamed: | |||||
- `ArithmeticException` has been renamed to `MathException` | |||||
## [0.6.2](https://github.com/brick/math/releases/tag/0.6.2) - 2017-10-02 | |||||
The base class for all exceptions is now `MathException`. | |||||
`ArithmeticException` has been deprecated, and will be removed in 0.7.0. | |||||
## [0.6.1](https://github.com/brick/math/releases/tag/0.6.1) - 2017-10-02 | |||||
A number of methods have been renamed: | |||||
- `BigNumber:sign()` is deprecated; use `getSign()` instead | |||||
- `BigDecimal::unscaledValue()` is deprecated; use `getUnscaledValue()` instead | |||||
- `BigDecimal::scale()` is deprecated; use `getScale()` instead | |||||
- `BigDecimal::integral()` is deprecated; use `getIntegral()` instead | |||||
- `BigDecimal::fraction()` is deprecated; use `getFraction()` instead | |||||
- `BigRational::numerator()` is deprecated; use `getNumerator()` instead | |||||
- `BigRational::denominator()` is deprecated; use `getDenominator()` instead | |||||
The old methods will be removed in version 0.7.0. | |||||
## [0.6.0](https://github.com/brick/math/releases/tag/0.6.0) - 2017-08-25 | |||||
- Minimum PHP version is now [7.1](https://gophp71.org/); for PHP 5.6 and PHP 7.0 support, use version `0.5` | |||||
- Deprecated method `BigDecimal::withScale()` has been removed; use `toScale()` instead | |||||
- Method `BigNumber::toInteger()` has been renamed to `toInt()` | |||||
## [0.5.4](https://github.com/brick/math/releases/tag/0.5.4) - 2016-10-17 | |||||
`BigNumber` classes now implement [JsonSerializable](http://php.net/manual/en/class.jsonserializable.php). | |||||
The JSON output is always a string. | |||||
## [0.5.3](https://github.com/brick/math/releases/tag/0.5.3) - 2016-03-31 | |||||
This is a bugfix release. Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6. | |||||
## [0.5.2](https://github.com/brick/math/releases/tag/0.5.2) - 2015-08-06 | |||||
The `$scale` parameter of `BigDecimal::dividedBy()` is now optional again. | |||||
## [0.5.1](https://github.com/brick/math/releases/tag/0.5.1) - 2015-07-05 | |||||
**New method: `BigNumber::toScale()`** | |||||
This allows to convert any `BigNumber` to a `BigDecimal` with a given scale, using rounding if necessary. | |||||
## [0.5.0](https://github.com/brick/math/releases/tag/0.5.0) - 2015-07-04 | |||||
**New features** | |||||
- Common `BigNumber` interface for all classes, with the following methods: | |||||
- `sign()` and derived methods (`isZero()`, `isPositive()`, ...) | |||||
- `compareTo()` and derived methods (`isEqualTo()`, `isGreaterThan()`, ...) that work across different `BigNumber` types | |||||
- `toBigInteger()`, `toBigDecimal()`, `toBigRational`() conversion methods | |||||
- `toInteger()` and `toFloat()` conversion methods to native types | |||||
- Unified `of()` behaviour: every class now accepts any type of number, provided that it can be safely converted to the current type | |||||
- New method: `BigDecimal::exactlyDividedBy()`; this method automatically computes the scale of the result, provided that the division yields a finite number of digits | |||||
- New methods: `BigRational::quotient()` and `remainder()` | |||||
- Fine-grained exceptions: `DivisionByZeroException`, `RoundingNecessaryException`, `NumberFormatException` | |||||
- Factory methods `zero()`, `one()` and `ten()` available in all classes | |||||
- Rounding mode reintroduced in `BigInteger::dividedBy()` | |||||
This release also comes with many performance improvements. | |||||
--- | |||||
**Breaking changes** | |||||
- `BigInteger`: | |||||
- `getSign()` is renamed to `sign()` | |||||
- `toString()` is renamed to `toBase()` | |||||
- `BigInteger::dividedBy()` now throws an exception by default if the remainder is not zero; use `quotient()` to get the previous behaviour | |||||
- `BigDecimal`: | |||||
- `getSign()` is renamed to `sign()` | |||||
- `getUnscaledValue()` is renamed to `unscaledValue()` | |||||
- `getScale()` is renamed to `scale()` | |||||
- `getIntegral()` is renamed to `integral()` | |||||
- `getFraction()` is renamed to `fraction()` | |||||
- `divideAndRemainder()` is renamed to `quotientAndRemainder()` | |||||
- `dividedBy()` now takes a **mandatory** `$scale` parameter **before** the rounding mode | |||||
- `toBigInteger()` does not accept a `$roundingMode` parameter any more | |||||
- `toBigRational()` does not simplify the fraction any more; explicitly add `->simplified()` to get the previous behaviour | |||||
- `BigRational`: | |||||
- `getSign()` is renamed to `sign()` | |||||
- `getNumerator()` is renamed to `numerator()` | |||||
- `getDenominator()` is renamed to `denominator()` | |||||
- `of()` is renamed to `nd()`, while `parse()` is renamed to `of()` | |||||
- Miscellaneous: | |||||
- `ArithmeticException` is moved to an `Exception\` sub-namespace | |||||
- `of()` factory methods now throw `NumberFormatException` instead of `InvalidArgumentException` | |||||
## [0.4.3](https://github.com/brick/math/releases/tag/0.4.3) - 2016-03-31 | |||||
Backport of two bug fixes from the 0.5 branch: | |||||
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected | |||||
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6. | |||||
## [0.4.2](https://github.com/brick/math/releases/tag/0.4.2) - 2015-06-16 | |||||
New method: `BigDecimal::stripTrailingZeros()` | |||||
## [0.4.1](https://github.com/brick/math/releases/tag/0.4.1) - 2015-06-12 | |||||
Introducing a `BigRational` class, to perform calculations on fractions of any size. | |||||
## [0.4.0](https://github.com/brick/math/releases/tag/0.4.0) - 2015-06-12 | |||||
Rounding modes have been removed from `BigInteger`, and are now a concept specific to `BigDecimal`. | |||||
`BigInteger::dividedBy()` now always returns the quotient of the division. | |||||
## [0.3.5](https://github.com/brick/math/releases/tag/0.3.5) - 2016-03-31 | |||||
Backport of two bug fixes from the 0.5 branch: | |||||
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected | |||||
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6. | |||||
## [0.3.4](https://github.com/brick/math/releases/tag/0.3.4) - 2015-06-11 | |||||
New methods: | |||||
- `BigInteger::remainder()` returns the remainder of a division only | |||||
- `BigInteger::gcd()` returns the greatest common divisor of two numbers | |||||
## [0.3.3](https://github.com/brick/math/releases/tag/0.3.3) - 2015-06-07 | |||||
Fix `toString()` not handling negative numbers. | |||||
## [0.3.2](https://github.com/brick/math/releases/tag/0.3.2) - 2015-06-07 | |||||
`BigInteger` and `BigDecimal` now have a `getSign()` method that returns: | |||||
- `-1` if the number is negative | |||||
- `0` if the number is zero | |||||
- `1` if the number is positive | |||||
## [0.3.1](https://github.com/brick/math/releases/tag/0.3.1) - 2015-06-05 | |||||
Minor performance improvements | |||||
## [0.3.0](https://github.com/brick/math/releases/tag/0.3.0) - 2015-06-04 | |||||
The `$roundingMode` and `$scale` parameters have been swapped in `BigDecimal::dividedBy()`. | |||||
## [0.2.2](https://github.com/brick/math/releases/tag/0.2.2) - 2015-06-04 | |||||
Stronger immutability guarantee for `BigInteger` and `BigDecimal`. | |||||
So far, it would have been possible to break immutability of these classes by calling the `unserialize()` internal function. This release fixes that. | |||||
## [0.2.1](https://github.com/brick/math/releases/tag/0.2.1) - 2015-06-02 | |||||
Added `BigDecimal::divideAndRemainder()` | |||||
## [0.2.0](https://github.com/brick/math/releases/tag/0.2.0) - 2015-05-22 | |||||
- `min()` and `max()` do not accept an `array` any more, but a variable number of parameters | |||||
- **minimum PHP version is now 5.6** | |||||
- continuous integration with PHP 7 | |||||
## [0.1.1](https://github.com/brick/math/releases/tag/0.1.1) - 2014-09-01 | |||||
- Added `BigInteger::power()` | |||||
- Added HHVM support | |||||
## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31 | |||||
First beta release. | |||||
@@ -0,0 +1,20 @@ | |||||
The MIT License (MIT) | |||||
Copyright (c) 2013-present Benjamin Morel | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy of | |||||
this software and associated documentation files (the "Software"), to deal in | |||||
the Software without restriction, including without limitation the rights to | |||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of | |||||
the Software, and to permit persons to whom the Software is furnished to do so, | |||||
subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS | |||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR | |||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@@ -0,0 +1,17 @@ | |||||
# Security Policy | |||||
## Supported Versions | |||||
Only the last two release streams are supported. | |||||
| Version | Supported | | |||||
| ------- | ------------------ | | |||||
| 0.9.x | :white_check_mark: | | |||||
| 0.8.x | :white_check_mark: | | |||||
| < 0.8 | :x: | | |||||
## Reporting a Vulnerability | |||||
To report a security vulnerability, please use the | |||||
[Tidelift security contact](https://tidelift.com/security). | |||||
Tidelift will coordinate the fix and disclosure. |
@@ -0,0 +1,35 @@ | |||||
{ | |||||
"name": "brick/math", | |||||
"description": "Arbitrary-precision arithmetic library", | |||||
"type": "library", | |||||
"keywords": [ | |||||
"Brick", | |||||
"Math", | |||||
"Arbitrary-precision", | |||||
"Arithmetic", | |||||
"BigInteger", | |||||
"BigDecimal", | |||||
"BigRational", | |||||
"Bignum" | |||||
], | |||||
"license": "MIT", | |||||
"require": { | |||||
"php": "^7.1 || ^8.0", | |||||
"ext-json": "*" | |||||
}, | |||||
"require-dev": { | |||||
"phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", | |||||
"php-coveralls/php-coveralls": "^2.2", | |||||
"vimeo/psalm": "4.3.2" | |||||
}, | |||||
"autoload": { | |||||
"psr-4": { | |||||
"Brick\\Math\\": "src/" | |||||
} | |||||
}, | |||||
"autoload-dev": { | |||||
"psr-4": { | |||||
"Brick\\Math\\Tests\\": "tests/" | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,194 @@ | |||||
<?php | |||||
/** | |||||
* This script stress tests calculators with random large numbers and ensures that all implementations return the same | |||||
* results. It is designed to run in an infinite loop unless a bug is found. | |||||
*/ | |||||
declare(strict_types=1); | |||||
require __DIR__ . '/vendor/autoload.php'; | |||||
use Brick\Math\Internal\Calculator; | |||||
(new class(30) { // max digits | |||||
private $gmp; | |||||
private $bcmath; | |||||
private $native; | |||||
private $maxDigits; | |||||
public function __construct(int $maxDigits) | |||||
{ | |||||
$this->gmp = new Calculator\GmpCalculator(); | |||||
$this->bcmath = new Calculator\BcMathCalculator(); | |||||
$this->native = new Calculator\NativeCalculator(); | |||||
$this->maxDigits = $maxDigits; | |||||
} | |||||
public function __invoke() : void | |||||
{ | |||||
for (;;) { | |||||
$a = $this->generateRandomNumber(); | |||||
$b = $this->generateRandomNumber(); | |||||
$c = $this->generateRandomNumber(); | |||||
$this->runTests($a, $b); | |||||
$this->runTests($b, $a); | |||||
if ($a !== '0') { | |||||
$this->runTests("-$a", $b); | |||||
$this->runTests($b, "-$a"); | |||||
} | |||||
if ($b !== '0') { | |||||
$this->runTests($a, "-$b"); | |||||
$this->runTests("-$b", $a); | |||||
} | |||||
if ($a !== '0' && $b !== '0') { | |||||
$this->runTests("-$a", "-$b"); | |||||
$this->runTests("-$b", "-$a"); | |||||
} | |||||
if ($c !== '0') { | |||||
$this->test("$a POW $b MOD $c", function(Calculator $calc) use($a, $b, $c) { | |||||
return $calc->modPow($a, $b, $c); | |||||
}); | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* @param string $a The left operand. | |||||
* @param string $b The right operand. | |||||
*/ | |||||
private function runTests(string $a, string $b) : void | |||||
{ | |||||
$this->test("$a + $b", function(Calculator $c) use($a, $b) { | |||||
return $c->add($a, $b); | |||||
}); | |||||
$this->test("$a - $b", function(Calculator $c) use($a, $b) { | |||||
return $c->sub($a, $b); | |||||
}); | |||||
$this->test("$a * $b", function(Calculator $c) use($a, $b) { | |||||
return $c->mul($a, $b); | |||||
}); | |||||
if ($b !== '0') { | |||||
$this->test("$a / $b", function(Calculator $c) use($a, $b) { | |||||
return $c->divQR($a, $b); | |||||
}); | |||||
$this->test("$a MOD $b", function(Calculator $c) use($a, $b) { | |||||
return $c->mod($a, $b); | |||||
}); | |||||
} | |||||
if ($b !== '0' && $b[0] !== '-') { | |||||
$this->test("INV $a MOD $b", function(Calculator $c) use($a, $b) { | |||||
return $c->modInverse($a, $b); | |||||
}); | |||||
} | |||||
$this->test("GCD $a, $b", function(Calculator $c) use($a, $b) { | |||||
return $c->gcd($a, $b); | |||||
}); | |||||
if ($a[0] !== '-') { | |||||
$this->test("SQRT $a", function(Calculator $c) use($a, $b) { | |||||
return $c->sqrt($a); | |||||
}); | |||||
} | |||||
$this->test("$a AND $b", function(Calculator $c) use($a, $b) { | |||||
return $c->and($a, $b); | |||||
}); | |||||
$this->test("$a OR $b", function(Calculator $c) use($a, $b) { | |||||
return $c->or($a, $b); | |||||
}); | |||||
$this->test("$a XOR $b", function(Calculator $c) use($a, $b) { | |||||
return $c->xor($a, $b); | |||||
}); | |||||
} | |||||
/** | |||||
* @param string $test A string representing the test being executed. | |||||
* @param Closure $callback A callback function accepting a Calculator instance and returning a calculation result. | |||||
*/ | |||||
private function test(string $test, Closure $callback) : void | |||||
{ | |||||
static $testCounter = 0; | |||||
static $lastOutputTime = 0.0; | |||||
static $currentSecond = 0; | |||||
static $currentSecondTestCounter = 0; | |||||
static $testsPerSecond = 0; | |||||
$gmpResult = $callback($this->gmp); | |||||
$bcmathResult = $callback($this->bcmath); | |||||
$nativeResult = $callback($this->native); | |||||
if ($gmpResult !== $bcmathResult) { | |||||
self::failure('GMP', 'BCMath', $test); | |||||
} | |||||
if ($gmpResult !== $nativeResult) { | |||||
self::failure('GMP', 'Native', $test); | |||||
} | |||||
$testCounter++; | |||||
$currentSecondTestCounter++; | |||||
$time = microtime(true); | |||||
$second = (int) $time; | |||||
if ($second !== $currentSecond) { | |||||
$currentSecond = $second; | |||||
$testsPerSecond = $currentSecondTestCounter; | |||||
$currentSecondTestCounter = 0; | |||||
} | |||||
if ($time - $lastOutputTime >= 0.1) { | |||||
echo "\r", number_format($testCounter), ' (', number_format($testsPerSecond) . ' / s)'; | |||||
$lastOutputTime = $time; | |||||
} | |||||
} | |||||
/** | |||||
* @param string $c1 The name of the first calculator. | |||||
* @param string $c2 The name of the second calculator. | |||||
* @param string $test A string representing the test being executed. | |||||
*/ | |||||
private static function failure(string $c1, string $c2, string $test) : void | |||||
{ | |||||
echo PHP_EOL; | |||||
echo 'FAILURE!', PHP_EOL; | |||||
echo $c1, ' vs ', $c2, PHP_EOL; | |||||
echo $test, PHP_EOL; | |||||
die; | |||||
} | |||||
private function generateRandomNumber() : string | |||||
{ | |||||
$length = random_int(1, $this->maxDigits); | |||||
$number = ''; | |||||
for ($i = 0; $i < $length; $i++) { | |||||
$number .= random_int(0, 9); | |||||
} | |||||
$number = ltrim($number, '0'); | |||||
if ($number === '') { | |||||
return '0'; | |||||
} | |||||
return $number; | |||||
} | |||||
})(); |
@@ -0,0 +1,861 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math; | |||||
use Brick\Math\Exception\DivisionByZeroException; | |||||
use Brick\Math\Exception\MathException; | |||||
use Brick\Math\Exception\NegativeNumberException; | |||||
use Brick\Math\Internal\Calculator; | |||||
/** | |||||
* Immutable, arbitrary-precision signed decimal numbers. | |||||
* | |||||
* @psalm-immutable | |||||
*/ | |||||
final class BigDecimal extends BigNumber | |||||
{ | |||||
/** | |||||
* The unscaled value of this decimal number. | |||||
* | |||||
* This is a string of digits with an optional leading minus sign. | |||||
* No leading zero must be present. | |||||
* No leading minus sign must be present if the value is 0. | |||||
* | |||||
* @var string | |||||
*/ | |||||
private $value; | |||||
/** | |||||
* The scale (number of digits after the decimal point) of this decimal number. | |||||
* | |||||
* This must be zero or more. | |||||
* | |||||
* @var int | |||||
*/ | |||||
private $scale; | |||||
/** | |||||
* Protected constructor. Use a factory method to obtain an instance. | |||||
* | |||||
* @param string $value The unscaled value, validated. | |||||
* @param int $scale The scale, validated. | |||||
*/ | |||||
protected function __construct(string $value, int $scale = 0) | |||||
{ | |||||
$this->value = $value; | |||||
$this->scale = $scale; | |||||
} | |||||
/** | |||||
* Creates a BigDecimal of the given value. | |||||
* | |||||
* @param BigNumber|int|float|string $value | |||||
* | |||||
* @return BigDecimal | |||||
* | |||||
* @throws MathException If the value cannot be converted to a BigDecimal. | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function of($value) : BigNumber | |||||
{ | |||||
return parent::of($value)->toBigDecimal(); | |||||
} | |||||
/** | |||||
* Creates a BigDecimal from an unscaled value and a scale. | |||||
* | |||||
* Example: `(12345, 3)` will result in the BigDecimal `12.345`. | |||||
* | |||||
* @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger. | |||||
* @param int $scale The scale of the number, positive or zero. | |||||
* | |||||
* @return BigDecimal | |||||
* | |||||
* @throws \InvalidArgumentException If the scale is negative. | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function ofUnscaledValue($value, int $scale = 0) : BigDecimal | |||||
{ | |||||
if ($scale < 0) { | |||||
throw new \InvalidArgumentException('The scale cannot be negative.'); | |||||
} | |||||
return new BigDecimal((string) BigInteger::of($value), $scale); | |||||
} | |||||
/** | |||||
* Returns a BigDecimal representing zero, with a scale of zero. | |||||
* | |||||
* @return BigDecimal | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function zero() : BigDecimal | |||||
{ | |||||
/** | |||||
* @psalm-suppress ImpureStaticVariable | |||||
* @var BigDecimal|null $zero | |||||
*/ | |||||
static $zero; | |||||
if ($zero === null) { | |||||
$zero = new BigDecimal('0'); | |||||
} | |||||
return $zero; | |||||
} | |||||
/** | |||||
* Returns a BigDecimal representing one, with a scale of zero. | |||||
* | |||||
* @return BigDecimal | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function one() : BigDecimal | |||||
{ | |||||
/** | |||||
* @psalm-suppress ImpureStaticVariable | |||||
* @var BigDecimal|null $one | |||||
*/ | |||||
static $one; | |||||
if ($one === null) { | |||||
$one = new BigDecimal('1'); | |||||
} | |||||
return $one; | |||||
} | |||||
/** | |||||
* Returns a BigDecimal representing ten, with a scale of zero. | |||||
* | |||||
* @return BigDecimal | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function ten() : BigDecimal | |||||
{ | |||||
/** | |||||
* @psalm-suppress ImpureStaticVariable | |||||
* @var BigDecimal|null $ten | |||||
*/ | |||||
static $ten; | |||||
if ($ten === null) { | |||||
$ten = new BigDecimal('10'); | |||||
} | |||||
return $ten; | |||||
} | |||||
/** | |||||
* Returns the sum of this number and the given one. | |||||
* | |||||
* The result has a scale of `max($this->scale, $that->scale)`. | |||||
* | |||||
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal. | |||||
* | |||||
* @return BigDecimal The result. | |||||
* | |||||
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal. | |||||
*/ | |||||
public function plus($that) : BigDecimal | |||||
{ | |||||
$that = BigDecimal::of($that); | |||||
if ($that->value === '0' && $that->scale <= $this->scale) { | |||||
return $this; | |||||
} | |||||
if ($this->value === '0' && $this->scale <= $that->scale) { | |||||
return $that; | |||||
} | |||||
[$a, $b] = $this->scaleValues($this, $that); | |||||
$value = Calculator::get()->add($a, $b); | |||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale; | |||||
return new BigDecimal($value, $scale); | |||||
} | |||||
/** | |||||
* Returns the difference of this number and the given one. | |||||
* | |||||
* The result has a scale of `max($this->scale, $that->scale)`. | |||||
* | |||||
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal. | |||||
* | |||||
* @return BigDecimal The result. | |||||
* | |||||
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal. | |||||
*/ | |||||
public function minus($that) : BigDecimal | |||||
{ | |||||
$that = BigDecimal::of($that); | |||||
if ($that->value === '0' && $that->scale <= $this->scale) { | |||||
return $this; | |||||
} | |||||
[$a, $b] = $this->scaleValues($this, $that); | |||||
$value = Calculator::get()->sub($a, $b); | |||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale; | |||||
return new BigDecimal($value, $scale); | |||||
} | |||||
/** | |||||
* Returns the product of this number and the given one. | |||||
* | |||||
* The result has a scale of `$this->scale + $that->scale`. | |||||
* | |||||
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal. | |||||
* | |||||
* @return BigDecimal The result. | |||||
* | |||||
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal. | |||||
*/ | |||||
public function multipliedBy($that) : BigDecimal | |||||
{ | |||||
$that = BigDecimal::of($that); | |||||
if ($that->value === '1' && $that->scale === 0) { | |||||
return $this; | |||||
} | |||||
if ($this->value === '1' && $this->scale === 0) { | |||||
return $that; | |||||
} | |||||
$value = Calculator::get()->mul($this->value, $that->value); | |||||
$scale = $this->scale + $that->scale; | |||||
return new BigDecimal($value, $scale); | |||||
} | |||||
/** | |||||
* Returns the result of the division of this number by the given one, at the given scale. | |||||
* | |||||
* @param BigNumber|int|float|string $that The divisor. | |||||
* @param int|null $scale The desired scale, or null to use the scale of this number. | |||||
* @param int $roundingMode An optional rounding mode. | |||||
* | |||||
* @return BigDecimal | |||||
* | |||||
* @throws \InvalidArgumentException If the scale or rounding mode is invalid. | |||||
* @throws MathException If the number is invalid, is zero, or rounding was necessary. | |||||
*/ | |||||
public function dividedBy($that, ?int $scale = null, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal | |||||
{ | |||||
$that = BigDecimal::of($that); | |||||
if ($that->isZero()) { | |||||
throw DivisionByZeroException::divisionByZero(); | |||||
} | |||||
if ($scale === null) { | |||||
$scale = $this->scale; | |||||
} elseif ($scale < 0) { | |||||
throw new \InvalidArgumentException('Scale cannot be negative.'); | |||||
} | |||||
if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) { | |||||
return $this; | |||||
} | |||||
$p = $this->valueWithMinScale($that->scale + $scale); | |||||
$q = $that->valueWithMinScale($this->scale - $scale); | |||||
$result = Calculator::get()->divRound($p, $q, $roundingMode); | |||||
return new BigDecimal($result, $scale); | |||||
} | |||||
/** | |||||
* Returns the exact result of the division of this number by the given one. | |||||
* | |||||
* The scale of the result is automatically calculated to fit all the fraction digits. | |||||
* | |||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. | |||||
* | |||||
* @return BigDecimal The result. | |||||
* | |||||
* @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero, | |||||
* or the result yields an infinite number of digits. | |||||
*/ | |||||
public function exactlyDividedBy($that) : BigDecimal | |||||
{ | |||||
$that = BigDecimal::of($that); | |||||
if ($that->value === '0') { | |||||
throw DivisionByZeroException::divisionByZero(); | |||||
} | |||||
[, $b] = $this->scaleValues($this, $that); | |||||
$d = \rtrim($b, '0'); | |||||
$scale = \strlen($b) - \strlen($d); | |||||
$calculator = Calculator::get(); | |||||
foreach ([5, 2] as $prime) { | |||||
for (;;) { | |||||
$lastDigit = (int) $d[-1]; | |||||
if ($lastDigit % $prime !== 0) { | |||||
break; | |||||
} | |||||
$d = $calculator->divQ($d, (string) $prime); | |||||
$scale++; | |||||
} | |||||
} | |||||
return $this->dividedBy($that, $scale)->stripTrailingZeros(); | |||||
} | |||||
/** | |||||
* Returns this number exponentiated to the given value. | |||||
* | |||||
* The result has a scale of `$this->scale * $exponent`. | |||||
* | |||||
* @param int $exponent The exponent. | |||||
* | |||||
* @return BigDecimal The result. | |||||
* | |||||
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. | |||||
*/ | |||||
public function power(int $exponent) : BigDecimal | |||||
{ | |||||
if ($exponent === 0) { | |||||
return BigDecimal::one(); | |||||
} | |||||
if ($exponent === 1) { | |||||
return $this; | |||||
} | |||||
if ($exponent < 0 || $exponent > Calculator::MAX_POWER) { | |||||
throw new \InvalidArgumentException(\sprintf( | |||||
'The exponent %d is not in the range 0 to %d.', | |||||
$exponent, | |||||
Calculator::MAX_POWER | |||||
)); | |||||
} | |||||
return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent); | |||||
} | |||||
/** | |||||
* Returns the quotient of the division of this number by this given one. | |||||
* | |||||
* The quotient has a scale of `0`. | |||||
* | |||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. | |||||
* | |||||
* @return BigDecimal The quotient. | |||||
* | |||||
* @throws MathException If the divisor is not a valid decimal number, or is zero. | |||||
*/ | |||||
public function quotient($that) : BigDecimal | |||||
{ | |||||
$that = BigDecimal::of($that); | |||||
if ($that->isZero()) { | |||||
throw DivisionByZeroException::divisionByZero(); | |||||
} | |||||
$p = $this->valueWithMinScale($that->scale); | |||||
$q = $that->valueWithMinScale($this->scale); | |||||
$quotient = Calculator::get()->divQ($p, $q); | |||||
return new BigDecimal($quotient, 0); | |||||
} | |||||
/** | |||||
* Returns the remainder of the division of this number by this given one. | |||||
* | |||||
* The remainder has a scale of `max($this->scale, $that->scale)`. | |||||
* | |||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. | |||||
* | |||||
* @return BigDecimal The remainder. | |||||
* | |||||
* @throws MathException If the divisor is not a valid decimal number, or is zero. | |||||
*/ | |||||
public function remainder($that) : BigDecimal | |||||
{ | |||||
$that = BigDecimal::of($that); | |||||
if ($that->isZero()) { | |||||
throw DivisionByZeroException::divisionByZero(); | |||||
} | |||||
$p = $this->valueWithMinScale($that->scale); | |||||
$q = $that->valueWithMinScale($this->scale); | |||||
$remainder = Calculator::get()->divR($p, $q); | |||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale; | |||||
return new BigDecimal($remainder, $scale); | |||||
} | |||||
/** | |||||
* Returns the quotient and remainder of the division of this number by the given one. | |||||
* | |||||
* The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`. | |||||
* | |||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal. | |||||
* | |||||
* @return BigDecimal[] An array containing the quotient and the remainder. | |||||
* | |||||
* @throws MathException If the divisor is not a valid decimal number, or is zero. | |||||
*/ | |||||
public function quotientAndRemainder($that) : array | |||||
{ | |||||
$that = BigDecimal::of($that); | |||||
if ($that->isZero()) { | |||||
throw DivisionByZeroException::divisionByZero(); | |||||
} | |||||
$p = $this->valueWithMinScale($that->scale); | |||||
$q = $that->valueWithMinScale($this->scale); | |||||
[$quotient, $remainder] = Calculator::get()->divQR($p, $q); | |||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale; | |||||
$quotient = new BigDecimal($quotient, 0); | |||||
$remainder = new BigDecimal($remainder, $scale); | |||||
return [$quotient, $remainder]; | |||||
} | |||||
/** | |||||
* Returns the square root of this number, rounded down to the given number of decimals. | |||||
* | |||||
* @param int $scale | |||||
* | |||||
* @return BigDecimal | |||||
* | |||||
* @throws \InvalidArgumentException If the scale is negative. | |||||
* @throws NegativeNumberException If this number is negative. | |||||
*/ | |||||
public function sqrt(int $scale) : BigDecimal | |||||
{ | |||||
if ($scale < 0) { | |||||
throw new \InvalidArgumentException('Scale cannot be negative.'); | |||||
} | |||||
if ($this->value === '0') { | |||||
return new BigDecimal('0', $scale); | |||||
} | |||||
if ($this->value[0] === '-') { | |||||
throw new NegativeNumberException('Cannot calculate the square root of a negative number.'); | |||||
} | |||||
$value = $this->value; | |||||
$addDigits = 2 * $scale - $this->scale; | |||||
if ($addDigits > 0) { | |||||
// add zeros | |||||
$value .= \str_repeat('0', $addDigits); | |||||
} elseif ($addDigits < 0) { | |||||
// trim digits | |||||
if (-$addDigits >= \strlen($this->value)) { | |||||
// requesting a scale too low, will always yield a zero result | |||||
return new BigDecimal('0', $scale); | |||||
} | |||||
$value = \substr($value, 0, $addDigits); | |||||
} | |||||
$value = Calculator::get()->sqrt($value); | |||||
return new BigDecimal($value, $scale); | |||||
} | |||||
/** | |||||
* Returns a copy of this BigDecimal with the decimal point moved $n places to the left. | |||||
* | |||||
* @param int $n | |||||
* | |||||
* @return BigDecimal | |||||
*/ | |||||
public function withPointMovedLeft(int $n) : BigDecimal | |||||
{ | |||||
if ($n === 0) { | |||||
return $this; | |||||
} | |||||
if ($n < 0) { | |||||
return $this->withPointMovedRight(-$n); | |||||
} | |||||
return new BigDecimal($this->value, $this->scale + $n); | |||||
} | |||||
/** | |||||
* Returns a copy of this BigDecimal with the decimal point moved $n places to the right. | |||||
* | |||||
* @param int $n | |||||
* | |||||
* @return BigDecimal | |||||
*/ | |||||
public function withPointMovedRight(int $n) : BigDecimal | |||||
{ | |||||
if ($n === 0) { | |||||
return $this; | |||||
} | |||||
if ($n < 0) { | |||||
return $this->withPointMovedLeft(-$n); | |||||
} | |||||
$value = $this->value; | |||||
$scale = $this->scale - $n; | |||||
if ($scale < 0) { | |||||
if ($value !== '0') { | |||||
$value .= \str_repeat('0', -$scale); | |||||
} | |||||
$scale = 0; | |||||
} | |||||
return new BigDecimal($value, $scale); | |||||
} | |||||
/** | |||||
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part. | |||||
* | |||||
* @return BigDecimal | |||||
*/ | |||||
public function stripTrailingZeros() : BigDecimal | |||||
{ | |||||
if ($this->scale === 0) { | |||||
return $this; | |||||
} | |||||
$trimmedValue = \rtrim($this->value, '0'); | |||||
if ($trimmedValue === '') { | |||||
return BigDecimal::zero(); | |||||
} | |||||
$trimmableZeros = \strlen($this->value) - \strlen($trimmedValue); | |||||
if ($trimmableZeros === 0) { | |||||
return $this; | |||||
} | |||||
if ($trimmableZeros > $this->scale) { | |||||
$trimmableZeros = $this->scale; | |||||
} | |||||
$value = \substr($this->value, 0, -$trimmableZeros); | |||||
$scale = $this->scale - $trimmableZeros; | |||||
return new BigDecimal($value, $scale); | |||||
} | |||||
/** | |||||
* Returns the absolute value of this number. | |||||
* | |||||
* @return BigDecimal | |||||
*/ | |||||
public function abs() : BigDecimal | |||||
{ | |||||
return $this->isNegative() ? $this->negated() : $this; | |||||
} | |||||
/** | |||||
* Returns the negated value of this number. | |||||
* | |||||
* @return BigDecimal | |||||
*/ | |||||
public function negated() : BigDecimal | |||||
{ | |||||
return new BigDecimal(Calculator::get()->neg($this->value), $this->scale); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function compareTo($that) : int | |||||
{ | |||||
$that = BigNumber::of($that); | |||||
if ($that instanceof BigInteger) { | |||||
$that = $that->toBigDecimal(); | |||||
} | |||||
if ($that instanceof BigDecimal) { | |||||
[$a, $b] = $this->scaleValues($this, $that); | |||||
return Calculator::get()->cmp($a, $b); | |||||
} | |||||
return - $that->compareTo($this); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function getSign() : int | |||||
{ | |||||
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1); | |||||
} | |||||
/** | |||||
* @return BigInteger | |||||
*/ | |||||
public function getUnscaledValue() : BigInteger | |||||
{ | |||||
return BigInteger::create($this->value); | |||||
} | |||||
/** | |||||
* @return int | |||||
*/ | |||||
public function getScale() : int | |||||
{ | |||||
return $this->scale; | |||||
} | |||||
/** | |||||
* Returns a string representing the integral part of this decimal number. | |||||
* | |||||
* Example: `-123.456` => `-123`. | |||||
* | |||||
* @return string | |||||
*/ | |||||
public function getIntegralPart() : string | |||||
{ | |||||
if ($this->scale === 0) { | |||||
return $this->value; | |||||
} | |||||
$value = $this->getUnscaledValueWithLeadingZeros(); | |||||
return \substr($value, 0, -$this->scale); | |||||
} | |||||
/** | |||||
* Returns a string representing the fractional part of this decimal number. | |||||
* | |||||
* If the scale is zero, an empty string is returned. | |||||
* | |||||
* Examples: `-123.456` => '456', `123` => ''. | |||||
* | |||||
* @return string | |||||
*/ | |||||
public function getFractionalPart() : string | |||||
{ | |||||
if ($this->scale === 0) { | |||||
return ''; | |||||
} | |||||
$value = $this->getUnscaledValueWithLeadingZeros(); | |||||
return \substr($value, -$this->scale); | |||||
} | |||||
/** | |||||
* Returns whether this decimal number has a non-zero fractional part. | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function hasNonZeroFractionalPart() : bool | |||||
{ | |||||
return $this->getFractionalPart() !== \str_repeat('0', $this->scale); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toBigInteger() : BigInteger | |||||
{ | |||||
$zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0); | |||||
return BigInteger::create($zeroScaleDecimal->value); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toBigDecimal() : BigDecimal | |||||
{ | |||||
return $this; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toBigRational() : BigRational | |||||
{ | |||||
$numerator = BigInteger::create($this->value); | |||||
$denominator = BigInteger::create('1' . \str_repeat('0', $this->scale)); | |||||
return BigRational::create($numerator, $denominator, false); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal | |||||
{ | |||||
if ($scale === $this->scale) { | |||||
return $this; | |||||
} | |||||
return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toInt() : int | |||||
{ | |||||
return $this->toBigInteger()->toInt(); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toFloat() : float | |||||
{ | |||||
return (float) (string) $this; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function __toString() : string | |||||
{ | |||||
if ($this->scale === 0) { | |||||
return $this->value; | |||||
} | |||||
$value = $this->getUnscaledValueWithLeadingZeros(); | |||||
return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale); | |||||
} | |||||
/** | |||||
* This method is required by interface Serializable and SHOULD NOT be accessed directly. | |||||
* | |||||
* @internal | |||||
* | |||||
* @return string | |||||
*/ | |||||
public function serialize() : string | |||||
{ | |||||
return $this->value . ':' . $this->scale; | |||||
} | |||||
/** | |||||
* This method is only here to implement interface Serializable and cannot be accessed directly. | |||||
* | |||||
* @internal | |||||
* @psalm-suppress RedundantPropertyInitializationCheck | |||||
* | |||||
* @param string $value | |||||
* | |||||
* @return void | |||||
* | |||||
* @throws \LogicException | |||||
*/ | |||||
public function unserialize($value) : void | |||||
{ | |||||
if (isset($this->value)) { | |||||
throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); | |||||
} | |||||
[$value, $scale] = \explode(':', $value); | |||||
$this->value = $value; | |||||
$this->scale = (int) $scale; | |||||
} | |||||
/** | |||||
* Puts the internal values of the given decimal numbers on the same scale. | |||||
* | |||||
* @param BigDecimal $x The first decimal number. | |||||
* @param BigDecimal $y The second decimal number. | |||||
* | |||||
* @return array{0: string, 1: string} The scaled integer values of $x and $y. | |||||
*/ | |||||
private function scaleValues(BigDecimal $x, BigDecimal $y) : array | |||||
{ | |||||
$a = $x->value; | |||||
$b = $y->value; | |||||
if ($b !== '0' && $x->scale > $y->scale) { | |||||
$b .= \str_repeat('0', $x->scale - $y->scale); | |||||
} elseif ($a !== '0' && $x->scale < $y->scale) { | |||||
$a .= \str_repeat('0', $y->scale - $x->scale); | |||||
} | |||||
return [$a, $b]; | |||||
} | |||||
/** | |||||
* @param int $scale | |||||
* | |||||
* @return string | |||||
*/ | |||||
private function valueWithMinScale(int $scale) : string | |||||
{ | |||||
$value = $this->value; | |||||
if ($this->value !== '0' && $scale > $this->scale) { | |||||
$value .= \str_repeat('0', $scale - $this->scale); | |||||
} | |||||
return $value; | |||||
} | |||||
/** | |||||
* Adds leading zeros if necessary to the unscaled value to represent the full decimal number. | |||||
* | |||||
* @return string | |||||
*/ | |||||
private function getUnscaledValueWithLeadingZeros() : string | |||||
{ | |||||
$value = $this->value; | |||||
$targetLength = $this->scale + 1; | |||||
$negative = ($value[0] === '-'); | |||||
$length = \strlen($value); | |||||
if ($negative) { | |||||
$length--; | |||||
} | |||||
if ($length >= $targetLength) { | |||||
return $this->value; | |||||
} | |||||
if ($negative) { | |||||
$value = \substr($value, 1); | |||||
} | |||||
$value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT); | |||||
if ($negative) { | |||||
$value = '-' . $value; | |||||
} | |||||
return $value; | |||||
} | |||||
} |
@@ -0,0 +1,572 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math; | |||||
use Brick\Math\Exception\DivisionByZeroException; | |||||
use Brick\Math\Exception\MathException; | |||||
use Brick\Math\Exception\NumberFormatException; | |||||
use Brick\Math\Exception\RoundingNecessaryException; | |||||
/** | |||||
* Common interface for arbitrary-precision rational numbers. | |||||
* | |||||
* @psalm-immutable | |||||
*/ | |||||
abstract class BigNumber implements \Serializable, \JsonSerializable | |||||
{ | |||||
/** | |||||
* The regular expression used to parse integer, decimal and rational numbers. | |||||
*/ | |||||
private const PARSE_REGEXP = | |||||
'/^' . | |||||
'(?<sign>[\-\+])?' . | |||||
'(?:' . | |||||
'(?:' . | |||||
'(?<integral>[0-9]+)?' . | |||||
'(?<point>\.)?' . | |||||
'(?<fractional>[0-9]+)?' . | |||||
'(?:[eE](?<exponent>[\-\+]?[0-9]+))?' . | |||||
')|(?:' . | |||||
'(?<numerator>[0-9]+)' . | |||||
'\/?' . | |||||
'(?<denominator>[0-9]+)' . | |||||
')' . | |||||
')' . | |||||
'$/'; | |||||
/** | |||||
* Creates a BigNumber of the given value. | |||||
* | |||||
* The concrete return type is dependent on the given value, with the following rules: | |||||
* | |||||
* - BigNumber instances are returned as is | |||||
* - integer numbers are returned as BigInteger | |||||
* - floating point numbers are converted to a string then parsed as such | |||||
* - strings containing a `/` character are returned as BigRational | |||||
* - strings containing a `.` character or using an exponential notation are returned as BigDecimal | |||||
* - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger | |||||
* | |||||
* @param BigNumber|int|float|string $value | |||||
* | |||||
* @return BigNumber | |||||
* | |||||
* @throws NumberFormatException If the format of the number is not valid. | |||||
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero. | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function of($value) : BigNumber | |||||
{ | |||||
if ($value instanceof BigNumber) { | |||||
return $value; | |||||
} | |||||
if (\is_int($value)) { | |||||
return new BigInteger((string) $value); | |||||
} | |||||
/** @psalm-suppress RedundantCastGivenDocblockType We cannot trust the untyped $value here! */ | |||||
$value = \is_float($value) ? self::floatToString($value) : (string) $value; | |||||
$throw = static function() use ($value) : void { | |||||
throw new NumberFormatException(\sprintf( | |||||
'The given value "%s" does not represent a valid number.', | |||||
$value | |||||
)); | |||||
}; | |||||
if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) { | |||||
$throw(); | |||||
} | |||||
$getMatch = static function(string $value) use ($matches) : ?string { | |||||
return isset($matches[$value]) && $matches[$value] !== '' ? $matches[$value] : null; | |||||
}; | |||||
$sign = $getMatch('sign'); | |||||
$numerator = $getMatch('numerator'); | |||||
$denominator = $getMatch('denominator'); | |||||
if ($numerator !== null) { | |||||
assert($denominator !== null); | |||||
if ($sign !== null) { | |||||
$numerator = $sign . $numerator; | |||||
} | |||||
$numerator = self::cleanUp($numerator); | |||||
$denominator = self::cleanUp($denominator); | |||||
if ($denominator === '0') { | |||||
throw DivisionByZeroException::denominatorMustNotBeZero(); | |||||
} | |||||
return new BigRational( | |||||
new BigInteger($numerator), | |||||
new BigInteger($denominator), | |||||
false | |||||
); | |||||
} | |||||
$point = $getMatch('point'); | |||||
$integral = $getMatch('integral'); | |||||
$fractional = $getMatch('fractional'); | |||||
$exponent = $getMatch('exponent'); | |||||
if ($integral === null && $fractional === null) { | |||||
$throw(); | |||||
} | |||||
if ($integral === null) { | |||||
$integral = '0'; | |||||
} | |||||
if ($point !== null || $exponent !== null) { | |||||
$fractional = ($fractional ?? ''); | |||||
$exponent = ($exponent !== null) ? (int) $exponent : 0; | |||||
if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) { | |||||
throw new NumberFormatException('Exponent too large.'); | |||||
} | |||||
$unscaledValue = self::cleanUp(($sign ?? ''). $integral . $fractional); | |||||
$scale = \strlen($fractional) - $exponent; | |||||
if ($scale < 0) { | |||||
if ($unscaledValue !== '0') { | |||||
$unscaledValue .= \str_repeat('0', - $scale); | |||||
} | |||||
$scale = 0; | |||||
} | |||||
return new BigDecimal($unscaledValue, $scale); | |||||
} | |||||
$integral = self::cleanUp(($sign ?? '') . $integral); | |||||
return new BigInteger($integral); | |||||
} | |||||
/** | |||||
* Safely converts float to string, avoiding locale-dependent issues. | |||||
* | |||||
* @see https://github.com/brick/math/pull/20 | |||||
* | |||||
* @param float $float | |||||
* | |||||
* @return string | |||||
* | |||||
* @psalm-pure | |||||
* @psalm-suppress ImpureFunctionCall | |||||
*/ | |||||
private static function floatToString(float $float) : string | |||||
{ | |||||
$currentLocale = \setlocale(LC_NUMERIC, '0'); | |||||
\setlocale(LC_NUMERIC, 'C'); | |||||
$result = (string) $float; | |||||
\setlocale(LC_NUMERIC, $currentLocale); | |||||
return $result; | |||||
} | |||||
/** | |||||
* Proxy method to access protected constructors from sibling classes. | |||||
* | |||||
* @internal | |||||
* | |||||
* @param mixed ...$args The arguments to the constructor. | |||||
* | |||||
* @return static | |||||
* | |||||
* @psalm-pure | |||||
* @psalm-suppress TooManyArguments | |||||
* @psalm-suppress UnsafeInstantiation | |||||
*/ | |||||
protected static function create(... $args) : BigNumber | |||||
{ | |||||
return new static(... $args); | |||||
} | |||||
/** | |||||
* Returns the minimum of the given values. | |||||
* | |||||
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible | |||||
* to an instance of the class this method is called on. | |||||
* | |||||
* @return static The minimum value. | |||||
* | |||||
* @throws \InvalidArgumentException If no values are given. | |||||
* @throws MathException If an argument is not valid. | |||||
* | |||||
* @psalm-suppress LessSpecificReturnStatement | |||||
* @psalm-suppress MoreSpecificReturnType | |||||
* @psalm-pure | |||||
*/ | |||||
public static function min(...$values) : BigNumber | |||||
{ | |||||
$min = null; | |||||
foreach ($values as $value) { | |||||
$value = static::of($value); | |||||
if ($min === null || $value->isLessThan($min)) { | |||||
$min = $value; | |||||
} | |||||
} | |||||
if ($min === null) { | |||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); | |||||
} | |||||
return $min; | |||||
} | |||||
/** | |||||
* Returns the maximum of the given values. | |||||
* | |||||
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible | |||||
* to an instance of the class this method is called on. | |||||
* | |||||
* @return static The maximum value. | |||||
* | |||||
* @throws \InvalidArgumentException If no values are given. | |||||
* @throws MathException If an argument is not valid. | |||||
* | |||||
* @psalm-suppress LessSpecificReturnStatement | |||||
* @psalm-suppress MoreSpecificReturnType | |||||
* @psalm-pure | |||||
*/ | |||||
public static function max(...$values) : BigNumber | |||||
{ | |||||
$max = null; | |||||
foreach ($values as $value) { | |||||
$value = static::of($value); | |||||
if ($max === null || $value->isGreaterThan($max)) { | |||||
$max = $value; | |||||
} | |||||
} | |||||
if ($max === null) { | |||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); | |||||
} | |||||
return $max; | |||||
} | |||||
/** | |||||
* Returns the sum of the given values. | |||||
* | |||||
* @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible | |||||
* to an instance of the class this method is called on. | |||||
* | |||||
* @return static The sum. | |||||
* | |||||
* @throws \InvalidArgumentException If no values are given. | |||||
* @throws MathException If an argument is not valid. | |||||
* | |||||
* @psalm-suppress LessSpecificReturnStatement | |||||
* @psalm-suppress MoreSpecificReturnType | |||||
* @psalm-pure | |||||
*/ | |||||
public static function sum(...$values) : BigNumber | |||||
{ | |||||
/** @var BigNumber|null $sum */ | |||||
$sum = null; | |||||
foreach ($values as $value) { | |||||
$value = static::of($value); | |||||
$sum = $sum === null ? $value : self::add($sum, $value); | |||||
} | |||||
if ($sum === null) { | |||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.'); | |||||
} | |||||
return $sum; | |||||
} | |||||
/** | |||||
* Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException. | |||||
* | |||||
* @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to | |||||
* concrete classes the responsibility to perform the addition themselves or delegate it to the given number, | |||||
* depending on their ability to perform the operation. This will also require a version bump because we're | |||||
* potentially breaking custom BigNumber implementations (if any...) | |||||
* | |||||
* @param BigNumber $a | |||||
* @param BigNumber $b | |||||
* | |||||
* @return BigNumber | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
private static function add(BigNumber $a, BigNumber $b) : BigNumber | |||||
{ | |||||
if ($a instanceof BigRational) { | |||||
return $a->plus($b); | |||||
} | |||||
if ($b instanceof BigRational) { | |||||
return $b->plus($a); | |||||
} | |||||
if ($a instanceof BigDecimal) { | |||||
return $a->plus($b); | |||||
} | |||||
if ($b instanceof BigDecimal) { | |||||
return $b->plus($a); | |||||
} | |||||
/** @var BigInteger $a */ | |||||
return $a->plus($b); | |||||
} | |||||
/** | |||||
* Removes optional leading zeros and + sign from the given number. | |||||
* | |||||
* @param string $number The number, validated as a non-empty string of digits with optional leading sign. | |||||
* | |||||
* @return string | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
private static function cleanUp(string $number) : string | |||||
{ | |||||
$firstChar = $number[0]; | |||||
if ($firstChar === '+' || $firstChar === '-') { | |||||
$number = \substr($number, 1); | |||||
} | |||||
$number = \ltrim($number, '0'); | |||||
if ($number === '') { | |||||
return '0'; | |||||
} | |||||
if ($firstChar === '-') { | |||||
return '-' . $number; | |||||
} | |||||
return $number; | |||||
} | |||||
/** | |||||
* Checks if this number is equal to the given one. | |||||
* | |||||
* @param BigNumber|int|float|string $that | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isEqualTo($that) : bool | |||||
{ | |||||
return $this->compareTo($that) === 0; | |||||
} | |||||
/** | |||||
* Checks if this number is strictly lower than the given one. | |||||
* | |||||
* @param BigNumber|int|float|string $that | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isLessThan($that) : bool | |||||
{ | |||||
return $this->compareTo($that) < 0; | |||||
} | |||||
/** | |||||
* Checks if this number is lower than or equal to the given one. | |||||
* | |||||
* @param BigNumber|int|float|string $that | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isLessThanOrEqualTo($that) : bool | |||||
{ | |||||
return $this->compareTo($that) <= 0; | |||||
} | |||||
/** | |||||
* Checks if this number is strictly greater than the given one. | |||||
* | |||||
* @param BigNumber|int|float|string $that | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isGreaterThan($that) : bool | |||||
{ | |||||
return $this->compareTo($that) > 0; | |||||
} | |||||
/** | |||||
* Checks if this number is greater than or equal to the given one. | |||||
* | |||||
* @param BigNumber|int|float|string $that | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isGreaterThanOrEqualTo($that) : bool | |||||
{ | |||||
return $this->compareTo($that) >= 0; | |||||
} | |||||
/** | |||||
* Checks if this number equals zero. | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isZero() : bool | |||||
{ | |||||
return $this->getSign() === 0; | |||||
} | |||||
/** | |||||
* Checks if this number is strictly negative. | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isNegative() : bool | |||||
{ | |||||
return $this->getSign() < 0; | |||||
} | |||||
/** | |||||
* Checks if this number is negative or zero. | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isNegativeOrZero() : bool | |||||
{ | |||||
return $this->getSign() <= 0; | |||||
} | |||||
/** | |||||
* Checks if this number is strictly positive. | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isPositive() : bool | |||||
{ | |||||
return $this->getSign() > 0; | |||||
} | |||||
/** | |||||
* Checks if this number is positive or zero. | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isPositiveOrZero() : bool | |||||
{ | |||||
return $this->getSign() >= 0; | |||||
} | |||||
/** | |||||
* Returns the sign of this number. | |||||
* | |||||
* @return int -1 if the number is negative, 0 if zero, 1 if positive. | |||||
*/ | |||||
abstract public function getSign() : int; | |||||
/** | |||||
* Compares this number to the given one. | |||||
* | |||||
* @param BigNumber|int|float|string $that | |||||
* | |||||
* @return int [-1,0,1] If `$this` is lower than, equal to, or greater than `$that`. | |||||
* | |||||
* @throws MathException If the number is not valid. | |||||
*/ | |||||
abstract public function compareTo($that) : int; | |||||
/** | |||||
* Converts this number to a BigInteger. | |||||
* | |||||
* @return BigInteger The converted number. | |||||
* | |||||
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding. | |||||
*/ | |||||
abstract public function toBigInteger() : BigInteger; | |||||
/** | |||||
* Converts this number to a BigDecimal. | |||||
* | |||||
* @return BigDecimal The converted number. | |||||
* | |||||
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding. | |||||
*/ | |||||
abstract public function toBigDecimal() : BigDecimal; | |||||
/** | |||||
* Converts this number to a BigRational. | |||||
* | |||||
* @return BigRational The converted number. | |||||
*/ | |||||
abstract public function toBigRational() : BigRational; | |||||
/** | |||||
* Converts this number to a BigDecimal with the given scale, using rounding if necessary. | |||||
* | |||||
* @param int $scale The scale of the resulting `BigDecimal`. | |||||
* @param int $roundingMode A `RoundingMode` constant. | |||||
* | |||||
* @return BigDecimal | |||||
* | |||||
* @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding. | |||||
* This only applies when RoundingMode::UNNECESSARY is used. | |||||
*/ | |||||
abstract public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal; | |||||
/** | |||||
* Returns the exact value of this number as a native integer. | |||||
* | |||||
* If this number cannot be converted to a native integer without losing precision, an exception is thrown. | |||||
* Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit. | |||||
* | |||||
* @return int The converted value. | |||||
* | |||||
* @throws MathException If this number cannot be exactly converted to a native integer. | |||||
*/ | |||||
abstract public function toInt() : int; | |||||
/** | |||||
* Returns an approximation of this number as a floating-point value. | |||||
* | |||||
* Note that this method can discard information as the precision of a floating-point value | |||||
* is inherently limited. | |||||
* | |||||
* If the number is greater than the largest representable floating point number, positive infinity is returned. | |||||
* If the number is less than the smallest representable floating point number, negative infinity is returned. | |||||
* | |||||
* @return float The converted value. | |||||
*/ | |||||
abstract public function toFloat() : float; | |||||
/** | |||||
* Returns a string representation of this number. | |||||
* | |||||
* The output of this method can be parsed by the `of()` factory method; | |||||
* this will yield an object equal to this one, without any information loss. | |||||
* | |||||
* @return string | |||||
*/ | |||||
abstract public function __toString() : string; | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function jsonSerialize() : string | |||||
{ | |||||
return $this->__toString(); | |||||
} | |||||
} |
@@ -0,0 +1,489 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math; | |||||
use Brick\Math\Exception\DivisionByZeroException; | |||||
use Brick\Math\Exception\MathException; | |||||
use Brick\Math\Exception\NumberFormatException; | |||||
use Brick\Math\Exception\RoundingNecessaryException; | |||||
/** | |||||
* An arbitrarily large rational number. | |||||
* | |||||
* This class is immutable. | |||||
* | |||||
* @psalm-immutable | |||||
*/ | |||||
final class BigRational extends BigNumber | |||||
{ | |||||
/** | |||||
* The numerator. | |||||
* | |||||
* @var BigInteger | |||||
*/ | |||||
private $numerator; | |||||
/** | |||||
* The denominator. Always strictly positive. | |||||
* | |||||
* @var BigInteger | |||||
*/ | |||||
private $denominator; | |||||
/** | |||||
* Protected constructor. Use a factory method to obtain an instance. | |||||
* | |||||
* @param BigInteger $numerator The numerator. | |||||
* @param BigInteger $denominator The denominator. | |||||
* @param bool $checkDenominator Whether to check the denominator for negative and zero. | |||||
* | |||||
* @throws DivisionByZeroException If the denominator is zero. | |||||
*/ | |||||
protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) | |||||
{ | |||||
if ($checkDenominator) { | |||||
if ($denominator->isZero()) { | |||||
throw DivisionByZeroException::denominatorMustNotBeZero(); | |||||
} | |||||
if ($denominator->isNegative()) { | |||||
$numerator = $numerator->negated(); | |||||
$denominator = $denominator->negated(); | |||||
} | |||||
} | |||||
$this->numerator = $numerator; | |||||
$this->denominator = $denominator; | |||||
} | |||||
/** | |||||
* Creates a BigRational of the given value. | |||||
* | |||||
* @param BigNumber|int|float|string $value | |||||
* | |||||
* @return BigRational | |||||
* | |||||
* @throws MathException If the value cannot be converted to a BigRational. | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function of($value) : BigNumber | |||||
{ | |||||
return parent::of($value)->toBigRational(); | |||||
} | |||||
/** | |||||
* Creates a BigRational out of a numerator and a denominator. | |||||
* | |||||
* If the denominator is negative, the signs of both the numerator and the denominator | |||||
* will be inverted to ensure that the denominator is always positive. | |||||
* | |||||
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger. | |||||
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger. | |||||
* | |||||
* @return BigRational | |||||
* | |||||
* @throws NumberFormatException If an argument does not represent a valid number. | |||||
* @throws RoundingNecessaryException If an argument represents a non-integer number. | |||||
* @throws DivisionByZeroException If the denominator is zero. | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function nd($numerator, $denominator) : BigRational | |||||
{ | |||||
$numerator = BigInteger::of($numerator); | |||||
$denominator = BigInteger::of($denominator); | |||||
return new BigRational($numerator, $denominator, true); | |||||
} | |||||
/** | |||||
* Returns a BigRational representing zero. | |||||
* | |||||
* @return BigRational | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function zero() : BigRational | |||||
{ | |||||
/** | |||||
* @psalm-suppress ImpureStaticVariable | |||||
* @var BigRational|null $zero | |||||
*/ | |||||
static $zero; | |||||
if ($zero === null) { | |||||
$zero = new BigRational(BigInteger::zero(), BigInteger::one(), false); | |||||
} | |||||
return $zero; | |||||
} | |||||
/** | |||||
* Returns a BigRational representing one. | |||||
* | |||||
* @return BigRational | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function one() : BigRational | |||||
{ | |||||
/** | |||||
* @psalm-suppress ImpureStaticVariable | |||||
* @var BigRational|null $one | |||||
*/ | |||||
static $one; | |||||
if ($one === null) { | |||||
$one = new BigRational(BigInteger::one(), BigInteger::one(), false); | |||||
} | |||||
return $one; | |||||
} | |||||
/** | |||||
* Returns a BigRational representing ten. | |||||
* | |||||
* @return BigRational | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function ten() : BigRational | |||||
{ | |||||
/** | |||||
* @psalm-suppress ImpureStaticVariable | |||||
* @var BigRational|null $ten | |||||
*/ | |||||
static $ten; | |||||
if ($ten === null) { | |||||
$ten = new BigRational(BigInteger::ten(), BigInteger::one(), false); | |||||
} | |||||
return $ten; | |||||
} | |||||
/** | |||||
* @return BigInteger | |||||
*/ | |||||
public function getNumerator() : BigInteger | |||||
{ | |||||
return $this->numerator; | |||||
} | |||||
/** | |||||
* @return BigInteger | |||||
*/ | |||||
public function getDenominator() : BigInteger | |||||
{ | |||||
return $this->denominator; | |||||
} | |||||
/** | |||||
* Returns the quotient of the division of the numerator by the denominator. | |||||
* | |||||
* @return BigInteger | |||||
*/ | |||||
public function quotient() : BigInteger | |||||
{ | |||||
return $this->numerator->quotient($this->denominator); | |||||
} | |||||
/** | |||||
* Returns the remainder of the division of the numerator by the denominator. | |||||
* | |||||
* @return BigInteger | |||||
*/ | |||||
public function remainder() : BigInteger | |||||
{ | |||||
return $this->numerator->remainder($this->denominator); | |||||
} | |||||
/** | |||||
* Returns the quotient and remainder of the division of the numerator by the denominator. | |||||
* | |||||
* @return BigInteger[] | |||||
*/ | |||||
public function quotientAndRemainder() : array | |||||
{ | |||||
return $this->numerator->quotientAndRemainder($this->denominator); | |||||
} | |||||
/** | |||||
* Returns the sum of this number and the given one. | |||||
* | |||||
* @param BigNumber|int|float|string $that The number to add. | |||||
* | |||||
* @return BigRational The result. | |||||
* | |||||
* @throws MathException If the number is not valid. | |||||
*/ | |||||
public function plus($that) : BigRational | |||||
{ | |||||
$that = BigRational::of($that); | |||||
$numerator = $this->numerator->multipliedBy($that->denominator); | |||||
$numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator)); | |||||
$denominator = $this->denominator->multipliedBy($that->denominator); | |||||
return new BigRational($numerator, $denominator, false); | |||||
} | |||||
/** | |||||
* Returns the difference of this number and the given one. | |||||
* | |||||
* @param BigNumber|int|float|string $that The number to subtract. | |||||
* | |||||
* @return BigRational The result. | |||||
* | |||||
* @throws MathException If the number is not valid. | |||||
*/ | |||||
public function minus($that) : BigRational | |||||
{ | |||||
$that = BigRational::of($that); | |||||
$numerator = $this->numerator->multipliedBy($that->denominator); | |||||
$numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator)); | |||||
$denominator = $this->denominator->multipliedBy($that->denominator); | |||||
return new BigRational($numerator, $denominator, false); | |||||
} | |||||
/** | |||||
* Returns the product of this number and the given one. | |||||
* | |||||
* @param BigNumber|int|float|string $that The multiplier. | |||||
* | |||||
* @return BigRational The result. | |||||
* | |||||
* @throws MathException If the multiplier is not a valid number. | |||||
*/ | |||||
public function multipliedBy($that) : BigRational | |||||
{ | |||||
$that = BigRational::of($that); | |||||
$numerator = $this->numerator->multipliedBy($that->numerator); | |||||
$denominator = $this->denominator->multipliedBy($that->denominator); | |||||
return new BigRational($numerator, $denominator, false); | |||||
} | |||||
/** | |||||
* Returns the result of the division of this number by the given one. | |||||
* | |||||
* @param BigNumber|int|float|string $that The divisor. | |||||
* | |||||
* @return BigRational The result. | |||||
* | |||||
* @throws MathException If the divisor is not a valid number, or is zero. | |||||
*/ | |||||
public function dividedBy($that) : BigRational | |||||
{ | |||||
$that = BigRational::of($that); | |||||
$numerator = $this->numerator->multipliedBy($that->denominator); | |||||
$denominator = $this->denominator->multipliedBy($that->numerator); | |||||
return new BigRational($numerator, $denominator, true); | |||||
} | |||||
/** | |||||
* Returns this number exponentiated to the given value. | |||||
* | |||||
* @param int $exponent The exponent. | |||||
* | |||||
* @return BigRational The result. | |||||
* | |||||
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000. | |||||
*/ | |||||
public function power(int $exponent) : BigRational | |||||
{ | |||||
if ($exponent === 0) { | |||||
$one = BigInteger::one(); | |||||
return new BigRational($one, $one, false); | |||||
} | |||||
if ($exponent === 1) { | |||||
return $this; | |||||
} | |||||
return new BigRational( | |||||
$this->numerator->power($exponent), | |||||
$this->denominator->power($exponent), | |||||
false | |||||
); | |||||
} | |||||
/** | |||||
* Returns the reciprocal of this BigRational. | |||||
* | |||||
* The reciprocal has the numerator and denominator swapped. | |||||
* | |||||
* @return BigRational | |||||
* | |||||
* @throws DivisionByZeroException If the numerator is zero. | |||||
*/ | |||||
public function reciprocal() : BigRational | |||||
{ | |||||
return new BigRational($this->denominator, $this->numerator, true); | |||||
} | |||||
/** | |||||
* Returns the absolute value of this BigRational. | |||||
* | |||||
* @return BigRational | |||||
*/ | |||||
public function abs() : BigRational | |||||
{ | |||||
return new BigRational($this->numerator->abs(), $this->denominator, false); | |||||
} | |||||
/** | |||||
* Returns the negated value of this BigRational. | |||||
* | |||||
* @return BigRational | |||||
*/ | |||||
public function negated() : BigRational | |||||
{ | |||||
return new BigRational($this->numerator->negated(), $this->denominator, false); | |||||
} | |||||
/** | |||||
* Returns the simplified value of this BigRational. | |||||
* | |||||
* @return BigRational | |||||
*/ | |||||
public function simplified() : BigRational | |||||
{ | |||||
$gcd = $this->numerator->gcd($this->denominator); | |||||
$numerator = $this->numerator->quotient($gcd); | |||||
$denominator = $this->denominator->quotient($gcd); | |||||
return new BigRational($numerator, $denominator, false); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function compareTo($that) : int | |||||
{ | |||||
return $this->minus($that)->getSign(); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function getSign() : int | |||||
{ | |||||
return $this->numerator->getSign(); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toBigInteger() : BigInteger | |||||
{ | |||||
$simplified = $this->simplified(); | |||||
if (! $simplified->denominator->isEqualTo(1)) { | |||||
throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.'); | |||||
} | |||||
return $simplified->numerator; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toBigDecimal() : BigDecimal | |||||
{ | |||||
return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toBigRational() : BigRational | |||||
{ | |||||
return $this; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal | |||||
{ | |||||
return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toInt() : int | |||||
{ | |||||
return $this->toBigInteger()->toInt(); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toFloat() : float | |||||
{ | |||||
return $this->numerator->toFloat() / $this->denominator->toFloat(); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function __toString() : string | |||||
{ | |||||
$numerator = (string) $this->numerator; | |||||
$denominator = (string) $this->denominator; | |||||
if ($denominator === '1') { | |||||
return $numerator; | |||||
} | |||||
return $this->numerator . '/' . $this->denominator; | |||||
} | |||||
/** | |||||
* This method is required by interface Serializable and SHOULD NOT be accessed directly. | |||||
* | |||||
* @internal | |||||
* | |||||
* @return string | |||||
*/ | |||||
public function serialize() : string | |||||
{ | |||||
return $this->numerator . '/' . $this->denominator; | |||||
} | |||||
/** | |||||
* This method is only here to implement interface Serializable and cannot be accessed directly. | |||||
* | |||||
* @internal | |||||
* @psalm-suppress RedundantPropertyInitializationCheck | |||||
* | |||||
* @param string $value | |||||
* | |||||
* @return void | |||||
* | |||||
* @throws \LogicException | |||||
*/ | |||||
public function unserialize($value) : void | |||||
{ | |||||
if (isset($this->numerator)) { | |||||
throw new \LogicException('unserialize() is an internal function, it must not be called directly.'); | |||||
} | |||||
[$numerator, $denominator] = \explode('/', $value); | |||||
$this->numerator = BigInteger::of($numerator); | |||||
$this->denominator = BigInteger::of($denominator); | |||||
} | |||||
} |
@@ -0,0 +1,41 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math\Exception; | |||||
/** | |||||
* Exception thrown when a division by zero occurs. | |||||
*/ | |||||
class DivisionByZeroException extends MathException | |||||
{ | |||||
/** | |||||
* @return DivisionByZeroException | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function divisionByZero() : DivisionByZeroException | |||||
{ | |||||
return new self('Division by zero.'); | |||||
} | |||||
/** | |||||
* @return DivisionByZeroException | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function modulusMustNotBeZero() : DivisionByZeroException | |||||
{ | |||||
return new self('The modulus must not be zero.'); | |||||
} | |||||
/** | |||||
* @return DivisionByZeroException | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function denominatorMustNotBeZero() : DivisionByZeroException | |||||
{ | |||||
return new self('The denominator of a rational number cannot be zero.'); | |||||
} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math\Exception; | |||||
use Brick\Math\BigInteger; | |||||
/** | |||||
* Exception thrown when an integer overflow occurs. | |||||
*/ | |||||
class IntegerOverflowException extends MathException | |||||
{ | |||||
/** | |||||
* @param BigInteger $value | |||||
* | |||||
* @return IntegerOverflowException | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function toIntOverflow(BigInteger $value) : IntegerOverflowException | |||||
{ | |||||
$message = '%s is out of range %d to %d and cannot be represented as an integer.'; | |||||
return new self(\sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX)); | |||||
} | |||||
} |
@@ -0,0 +1,14 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math\Exception; | |||||
/** | |||||
* Base class for all math exceptions. | |||||
* | |||||
* This class is abstract to ensure that only fine-grained exceptions are thrown throughout the code. | |||||
*/ | |||||
class MathException extends \RuntimeException | |||||
{ | |||||
} |
@@ -0,0 +1,12 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math\Exception; | |||||
/** | |||||
* Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number. | |||||
*/ | |||||
class NegativeNumberException extends MathException | |||||
{ | |||||
} |
@@ -0,0 +1,35 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math\Exception; | |||||
/** | |||||
* Exception thrown when attempting to create a number from a string with an invalid format. | |||||
*/ | |||||
class NumberFormatException extends MathException | |||||
{ | |||||
/** | |||||
* @param string $char The failing character. | |||||
* | |||||
* @return NumberFormatException | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function charNotInAlphabet(string $char) : self | |||||
{ | |||||
$ord = \ord($char); | |||||
if ($ord < 32 || $ord > 126) { | |||||
$char = \strtoupper(\dechex($ord)); | |||||
if ($ord < 10) { | |||||
$char = '0' . $char; | |||||
} | |||||
} else { | |||||
$char = '"' . $char . '"'; | |||||
} | |||||
return new self(sprintf('Char %s is not a valid character in the given alphabet.', $char)); | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math\Exception; | |||||
/** | |||||
* Exception thrown when a number cannot be represented at the requested scale without rounding. | |||||
*/ | |||||
class RoundingNecessaryException extends MathException | |||||
{ | |||||
/** | |||||
* @return RoundingNecessaryException | |||||
* | |||||
* @psalm-pure | |||||
*/ | |||||
public static function roundingNecessary() : RoundingNecessaryException | |||||
{ | |||||
return new self('Rounding is necessary to represent the result of the operation at this scale.'); | |||||
} | |||||
} |
@@ -0,0 +1,759 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math\Internal; | |||||
use Brick\Math\Exception\RoundingNecessaryException; | |||||
use Brick\Math\RoundingMode; | |||||
/** | |||||
* Performs basic operations on arbitrary size integers. | |||||
* | |||||
* Unless otherwise specified, all parameters must be validated as non-empty strings of digits, | |||||
* without leading zero, and with an optional leading minus sign if the number is not zero. | |||||
* | |||||
* Any other parameter format will lead to undefined behaviour. | |||||
* All methods must return strings respecting this format, unless specified otherwise. | |||||
* | |||||
* @internal | |||||
* | |||||
* @psalm-immutable | |||||
*/ | |||||
abstract class Calculator | |||||
{ | |||||
/** | |||||
* The maximum exponent value allowed for the pow() method. | |||||
*/ | |||||
public const MAX_POWER = 1000000; | |||||
/** | |||||
* The alphabet for converting from and to base 2 to 36, lowercase. | |||||
*/ | |||||
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'; | |||||
/** | |||||
* The Calculator instance in use. | |||||
* | |||||
* @var Calculator|null | |||||
*/ | |||||
private static $instance; | |||||
/** | |||||
* Sets the Calculator instance to use. | |||||
* | |||||
* An instance is typically set only in unit tests: the autodetect is usually the best option. | |||||
* | |||||
* @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect. | |||||
* | |||||
* @return void | |||||
*/ | |||||
final public static function set(?Calculator $calculator) : void | |||||
{ | |||||
self::$instance = $calculator; | |||||
} | |||||
/** | |||||
* Returns the Calculator instance to use. | |||||
* | |||||
* If none has been explicitly set, the fastest available implementation will be returned. | |||||
* | |||||
* @return Calculator | |||||
* | |||||
* @psalm-pure | |||||
* @psalm-suppress ImpureStaticProperty | |||||
*/ | |||||
final public static function get() : Calculator | |||||
{ | |||||
if (self::$instance === null) { | |||||
/** @psalm-suppress ImpureMethodCall */ | |||||
self::$instance = self::detect(); | |||||
} | |||||
return self::$instance; | |||||
} | |||||
/** | |||||
* Returns the fastest available Calculator implementation. | |||||
* | |||||
* @codeCoverageIgnore | |||||
* | |||||
* @return Calculator | |||||
*/ | |||||
private static function detect() : Calculator | |||||
{ | |||||
if (\extension_loaded('gmp')) { | |||||
return new Calculator\GmpCalculator(); | |||||
} | |||||
if (\extension_loaded('bcmath')) { | |||||
return new Calculator\BcMathCalculator(); | |||||
} | |||||
return new Calculator\NativeCalculator(); | |||||
} | |||||
/** | |||||
* Extracts the sign & digits of the operands. | |||||
* | |||||
* @param string $a The first operand. | |||||
* @param string $b The second operand. | |||||
* | |||||
* @return array{0: bool, 1: bool, 2: string, 3: string} Whether $a and $b are negative, followed by their digits. | |||||
*/ | |||||
final protected function init(string $a, string $b) : array | |||||
{ | |||||
return [ | |||||
$aNeg = ($a[0] === '-'), | |||||
$bNeg = ($b[0] === '-'), | |||||
$aNeg ? \substr($a, 1) : $a, | |||||
$bNeg ? \substr($b, 1) : $b, | |||||
]; | |||||
} | |||||
/** | |||||
* Returns the absolute value of a number. | |||||
* | |||||
* @param string $n The number. | |||||
* | |||||
* @return string The absolute value. | |||||
*/ | |||||
final public function abs(string $n) : string | |||||
{ | |||||
return ($n[0] === '-') ? \substr($n, 1) : $n; | |||||
} | |||||
/** | |||||
* Negates a number. | |||||
* | |||||
* @param string $n The number. | |||||
* | |||||
* @return string The negated value. | |||||
*/ | |||||
final public function neg(string $n) : string | |||||
{ | |||||
if ($n === '0') { | |||||
return '0'; | |||||
} | |||||
if ($n[0] === '-') { | |||||
return \substr($n, 1); | |||||
} | |||||
return '-' . $n; | |||||
} | |||||
/** | |||||
* Compares two numbers. | |||||
* | |||||
* @param string $a The first number. | |||||
* @param string $b The second number. | |||||
* | |||||
* @return int [-1, 0, 1] If the first number is less than, equal to, or greater than the second number. | |||||
*/ | |||||
final public function cmp(string $a, string $b) : int | |||||
{ | |||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); | |||||
if ($aNeg && ! $bNeg) { | |||||
return -1; | |||||
} | |||||
if ($bNeg && ! $aNeg) { | |||||
return 1; | |||||
} | |||||
$aLen = \strlen($aDig); | |||||
$bLen = \strlen($bDig); | |||||
if ($aLen < $bLen) { | |||||
$result = -1; | |||||
} elseif ($aLen > $bLen) { | |||||
$result = 1; | |||||
} else { | |||||
$result = $aDig <=> $bDig; | |||||
} | |||||
return $aNeg ? -$result : $result; | |||||
} | |||||
/** | |||||
* Adds two numbers. | |||||
* | |||||
* @param string $a The augend. | |||||
* @param string $b The addend. | |||||
* | |||||
* @return string The sum. | |||||
*/ | |||||
abstract public function add(string $a, string $b) : string; | |||||
/** | |||||
* Subtracts two numbers. | |||||
* | |||||
* @param string $a The minuend. | |||||
* @param string $b The subtrahend. | |||||
* | |||||
* @return string The difference. | |||||
*/ | |||||
abstract public function sub(string $a, string $b) : string; | |||||
/** | |||||
* Multiplies two numbers. | |||||
* | |||||
* @param string $a The multiplicand. | |||||
* @param string $b The multiplier. | |||||
* | |||||
* @return string The product. | |||||
*/ | |||||
abstract public function mul(string $a, string $b) : string; | |||||
/** | |||||
* Returns the quotient of the division of two numbers. | |||||
* | |||||
* @param string $a The dividend. | |||||
* @param string $b The divisor, must not be zero. | |||||
* | |||||
* @return string The quotient. | |||||
*/ | |||||
abstract public function divQ(string $a, string $b) : string; | |||||
/** | |||||
* Returns the remainder of the division of two numbers. | |||||
* | |||||
* @param string $a The dividend. | |||||
* @param string $b The divisor, must not be zero. | |||||
* | |||||
* @return string The remainder. | |||||
*/ | |||||
abstract public function divR(string $a, string $b) : string; | |||||
/** | |||||
* Returns the quotient and remainder of the division of two numbers. | |||||
* | |||||
* @param string $a The dividend. | |||||
* @param string $b The divisor, must not be zero. | |||||
* | |||||
* @return string[] An array containing the quotient and remainder. | |||||
*/ | |||||
abstract public function divQR(string $a, string $b) : array; | |||||
/** | |||||
* Exponentiates a number. | |||||
* | |||||
* @param string $a The base number. | |||||
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER. | |||||
* | |||||
* @return string The power. | |||||
*/ | |||||
abstract public function pow(string $a, int $e) : string; | |||||
/** | |||||
* @param string $a | |||||
* @param string $b The modulus; must not be zero. | |||||
* | |||||
* @return string | |||||
*/ | |||||
public function mod(string $a, string $b) : string | |||||
{ | |||||
return $this->divR($this->add($this->divR($a, $b), $b), $b); | |||||
} | |||||
/** | |||||
* Returns the modular multiplicative inverse of $x modulo $m. | |||||
* | |||||
* If $x has no multiplicative inverse mod m, this method must return null. | |||||
* | |||||
* This method can be overridden by the concrete implementation if the underlying library has built-in support. | |||||
* | |||||
* @param string $x | |||||
* @param string $m The modulus; must not be negative or zero. | |||||
* | |||||
* @return string|null | |||||
*/ | |||||
public function modInverse(string $x, string $m) : ?string | |||||
{ | |||||
if ($m === '1') { | |||||
return '0'; | |||||
} | |||||
$modVal = $x; | |||||
if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) { | |||||
$modVal = $this->mod($x, $m); | |||||
} | |||||
$x = '0'; | |||||
$y = '0'; | |||||
$g = $this->gcdExtended($modVal, $m, $x, $y); | |||||
if ($g !== '1') { | |||||
return null; | |||||
} | |||||
return $this->mod($this->add($this->mod($x, $m), $m), $m); | |||||
} | |||||
/** | |||||
* Raises a number into power with modulo. | |||||
* | |||||
* @param string $base The base number; must be positive or zero. | |||||
* @param string $exp The exponent; must be positive or zero. | |||||
* @param string $mod The modulus; must be strictly positive. | |||||
* | |||||
* @return string The power. | |||||
*/ | |||||
abstract public function modPow(string $base, string $exp, string $mod) : string; | |||||
/** | |||||
* Returns the greatest common divisor of the two numbers. | |||||
* | |||||
* This method can be overridden by the concrete implementation if the underlying library | |||||
* has built-in support for GCD calculations. | |||||
* | |||||
* @param string $a The first number. | |||||
* @param string $b The second number. | |||||
* | |||||
* @return string The GCD, always positive, or zero if both arguments are zero. | |||||
*/ | |||||
public function gcd(string $a, string $b) : string | |||||
{ | |||||
if ($a === '0') { | |||||
return $this->abs($b); | |||||
} | |||||
if ($b === '0') { | |||||
return $this->abs($a); | |||||
} | |||||
return $this->gcd($b, $this->divR($a, $b)); | |||||
} | |||||
private function gcdExtended(string $a, string $b, string &$x, string &$y) : string | |||||
{ | |||||
if ($a === '0') { | |||||
$x = '0'; | |||||
$y = '1'; | |||||
return $b; | |||||
} | |||||
$x1 = '0'; | |||||
$y1 = '0'; | |||||
$gcd = $this->gcdExtended($this->mod($b, $a), $a, $x1, $y1); | |||||
$x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1)); | |||||
$y = $x1; | |||||
return $gcd; | |||||
} | |||||
/** | |||||
* Returns the square root of the given number, rounded down. | |||||
* | |||||
* The result is the largest x such that x² ≤ n. | |||||
* The input MUST NOT be negative. | |||||
* | |||||
* @param string $n The number. | |||||
* | |||||
* @return string The square root. | |||||
*/ | |||||
abstract public function sqrt(string $n) : string; | |||||
/** | |||||
* Converts a number from an arbitrary base. | |||||
* | |||||
* This method can be overridden by the concrete implementation if the underlying library | |||||
* has built-in support for base conversion. | |||||
* | |||||
* @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base. | |||||
* @param int $base The base of the number, validated from 2 to 36. | |||||
* | |||||
* @return string The converted number, following the Calculator conventions. | |||||
*/ | |||||
public function fromBase(string $number, int $base) : string | |||||
{ | |||||
return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base); | |||||
} | |||||
/** | |||||
* Converts a number to an arbitrary base. | |||||
* | |||||
* This method can be overridden by the concrete implementation if the underlying library | |||||
* has built-in support for base conversion. | |||||
* | |||||
* @param string $number The number to convert, following the Calculator conventions. | |||||
* @param int $base The base to convert to, validated from 2 to 36. | |||||
* | |||||
* @return string The converted number, lowercase. | |||||
*/ | |||||
public function toBase(string $number, int $base) : string | |||||
{ | |||||
$negative = ($number[0] === '-'); | |||||
if ($negative) { | |||||
$number = \substr($number, 1); | |||||
} | |||||
$number = $this->toArbitraryBase($number, self::ALPHABET, $base); | |||||
if ($negative) { | |||||
return '-' . $number; | |||||
} | |||||
return $number; | |||||
} | |||||
/** | |||||
* Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10. | |||||
* | |||||
* @param string $number The number to convert, validated as a non-empty string, | |||||
* containing only chars in the given alphabet/base. | |||||
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. | |||||
* @param int $base The base of the number, validated from 2 to alphabet length. | |||||
* | |||||
* @return string The number in base 10, following the Calculator conventions. | |||||
*/ | |||||
final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string | |||||
{ | |||||
// remove leading "zeros" | |||||
$number = \ltrim($number, $alphabet[0]); | |||||
if ($number === '') { | |||||
return '0'; | |||||
} | |||||
// optimize for "one" | |||||
if ($number === $alphabet[1]) { | |||||
return '1'; | |||||
} | |||||
$result = '0'; | |||||
$power = '1'; | |||||
$base = (string) $base; | |||||
for ($i = \strlen($number) - 1; $i >= 0; $i--) { | |||||
$index = \strpos($alphabet, $number[$i]); | |||||
if ($index !== 0) { | |||||
$result = $this->add($result, ($index === 1) | |||||
? $power | |||||
: $this->mul($power, (string) $index) | |||||
); | |||||
} | |||||
if ($i !== 0) { | |||||
$power = $this->mul($power, $base); | |||||
} | |||||
} | |||||
return $result; | |||||
} | |||||
/** | |||||
* Converts a non-negative number to an arbitrary base using a custom alphabet. | |||||
* | |||||
* @param string $number The number to convert, positive or zero, following the Calculator conventions. | |||||
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum. | |||||
* @param int $base The base to convert to, validated from 2 to alphabet length. | |||||
* | |||||
* @return string The converted number in the given alphabet. | |||||
*/ | |||||
final public function toArbitraryBase(string $number, string $alphabet, int $base) : string | |||||
{ | |||||
if ($number === '0') { | |||||
return $alphabet[0]; | |||||
} | |||||
$base = (string) $base; | |||||
$result = ''; | |||||
while ($number !== '0') { | |||||
[$number, $remainder] = $this->divQR($number, $base); | |||||
$remainder = (int) $remainder; | |||||
$result .= $alphabet[$remainder]; | |||||
} | |||||
return \strrev($result); | |||||
} | |||||
/** | |||||
* Performs a rounded division. | |||||
* | |||||
* Rounding is performed when the remainder of the division is not zero. | |||||
* | |||||
* @param string $a The dividend. | |||||
* @param string $b The divisor, must not be zero. | |||||
* @param int $roundingMode The rounding mode. | |||||
* | |||||
* @return string | |||||
* | |||||
* @throws \InvalidArgumentException If the rounding mode is invalid. | |||||
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary. | |||||
*/ | |||||
final public function divRound(string $a, string $b, int $roundingMode) : string | |||||
{ | |||||
[$quotient, $remainder] = $this->divQR($a, $b); | |||||
$hasDiscardedFraction = ($remainder !== '0'); | |||||
$isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-'); | |||||
$discardedFractionSign = function() use ($remainder, $b) : int { | |||||
$r = $this->abs($this->mul($remainder, '2')); | |||||
$b = $this->abs($b); | |||||
return $this->cmp($r, $b); | |||||
}; | |||||
$increment = false; | |||||
switch ($roundingMode) { | |||||
case RoundingMode::UNNECESSARY: | |||||
if ($hasDiscardedFraction) { | |||||
throw RoundingNecessaryException::roundingNecessary(); | |||||
} | |||||
break; | |||||
case RoundingMode::UP: | |||||
$increment = $hasDiscardedFraction; | |||||
break; | |||||
case RoundingMode::DOWN: | |||||
break; | |||||
case RoundingMode::CEILING: | |||||
$increment = $hasDiscardedFraction && $isPositiveOrZero; | |||||
break; | |||||
case RoundingMode::FLOOR: | |||||
$increment = $hasDiscardedFraction && ! $isPositiveOrZero; | |||||
break; | |||||
case RoundingMode::HALF_UP: | |||||
$increment = $discardedFractionSign() >= 0; | |||||
break; | |||||
case RoundingMode::HALF_DOWN: | |||||
$increment = $discardedFractionSign() > 0; | |||||
break; | |||||
case RoundingMode::HALF_CEILING: | |||||
$increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0; | |||||
break; | |||||
case RoundingMode::HALF_FLOOR: | |||||
$increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; | |||||
break; | |||||
case RoundingMode::HALF_EVEN: | |||||
$lastDigit = (int) $quotient[-1]; | |||||
$lastDigitIsEven = ($lastDigit % 2 === 0); | |||||
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0; | |||||
break; | |||||
default: | |||||
throw new \InvalidArgumentException('Invalid rounding mode.'); | |||||
} | |||||
if ($increment) { | |||||
return $this->add($quotient, $isPositiveOrZero ? '1' : '-1'); | |||||
} | |||||
return $quotient; | |||||
} | |||||
/** | |||||
* Calculates bitwise AND of two numbers. | |||||
* | |||||
* This method can be overridden by the concrete implementation if the underlying library | |||||
* has built-in support for bitwise operations. | |||||
* | |||||
* @param string $a | |||||
* @param string $b | |||||
* | |||||
* @return string | |||||
*/ | |||||
public function and(string $a, string $b) : string | |||||
{ | |||||
return $this->bitwise('and', $a, $b); | |||||
} | |||||
/** | |||||
* Calculates bitwise OR of two numbers. | |||||
* | |||||
* This method can be overridden by the concrete implementation if the underlying library | |||||
* has built-in support for bitwise operations. | |||||
* | |||||
* @param string $a | |||||
* @param string $b | |||||
* | |||||
* @return string | |||||
*/ | |||||
public function or(string $a, string $b) : string | |||||
{ | |||||
return $this->bitwise('or', $a, $b); | |||||
} | |||||
/** | |||||
* Calculates bitwise XOR of two numbers. | |||||
* | |||||
* This method can be overridden by the concrete implementation if the underlying library | |||||
* has built-in support for bitwise operations. | |||||
* | |||||
* @param string $a | |||||
* @param string $b | |||||
* | |||||
* @return string | |||||
*/ | |||||
public function xor(string $a, string $b) : string | |||||
{ | |||||
return $this->bitwise('xor', $a, $b); | |||||
} | |||||
/** | |||||
* Performs a bitwise operation on a decimal number. | |||||
* | |||||
* @param string $operator The operator to use, must be "and", "or" or "xor". | |||||
* @param string $a The left operand. | |||||
* @param string $b The right operand. | |||||
* | |||||
* @return string | |||||
*/ | |||||
private function bitwise(string $operator, string $a, string $b) : string | |||||
{ | |||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); | |||||
$aBin = $this->toBinary($aDig); | |||||
$bBin = $this->toBinary($bDig); | |||||
$aLen = \strlen($aBin); | |||||
$bLen = \strlen($bBin); | |||||
if ($aLen > $bLen) { | |||||
$bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin; | |||||
} elseif ($bLen > $aLen) { | |||||
$aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin; | |||||
} | |||||
if ($aNeg) { | |||||
$aBin = $this->twosComplement($aBin); | |||||
} | |||||
if ($bNeg) { | |||||
$bBin = $this->twosComplement($bBin); | |||||
} | |||||
switch ($operator) { | |||||
case 'and': | |||||
$value = $aBin & $bBin; | |||||
$negative = ($aNeg and $bNeg); | |||||
break; | |||||
case 'or': | |||||
$value = $aBin | $bBin; | |||||
$negative = ($aNeg or $bNeg); | |||||
break; | |||||
case 'xor': | |||||
$value = $aBin ^ $bBin; | |||||
$negative = ($aNeg xor $bNeg); | |||||
break; | |||||
// @codeCoverageIgnoreStart | |||||
default: | |||||
throw new \InvalidArgumentException('Invalid bitwise operator.'); | |||||
// @codeCoverageIgnoreEnd | |||||
} | |||||
if ($negative) { | |||||
$value = $this->twosComplement($value); | |||||
} | |||||
$result = $this->toDecimal($value); | |||||
return $negative ? $this->neg($result) : $result; | |||||
} | |||||
/** | |||||
* @psalm-suppress InvalidOperand | |||||
* @see https://github.com/vimeo/psalm/issues/4456 | |||||
* | |||||
* @param string $number A positive, binary number. | |||||
* | |||||
* @return string | |||||
*/ | |||||
private function twosComplement(string $number) : string | |||||
{ | |||||
$xor = \str_repeat("\xff", \strlen($number)); | |||||
$number ^= $xor; | |||||
for ($i = \strlen($number) - 1; $i >= 0; $i--) { | |||||
$byte = \ord($number[$i]); | |||||
if (++$byte !== 256) { | |||||
$number[$i] = \chr($byte); | |||||
break; | |||||
} | |||||
$number[$i] = "\x00"; | |||||
if ($i === 0) { | |||||
$number = "\x01" . $number; | |||||
} | |||||
} | |||||
return $number; | |||||
} | |||||
/** | |||||
* Converts a decimal number to a binary string. | |||||
* | |||||
* @param string $number The number to convert, positive or zero, only digits. | |||||
* | |||||
* @return string | |||||
*/ | |||||
private function toBinary(string $number) : string | |||||
{ | |||||
$result = ''; | |||||
while ($number !== '0') { | |||||
[$number, $remainder] = $this->divQR($number, '256'); | |||||
$result .= \chr((int) $remainder); | |||||
} | |||||
return \strrev($result); | |||||
} | |||||
/** | |||||
* Returns the positive decimal representation of a binary number. | |||||
* | |||||
* @param string $bytes The bytes representing the number. | |||||
* | |||||
* @return string | |||||
*/ | |||||
private function toDecimal(string $bytes) : string | |||||
{ | |||||
$result = '0'; | |||||
$power = '1'; | |||||
for ($i = \strlen($bytes) - 1; $i >= 0; $i--) { | |||||
$index = \ord($bytes[$i]); | |||||
if ($index !== 0) { | |||||
$result = $this->add($result, ($index === 1) | |||||
? $power | |||||
: $this->mul($power, (string) $index) | |||||
); | |||||
} | |||||
if ($i !== 0) { | |||||
$power = $this->mul($power, '256'); | |||||
} | |||||
} | |||||
return $result; | |||||
} | |||||
} |
@@ -0,0 +1,116 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math\Internal\Calculator; | |||||
use Brick\Math\Internal\Calculator; | |||||
/** | |||||
* Calculator implementation built around the bcmath library. | |||||
* | |||||
* @internal | |||||
* | |||||
* @psalm-immutable | |||||
*/ | |||||
class BcMathCalculator extends Calculator | |||||
{ | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function add(string $a, string $b) : string | |||||
{ | |||||
return \bcadd($a, $b, 0); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function sub(string $a, string $b) : string | |||||
{ | |||||
return \bcsub($a, $b, 0); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function mul(string $a, string $b) : string | |||||
{ | |||||
return \bcmul($a, $b, 0); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
* | |||||
* @psalm-suppress InvalidNullableReturnType | |||||
* @psalm-suppress NullableReturnStatement | |||||
*/ | |||||
public function divQ(string $a, string $b) : string | |||||
{ | |||||
return \bcdiv($a, $b, 0); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
* | |||||
* @psalm-suppress InvalidNullableReturnType | |||||
* @psalm-suppress NullableReturnStatement | |||||
*/ | |||||
public function divR(string $a, string $b) : string | |||||
{ | |||||
if (version_compare(PHP_VERSION, '7.2') >= 0) { | |||||
return \bcmod($a, $b, 0); | |||||
} | |||||
return \bcmod($a, $b); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function divQR(string $a, string $b) : array | |||||
{ | |||||
$q = \bcdiv($a, $b, 0); | |||||
if (version_compare(PHP_VERSION, '7.2') >= 0) { | |||||
$r = \bcmod($a, $b, 0); | |||||
} else { | |||||
$r = \bcmod($a, $b); | |||||
} | |||||
assert($q !== null); | |||||
assert($r !== null); | |||||
return [$q, $r]; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function pow(string $a, int $e) : string | |||||
{ | |||||
return \bcpow($a, (string) $e, 0); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
* | |||||
* @psalm-suppress InvalidNullableReturnType | |||||
* @psalm-suppress NullableReturnStatement | |||||
*/ | |||||
public function modPow(string $base, string $exp, string $mod) : string | |||||
{ | |||||
return \bcpowmod($base, $exp, $mod, 0); | |||||
} | |||||
/** | |||||
* {@inheritDoc} | |||||
* | |||||
* @psalm-suppress NullableReturnStatement | |||||
* @psalm-suppress InvalidNullableReturnType | |||||
*/ | |||||
public function sqrt(string $n) : string | |||||
{ | |||||
return \bcsqrt($n, 0); | |||||
} | |||||
} |
@@ -0,0 +1,156 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math\Internal\Calculator; | |||||
use Brick\Math\Internal\Calculator; | |||||
/** | |||||
* Calculator implementation built around the GMP library. | |||||
* | |||||
* @internal | |||||
* | |||||
* @psalm-immutable | |||||
*/ | |||||
class GmpCalculator extends Calculator | |||||
{ | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function add(string $a, string $b) : string | |||||
{ | |||||
return \gmp_strval(\gmp_add($a, $b)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function sub(string $a, string $b) : string | |||||
{ | |||||
return \gmp_strval(\gmp_sub($a, $b)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function mul(string $a, string $b) : string | |||||
{ | |||||
return \gmp_strval(\gmp_mul($a, $b)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function divQ(string $a, string $b) : string | |||||
{ | |||||
return \gmp_strval(\gmp_div_q($a, $b)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function divR(string $a, string $b) : string | |||||
{ | |||||
return \gmp_strval(\gmp_div_r($a, $b)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function divQR(string $a, string $b) : array | |||||
{ | |||||
[$q, $r] = \gmp_div_qr($a, $b); | |||||
return [ | |||||
\gmp_strval($q), | |||||
\gmp_strval($r) | |||||
]; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function pow(string $a, int $e) : string | |||||
{ | |||||
return \gmp_strval(\gmp_pow($a, $e)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function modInverse(string $x, string $m) : ?string | |||||
{ | |||||
$result = \gmp_invert($x, $m); | |||||
if ($result === false) { | |||||
return null; | |||||
} | |||||
return \gmp_strval($result); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function modPow(string $base, string $exp, string $mod) : string | |||||
{ | |||||
return \gmp_strval(\gmp_powm($base, $exp, $mod)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function gcd(string $a, string $b) : string | |||||
{ | |||||
return \gmp_strval(\gmp_gcd($a, $b)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function fromBase(string $number, int $base) : string | |||||
{ | |||||
return \gmp_strval(\gmp_init($number, $base)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function toBase(string $number, int $base) : string | |||||
{ | |||||
return \gmp_strval($number, $base); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function and(string $a, string $b) : string | |||||
{ | |||||
return \gmp_strval(\gmp_and($a, $b)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function or(string $a, string $b) : string | |||||
{ | |||||
return \gmp_strval(\gmp_or($a, $b)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function xor(string $a, string $b) : string | |||||
{ | |||||
return \gmp_strval(\gmp_xor($a, $b)); | |||||
} | |||||
/** | |||||
* {@inheritDoc} | |||||
*/ | |||||
public function sqrt(string $n) : string | |||||
{ | |||||
return \gmp_strval(\gmp_sqrt($n)); | |||||
} | |||||
} |
@@ -0,0 +1,634 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math\Internal\Calculator; | |||||
use Brick\Math\Internal\Calculator; | |||||
/** | |||||
* Calculator implementation using only native PHP code. | |||||
* | |||||
* @internal | |||||
* | |||||
* @psalm-immutable | |||||
*/ | |||||
class NativeCalculator extends Calculator | |||||
{ | |||||
/** | |||||
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow. | |||||
* For multiplication, this represents the max sum of the lengths of both operands. | |||||
* | |||||
* For addition, it is assumed that an extra digit can hold a carry (1) without overflowing. | |||||
* Example: 32-bit: max number 1,999,999,999 (9 digits + carry) | |||||
* 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry) | |||||
* | |||||
* @var int | |||||
*/ | |||||
private $maxDigits; | |||||
/** | |||||
* Class constructor. | |||||
* | |||||
* @codeCoverageIgnore | |||||
*/ | |||||
public function __construct() | |||||
{ | |||||
switch (PHP_INT_SIZE) { | |||||
case 4: | |||||
$this->maxDigits = 9; | |||||
break; | |||||
case 8: | |||||
$this->maxDigits = 18; | |||||
break; | |||||
default: | |||||
throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.'); | |||||
} | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function add(string $a, string $b) : string | |||||
{ | |||||
/** | |||||
* @psalm-var numeric-string $a | |||||
* @psalm-var numeric-string $b | |||||
*/ | |||||
$result = $a + $b; | |||||
if (is_int($result)) { | |||||
return (string) $result; | |||||
} | |||||
if ($a === '0') { | |||||
return $b; | |||||
} | |||||
if ($b === '0') { | |||||
return $a; | |||||
} | |||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); | |||||
$result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig); | |||||
if ($aNeg) { | |||||
$result = $this->neg($result); | |||||
} | |||||
return $result; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function sub(string $a, string $b) : string | |||||
{ | |||||
return $this->add($a, $this->neg($b)); | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function mul(string $a, string $b) : string | |||||
{ | |||||
/** | |||||
* @psalm-var numeric-string $a | |||||
* @psalm-var numeric-string $b | |||||
*/ | |||||
$result = $a * $b; | |||||
if (is_int($result)) { | |||||
return (string) $result; | |||||
} | |||||
if ($a === '0' || $b === '0') { | |||||
return '0'; | |||||
} | |||||
if ($a === '1') { | |||||
return $b; | |||||
} | |||||
if ($b === '1') { | |||||
return $a; | |||||
} | |||||
if ($a === '-1') { | |||||
return $this->neg($b); | |||||
} | |||||
if ($b === '-1') { | |||||
return $this->neg($a); | |||||
} | |||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); | |||||
$result = $this->doMul($aDig, $bDig); | |||||
if ($aNeg !== $bNeg) { | |||||
$result = $this->neg($result); | |||||
} | |||||
return $result; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function divQ(string $a, string $b) : string | |||||
{ | |||||
return $this->divQR($a, $b)[0]; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function divR(string $a, string $b): string | |||||
{ | |||||
return $this->divQR($a, $b)[1]; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function divQR(string $a, string $b) : array | |||||
{ | |||||
if ($a === '0') { | |||||
return ['0', '0']; | |||||
} | |||||
if ($a === $b) { | |||||
return ['1', '0']; | |||||
} | |||||
if ($b === '1') { | |||||
return [$a, '0']; | |||||
} | |||||
if ($b === '-1') { | |||||
return [$this->neg($a), '0']; | |||||
} | |||||
/** @psalm-var numeric-string $a */ | |||||
$na = $a * 1; // cast to number | |||||
if (is_int($na)) { | |||||
/** @psalm-var numeric-string $b */ | |||||
$nb = $b * 1; | |||||
if (is_int($nb)) { | |||||
// the only division that may overflow is PHP_INT_MIN / -1, | |||||
// which cannot happen here as we've already handled a divisor of -1 above. | |||||
$r = $na % $nb; | |||||
$q = ($na - $r) / $nb; | |||||
assert(is_int($q)); | |||||
return [ | |||||
(string) $q, | |||||
(string) $r | |||||
]; | |||||
} | |||||
} | |||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b); | |||||
[$q, $r] = $this->doDiv($aDig, $bDig); | |||||
if ($aNeg !== $bNeg) { | |||||
$q = $this->neg($q); | |||||
} | |||||
if ($aNeg) { | |||||
$r = $this->neg($r); | |||||
} | |||||
return [$q, $r]; | |||||
} | |||||
/** | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function pow(string $a, int $e) : string | |||||
{ | |||||
if ($e === 0) { | |||||
return '1'; | |||||
} | |||||
if ($e === 1) { | |||||
return $a; | |||||
} | |||||
$odd = $e % 2; | |||||
$e -= $odd; | |||||
$aa = $this->mul($a, $a); | |||||
/** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */ | |||||
$result = $this->pow($aa, $e / 2); | |||||
if ($odd === 1) { | |||||
$result = $this->mul($result, $a); | |||||
} | |||||
return $result; | |||||
} | |||||
/** | |||||
* Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/ | |||||
* | |||||
* {@inheritdoc} | |||||
*/ | |||||
public function modPow(string $base, string $exp, string $mod) : string | |||||
{ | |||||
// special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0) | |||||
if ($base === '0' && $exp === '0' && $mod === '1') { | |||||
return '0'; | |||||
} | |||||
// special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0) | |||||
if ($exp === '0' && $mod === '1') { | |||||
return '0'; | |||||
} | |||||
$x = $base; | |||||
$res = '1'; | |||||
// numbers are positive, so we can use remainder instead of modulo | |||||
$x = $this->divR($x, $mod); | |||||
while ($exp !== '0') { | |||||
if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd | |||||
$res = $this->divR($this->mul($res, $x), $mod); | |||||
} | |||||
$exp = $this->divQ($exp, '2'); | |||||
$x = $this->divR($this->mul($x, $x), $mod); | |||||
} | |||||
return $res; | |||||
} | |||||
/** | |||||
* Adapted from https://cp-algorithms.com/num_methods/roots_newton.html | |||||
* | |||||
* {@inheritDoc} | |||||
*/ | |||||
public function sqrt(string $n) : string | |||||
{ | |||||
if ($n === '0') { | |||||
return '0'; | |||||
} | |||||
// initial approximation | |||||
$x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1); | |||||
$decreased = false; | |||||
for (;;) { | |||||
$nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2'); | |||||
if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) { | |||||
break; | |||||
} | |||||
$decreased = $this->cmp($nx, $x) < 0; | |||||
$x = $nx; | |||||
} | |||||
return $x; | |||||
} | |||||
/** | |||||
* Performs the addition of two non-signed large integers. | |||||
* | |||||
* @param string $a The first operand. | |||||
* @param string $b The second operand. | |||||
* | |||||
* @return string | |||||
*/ | |||||
private function doAdd(string $a, string $b) : string | |||||
{ | |||||
[$a, $b, $length] = $this->pad($a, $b); | |||||
$carry = 0; | |||||
$result = ''; | |||||
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { | |||||
$blockLength = $this->maxDigits; | |||||
if ($i < 0) { | |||||
$blockLength += $i; | |||||
/** @psalm-suppress LoopInvalidation */ | |||||
$i = 0; | |||||
} | |||||
/** @psalm-var numeric-string $blockA */ | |||||
$blockA = \substr($a, $i, $blockLength); | |||||
/** @psalm-var numeric-string $blockB */ | |||||
$blockB = \substr($b, $i, $blockLength); | |||||
$sum = (string) ($blockA + $blockB + $carry); | |||||
$sumLength = \strlen($sum); | |||||
if ($sumLength > $blockLength) { | |||||
$sum = \substr($sum, 1); | |||||
$carry = 1; | |||||
} else { | |||||
if ($sumLength < $blockLength) { | |||||
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum; | |||||
} | |||||
$carry = 0; | |||||
} | |||||
$result = $sum . $result; | |||||
if ($i === 0) { | |||||
break; | |||||
} | |||||
} | |||||
if ($carry === 1) { | |||||
$result = '1' . $result; | |||||
} | |||||
return $result; | |||||
} | |||||
/** | |||||
* Performs the subtraction of two non-signed large integers. | |||||
* | |||||
* @param string $a The first operand. | |||||
* @param string $b The second operand. | |||||
* | |||||
* @return string | |||||
*/ | |||||
private function doSub(string $a, string $b) : string | |||||
{ | |||||
if ($a === $b) { | |||||
return '0'; | |||||
} | |||||
// Ensure that we always subtract to a positive result: biggest minus smallest. | |||||
$cmp = $this->doCmp($a, $b); | |||||
$invert = ($cmp === -1); | |||||
if ($invert) { | |||||
$c = $a; | |||||
$a = $b; | |||||
$b = $c; | |||||
} | |||||
[$a, $b, $length] = $this->pad($a, $b); | |||||
$carry = 0; | |||||
$result = ''; | |||||
$complement = 10 ** $this->maxDigits; | |||||
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) { | |||||
$blockLength = $this->maxDigits; | |||||
if ($i < 0) { | |||||
$blockLength += $i; | |||||
/** @psalm-suppress LoopInvalidation */ | |||||
$i = 0; | |||||
} | |||||
/** @psalm-var numeric-string $blockA */ | |||||
$blockA = \substr($a, $i, $blockLength); | |||||
/** @psalm-var numeric-string $blockB */ | |||||
$blockB = \substr($b, $i, $blockLength); | |||||
$sum = $blockA - $blockB - $carry; | |||||
if ($sum < 0) { | |||||
$sum += $complement; | |||||
$carry = 1; | |||||
} else { | |||||
$carry = 0; | |||||
} | |||||
$sum = (string) $sum; | |||||
$sumLength = \strlen($sum); | |||||
if ($sumLength < $blockLength) { | |||||
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum; | |||||
} | |||||
$result = $sum . $result; | |||||
if ($i === 0) { | |||||
break; | |||||
} | |||||
} | |||||
// Carry cannot be 1 when the loop ends, as a > b | |||||
assert($carry === 0); | |||||
$result = \ltrim($result, '0'); | |||||
if ($invert) { | |||||
$result = $this->neg($result); | |||||
} | |||||
return $result; | |||||
} | |||||
/** | |||||
* Performs the multiplication of two non-signed large integers. | |||||
* | |||||
* @param string $a The first operand. | |||||
* @param string $b The second operand. | |||||
* | |||||
* @return string | |||||
*/ | |||||
private function doMul(string $a, string $b) : string | |||||
{ | |||||
$x = \strlen($a); | |||||
$y = \strlen($b); | |||||
$maxDigits = \intdiv($this->maxDigits, 2); | |||||
$complement = 10 ** $maxDigits; | |||||
$result = '0'; | |||||
for ($i = $x - $maxDigits;; $i -= $maxDigits) { | |||||
$blockALength = $maxDigits; | |||||
if ($i < 0) { | |||||
$blockALength += $i; | |||||
/** @psalm-suppress LoopInvalidation */ | |||||
$i = 0; | |||||
} | |||||
$blockA = (int) \substr($a, $i, $blockALength); | |||||
$line = ''; | |||||
$carry = 0; | |||||
for ($j = $y - $maxDigits;; $j -= $maxDigits) { | |||||
$blockBLength = $maxDigits; | |||||
if ($j < 0) { | |||||
$blockBLength += $j; | |||||
/** @psalm-suppress LoopInvalidation */ | |||||
$j = 0; | |||||
} | |||||
$blockB = (int) \substr($b, $j, $blockBLength); | |||||
$mul = $blockA * $blockB + $carry; | |||||
$value = $mul % $complement; | |||||
$carry = ($mul - $value) / $complement; | |||||
$value = (string) $value; | |||||
$value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT); | |||||
$line = $value . $line; | |||||
if ($j === 0) { | |||||
break; | |||||
} | |||||
} | |||||
if ($carry !== 0) { | |||||
$line = $carry . $line; | |||||
} | |||||
$line = \ltrim($line, '0'); | |||||
if ($line !== '') { | |||||
$line .= \str_repeat('0', $x - $blockALength - $i); | |||||
$result = $this->add($result, $line); | |||||
} | |||||
if ($i === 0) { | |||||
break; | |||||
} | |||||
} | |||||
return $result; | |||||
} | |||||
/** | |||||
* Performs the division of two non-signed large integers. | |||||
* | |||||
* @param string $a The first operand. | |||||
* @param string $b The second operand. | |||||
* | |||||
* @return string[] The quotient and remainder. | |||||
*/ | |||||
private function doDiv(string $a, string $b) : array | |||||
{ | |||||
$cmp = $this->doCmp($a, $b); | |||||
if ($cmp === -1) { | |||||
return ['0', $a]; | |||||
} | |||||
$x = \strlen($a); | |||||
$y = \strlen($b); | |||||
// we now know that a >= b && x >= y | |||||
$q = '0'; // quotient | |||||
$r = $a; // remainder | |||||
$z = $y; // focus length, always $y or $y+1 | |||||
for (;;) { | |||||
$focus = \substr($a, 0, $z); | |||||
$cmp = $this->doCmp($focus, $b); | |||||
if ($cmp === -1) { | |||||
if ($z === $x) { // remainder < dividend | |||||
break; | |||||
} | |||||
$z++; | |||||
} | |||||
$zeros = \str_repeat('0', $x - $z); | |||||
$q = $this->add($q, '1' . $zeros); | |||||
$a = $this->sub($a, $b . $zeros); | |||||
$r = $a; | |||||
if ($r === '0') { // remainder == 0 | |||||
break; | |||||
} | |||||
$x = \strlen($a); | |||||
if ($x < $y) { // remainder < dividend | |||||
break; | |||||
} | |||||
$z = $y; | |||||
} | |||||
return [$q, $r]; | |||||
} | |||||
/** | |||||
* Compares two non-signed large numbers. | |||||
* | |||||
* @param string $a The first operand. | |||||
* @param string $b The second operand. | |||||
* | |||||
* @return int [-1, 0, 1] | |||||
*/ | |||||
private function doCmp(string $a, string $b) : int | |||||
{ | |||||
$x = \strlen($a); | |||||
$y = \strlen($b); | |||||
$cmp = $x <=> $y; | |||||
if ($cmp !== 0) { | |||||
return $cmp; | |||||
} | |||||
return \strcmp($a, $b) <=> 0; // enforce [-1, 0, 1] | |||||
} | |||||
/** | |||||
* Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length. | |||||
* | |||||
* The numbers must only consist of digits, without leading minus sign. | |||||
* | |||||
* @param string $a The first operand. | |||||
* @param string $b The second operand. | |||||
* | |||||
* @return array{0: string, 1: string, 2: int} | |||||
*/ | |||||
private function pad(string $a, string $b) : array | |||||
{ | |||||
$x = \strlen($a); | |||||
$y = \strlen($b); | |||||
if ($x > $y) { | |||||
$b = \str_repeat('0', $x - $y) . $b; | |||||
return [$a, $b, $x]; | |||||
} | |||||
if ($x < $y) { | |||||
$a = \str_repeat('0', $y - $x) . $a; | |||||
return [$a, $b, $y]; | |||||
} | |||||
return [$a, $b, $x]; | |||||
} | |||||
} |
@@ -0,0 +1,107 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Brick\Math; | |||||
/** | |||||
* Specifies a rounding behavior for numerical operations capable of discarding precision. | |||||
* | |||||
* Each rounding mode indicates how the least significant returned digit of a rounded result | |||||
* is to be calculated. If fewer digits are returned than the digits needed to represent the | |||||
* exact numerical result, the discarded digits will be referred to as the discarded fraction | |||||
* regardless the digits' contribution to the value of the number. In other words, considered | |||||
* as a numerical value, the discarded fraction could have an absolute value greater than one. | |||||
*/ | |||||
final class RoundingMode | |||||
{ | |||||
/** | |||||
* Private constructor. This class is not instantiable. | |||||
* | |||||
* @codeCoverageIgnore | |||||
*/ | |||||
private function __construct() | |||||
{ | |||||
} | |||||
/** | |||||
* Asserts that the requested operation has an exact result, hence no rounding is necessary. | |||||
* | |||||
* If this rounding mode is specified on an operation that yields a result that | |||||
* cannot be represented at the requested scale, a RoundingNecessaryException is thrown. | |||||
*/ | |||||
public const UNNECESSARY = 0; | |||||
/** | |||||
* Rounds away from zero. | |||||
* | |||||
* Always increments the digit prior to a nonzero discarded fraction. | |||||
* Note that this rounding mode never decreases the magnitude of the calculated value. | |||||
*/ | |||||
public const UP = 1; | |||||
/** | |||||
* Rounds towards zero. | |||||
* | |||||
* Never increments the digit prior to a discarded fraction (i.e., truncates). | |||||
* Note that this rounding mode never increases the magnitude of the calculated value. | |||||
*/ | |||||
public const DOWN = 2; | |||||
/** | |||||
* Rounds towards positive infinity. | |||||
* | |||||
* If the result is positive, behaves as for UP; if negative, behaves as for DOWN. | |||||
* Note that this rounding mode never decreases the calculated value. | |||||
*/ | |||||
public const CEILING = 3; | |||||
/** | |||||
* Rounds towards negative infinity. | |||||
* | |||||
* If the result is positive, behave as for DOWN; if negative, behave as for UP. | |||||
* Note that this rounding mode never increases the calculated value. | |||||
*/ | |||||
public const FLOOR = 4; | |||||
/** | |||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up. | |||||
* | |||||
* Behaves as for UP if the discarded fraction is >= 0.5; otherwise, behaves as for DOWN. | |||||
* Note that this is the rounding mode commonly taught at school. | |||||
*/ | |||||
public const HALF_UP = 5; | |||||
/** | |||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down. | |||||
* | |||||
* Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN. | |||||
*/ | |||||
public const HALF_DOWN = 6; | |||||
/** | |||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity. | |||||
* | |||||
* If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN. | |||||
*/ | |||||
public const HALF_CEILING = 7; | |||||
/** | |||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity. | |||||
* | |||||
* If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP. | |||||
*/ | |||||
public const HALF_FLOOR = 8; | |||||
/** | |||||
* Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor. | |||||
* | |||||
* Behaves as for HALF_UP if the digit to the left of the discarded fraction is odd; | |||||
* behaves as for HALF_DOWN if it's even. | |||||
* | |||||
* Note that this is the rounding mode that statistically minimizes | |||||
* cumulative error when applied repeatedly over a sequence of calculations. | |||||
* It is sometimes known as "Banker's rounding", and is chiefly used in the USA. | |||||
*/ | |||||
public const HALF_EVEN = 9; | |||||
} |
@@ -0,0 +1,477 @@ | |||||
<?php | |||||
/* | |||||
* This file is part of Composer. | |||||
* | |||||
* (c) Nils Adermann <naderman@naderman.de> | |||||
* Jordi Boggiano <j.boggiano@seld.be> | |||||
* | |||||
* For the full copyright and license information, please view the LICENSE | |||||
* file that was distributed with this source code. | |||||
*/ | |||||
namespace Composer\Autoload; | |||||
/** | |||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader. | |||||
* | |||||
* $loader = new \Composer\Autoload\ClassLoader(); | |||||
* | |||||
* // register classes with namespaces | |||||
* $loader->add('Symfony\Component', __DIR__.'/component'); | |||||
* $loader->add('Symfony', __DIR__.'/framework'); | |||||
* | |||||
* // activate the autoloader | |||||
* $loader->register(); | |||||
* | |||||
* // to enable searching the include path (eg. for PEAR packages) | |||||
* $loader->setUseIncludePath(true); | |||||
* | |||||
* In this example, if you try to use a class in the Symfony\Component | |||||
* namespace or one of its children (Symfony\Component\Console for instance), | |||||
* the autoloader will first look for the class under the component/ | |||||
* directory, and it will then fallback to the framework/ directory if not | |||||
* found before giving up. | |||||
* | |||||
* This class is loosely based on the Symfony UniversalClassLoader. | |||||
* | |||||
* @author Fabien Potencier <fabien@symfony.com> | |||||
* @author Jordi Boggiano <j.boggiano@seld.be> | |||||
* @see https://www.php-fig.org/psr/psr-0/ | |||||
* @see https://www.php-fig.org/psr/psr-4/ | |||||
*/ | |||||
class ClassLoader | |||||
{ | |||||
private $vendorDir; | |||||
// PSR-4 | |||||
private $prefixLengthsPsr4 = array(); | |||||
private $prefixDirsPsr4 = array(); | |||||
private $fallbackDirsPsr4 = array(); | |||||
// PSR-0 | |||||
private $prefixesPsr0 = array(); | |||||
private $fallbackDirsPsr0 = array(); | |||||
private $useIncludePath = false; | |||||
private $classMap = array(); | |||||
private $classMapAuthoritative = false; | |||||
private $missingClasses = array(); | |||||
private $apcuPrefix; | |||||
private static $registeredLoaders = array(); | |||||
public function __construct($vendorDir = null) | |||||
{ | |||||
$this->vendorDir = $vendorDir; | |||||
} | |||||
public function getPrefixes() | |||||
{ | |||||
if (!empty($this->prefixesPsr0)) { | |||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); | |||||
} | |||||
return array(); | |||||
} | |||||
public function getPrefixesPsr4() | |||||
{ | |||||
return $this->prefixDirsPsr4; | |||||
} | |||||
public function getFallbackDirs() | |||||
{ | |||||
return $this->fallbackDirsPsr0; | |||||
} | |||||
public function getFallbackDirsPsr4() | |||||
{ | |||||
return $this->fallbackDirsPsr4; | |||||
} | |||||
public function getClassMap() | |||||
{ | |||||
return $this->classMap; | |||||
} | |||||
/** | |||||
* @param array $classMap Class to filename map | |||||
*/ | |||||
public function addClassMap(array $classMap) | |||||
{ | |||||
if ($this->classMap) { | |||||
$this->classMap = array_merge($this->classMap, $classMap); | |||||
} else { | |||||
$this->classMap = $classMap; | |||||
} | |||||
} | |||||
/** | |||||
* Registers a set of PSR-0 directories for a given prefix, either | |||||
* appending or prepending to the ones previously set for this prefix. | |||||
* | |||||
* @param string $prefix The prefix | |||||
* @param array|string $paths The PSR-0 root directories | |||||
* @param bool $prepend Whether to prepend the directories | |||||
*/ | |||||
public function add($prefix, $paths, $prepend = false) | |||||
{ | |||||
if (!$prefix) { | |||||
if ($prepend) { | |||||
$this->fallbackDirsPsr0 = array_merge( | |||||
(array) $paths, | |||||
$this->fallbackDirsPsr0 | |||||
); | |||||
} else { | |||||
$this->fallbackDirsPsr0 = array_merge( | |||||
$this->fallbackDirsPsr0, | |||||
(array) $paths | |||||
); | |||||
} | |||||
return; | |||||
} | |||||
$first = $prefix[0]; | |||||
if (!isset($this->prefixesPsr0[$first][$prefix])) { | |||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths; | |||||
return; | |||||
} | |||||
if ($prepend) { | |||||
$this->prefixesPsr0[$first][$prefix] = array_merge( | |||||
(array) $paths, | |||||
$this->prefixesPsr0[$first][$prefix] | |||||
); | |||||
} else { | |||||
$this->prefixesPsr0[$first][$prefix] = array_merge( | |||||
$this->prefixesPsr0[$first][$prefix], | |||||
(array) $paths | |||||
); | |||||
} | |||||
} | |||||
/** | |||||
* Registers a set of PSR-4 directories for a given namespace, either | |||||
* appending or prepending to the ones previously set for this namespace. | |||||
* | |||||
* @param string $prefix The prefix/namespace, with trailing '\\' | |||||
* @param array|string $paths The PSR-4 base directories | |||||
* @param bool $prepend Whether to prepend the directories | |||||
* | |||||
* @throws \InvalidArgumentException | |||||
*/ | |||||
public function addPsr4($prefix, $paths, $prepend = false) | |||||
{ | |||||
if (!$prefix) { | |||||
// Register directories for the root namespace. | |||||
if ($prepend) { | |||||
$this->fallbackDirsPsr4 = array_merge( | |||||
(array) $paths, | |||||
$this->fallbackDirsPsr4 | |||||
); | |||||
} else { | |||||
$this->fallbackDirsPsr4 = array_merge( | |||||
$this->fallbackDirsPsr4, | |||||
(array) $paths | |||||
); | |||||
} | |||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) { | |||||
// Register directories for a new namespace. | |||||
$length = strlen($prefix); | |||||
if ('\\' !== $prefix[$length - 1]) { | |||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); | |||||
} | |||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; | |||||
$this->prefixDirsPsr4[$prefix] = (array) $paths; | |||||
} elseif ($prepend) { | |||||
// Prepend directories for an already registered namespace. | |||||
$this->prefixDirsPsr4[$prefix] = array_merge( | |||||
(array) $paths, | |||||
$this->prefixDirsPsr4[$prefix] | |||||
); | |||||
} else { | |||||
// Append directories for an already registered namespace. | |||||
$this->prefixDirsPsr4[$prefix] = array_merge( | |||||
$this->prefixDirsPsr4[$prefix], | |||||
(array) $paths | |||||
); | |||||
} | |||||
} | |||||
/** | |||||
* Registers a set of PSR-0 directories for a given prefix, | |||||
* replacing any others previously set for this prefix. | |||||
* | |||||
* @param string $prefix The prefix | |||||
* @param array|string $paths The PSR-0 base directories | |||||
*/ | |||||
public function set($prefix, $paths) | |||||
{ | |||||
if (!$prefix) { | |||||
$this->fallbackDirsPsr0 = (array) $paths; | |||||
} else { | |||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; | |||||
} | |||||
} | |||||
/** | |||||
* Registers a set of PSR-4 directories for a given namespace, | |||||
* replacing any others previously set for this namespace. | |||||
* | |||||
* @param string $prefix The prefix/namespace, with trailing '\\' | |||||
* @param array|string $paths The PSR-4 base directories | |||||
* | |||||
* @throws \InvalidArgumentException | |||||
*/ | |||||
public function setPsr4($prefix, $paths) | |||||
{ | |||||
if (!$prefix) { | |||||
$this->fallbackDirsPsr4 = (array) $paths; | |||||
} else { | |||||
$length = strlen($prefix); | |||||
if ('\\' !== $prefix[$length - 1]) { | |||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); | |||||
} | |||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; | |||||
$this->prefixDirsPsr4[$prefix] = (array) $paths; | |||||
} | |||||
} | |||||
/** | |||||
* Turns on searching the include path for class files. | |||||
* | |||||
* @param bool $useIncludePath | |||||
*/ | |||||
public function setUseIncludePath($useIncludePath) | |||||
{ | |||||
$this->useIncludePath = $useIncludePath; | |||||
} | |||||
/** | |||||
* Can be used to check if the autoloader uses the include path to check | |||||
* for classes. | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function getUseIncludePath() | |||||
{ | |||||
return $this->useIncludePath; | |||||
} | |||||
/** | |||||
* Turns off searching the prefix and fallback directories for classes | |||||
* that have not been registered with the class map. | |||||
* | |||||
* @param bool $classMapAuthoritative | |||||
*/ | |||||
public function setClassMapAuthoritative($classMapAuthoritative) | |||||
{ | |||||
$this->classMapAuthoritative = $classMapAuthoritative; | |||||
} | |||||
/** | |||||
* Should class lookup fail if not found in the current class map? | |||||
* | |||||
* @return bool | |||||
*/ | |||||
public function isClassMapAuthoritative() | |||||
{ | |||||
return $this->classMapAuthoritative; | |||||
} | |||||
/** | |||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled. | |||||
* | |||||
* @param string|null $apcuPrefix | |||||
*/ | |||||
public function setApcuPrefix($apcuPrefix) | |||||
{ | |||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; | |||||
} | |||||
/** | |||||
* The APCu prefix in use, or null if APCu caching is not enabled. | |||||
* | |||||
* @return string|null | |||||
*/ | |||||
public function getApcuPrefix() | |||||
{ | |||||
return $this->apcuPrefix; | |||||
} | |||||
/** | |||||
* Registers this instance as an autoloader. | |||||
* | |||||
* @param bool $prepend Whether to prepend the autoloader or not | |||||
*/ | |||||
public function register($prepend = false) | |||||
{ | |||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend); | |||||
if (null === $this->vendorDir) { | |||||
//no-op | |||||
} elseif ($prepend) { | |||||
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; | |||||
} else { | |||||
unset(self::$registeredLoaders[$this->vendorDir]); | |||||
self::$registeredLoaders[$this->vendorDir] = $this; | |||||
} | |||||
} | |||||
/** | |||||
* Unregisters this instance as an autoloader. | |||||
*/ | |||||
public function unregister() | |||||
{ | |||||
spl_autoload_unregister(array($this, 'loadClass')); | |||||
if (null !== $this->vendorDir) { | |||||
unset(self::$registeredLoaders[$this->vendorDir]); | |||||
} | |||||
} | |||||
/** | |||||
* Loads the given class or interface. | |||||
* | |||||
* @param string $class The name of the class | |||||
* @return bool|null True if loaded, null otherwise | |||||
*/ | |||||
public function loadClass($class) | |||||
{ | |||||
if ($file = $this->findFile($class)) { | |||||
includeFile($file); | |||||
return true; | |||||
} | |||||
} | |||||
/** | |||||
* Finds the path to the file where the class is defined. | |||||
* | |||||
* @param string $class The name of the class | |||||
* | |||||
* @return string|false The path if found, false otherwise | |||||
*/ | |||||
public function findFile($class) | |||||
{ | |||||
// class map lookup | |||||
if (isset($this->classMap[$class])) { | |||||
return $this->classMap[$class]; | |||||
} | |||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { | |||||
return false; | |||||
} | |||||
if (null !== $this->apcuPrefix) { | |||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit); | |||||
if ($hit) { | |||||
return $file; | |||||
} | |||||
} | |||||
$file = $this->findFileWithExtension($class, '.php'); | |||||
// Search for Hack files if we are running on HHVM | |||||
if (false === $file && defined('HHVM_VERSION')) { | |||||
$file = $this->findFileWithExtension($class, '.hh'); | |||||
} | |||||
if (null !== $this->apcuPrefix) { | |||||
apcu_add($this->apcuPrefix.$class, $file); | |||||
} | |||||
if (false === $file) { | |||||
// Remember that this class does not exist. | |||||
$this->missingClasses[$class] = true; | |||||
} | |||||
return $file; | |||||
} | |||||
/** | |||||
* Returns the currently registered loaders indexed by their corresponding vendor directories. | |||||
* | |||||
* @return self[] | |||||
*/ | |||||
public static function getRegisteredLoaders() | |||||
{ | |||||
return self::$registeredLoaders; | |||||
} | |||||
private function findFileWithExtension($class, $ext) | |||||
{ | |||||
// PSR-4 lookup | |||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; | |||||
$first = $class[0]; | |||||
if (isset($this->prefixLengthsPsr4[$first])) { | |||||
$subPath = $class; | |||||
while (false !== $lastPos = strrpos($subPath, '\\')) { | |||||
$subPath = substr($subPath, 0, $lastPos); | |||||
$search = $subPath . '\\'; | |||||
if (isset($this->prefixDirsPsr4[$search])) { | |||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); | |||||
foreach ($this->prefixDirsPsr4[$search] as $dir) { | |||||
if (file_exists($file = $dir . $pathEnd)) { | |||||
return $file; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// PSR-4 fallback dirs | |||||
foreach ($this->fallbackDirsPsr4 as $dir) { | |||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { | |||||
return $file; | |||||
} | |||||
} | |||||
// PSR-0 lookup | |||||
if (false !== $pos = strrpos($class, '\\')) { | |||||
// namespaced class name | |||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) | |||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); | |||||
} else { | |||||
// PEAR-like class name | |||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; | |||||
} | |||||
if (isset($this->prefixesPsr0[$first])) { | |||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { | |||||
if (0 === strpos($class, $prefix)) { | |||||
foreach ($dirs as $dir) { | |||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { | |||||
return $file; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// PSR-0 fallback dirs | |||||
foreach ($this->fallbackDirsPsr0 as $dir) { | |||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { | |||||
return $file; | |||||
} | |||||
} | |||||
// PSR-0 include paths. | |||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { | |||||
return $file; | |||||
} | |||||
return false; | |||||
} | |||||
} | |||||
/** | |||||
* Scope isolated include. | |||||
* | |||||
* Prevents access to $this/self from included files. | |||||
*/ | |||||
function includeFile($file) | |||||
{ | |||||
include $file; | |||||
} |
@@ -0,0 +1,19 @@ | |||||
Copyright (c) Nils Adermann, Jordi Boggiano | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is furnished | |||||
to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
THE SOFTWARE. |
@@ -0,0 +1,38 @@ | |||||
<?php | |||||
// autoload_files.php @generated by Composer | |||||
$vendorDir = dirname(dirname(__FILE__)); | |||||
$baseDir = dirname($vendorDir); | |||||
return array( | |||||
'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php', | |||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', | |||||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', | |||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', | |||||
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', | |||||
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', | |||||
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', | |||||
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', | |||||
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', | |||||
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', | |||||
'801c31d8ed748cfa537fa45402288c95' => $vendorDir . '/psy/psysh/src/functions.php', | |||||
'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php', | |||||
'2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', | |||||
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', | |||||
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php', | |||||
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', | |||||
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', | |||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', | |||||
'538ca81a9a966a6716601ecf48f4eaef' => $vendorDir . '/opis/closure/functions.php', | |||||
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php', | |||||
'265b4faa2b3a9766332744949e83bf97' => $vendorDir . '/laravel/framework/src/Illuminate/Collections/helpers.php', | |||||
'c7a3c339e7e14b60e06a2d7fcce9476b' => $vendorDir . '/laravel/framework/src/Illuminate/Events/functions.php', | |||||
'f0906e6318348a765ffb6eb24e0d0938' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php', | |||||
'58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php', | |||||
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', | |||||
'9cdd7b9056abc3081735233ba9dd9c7f' => $vendorDir . '/facade/flare-client-php/src/helpers.php', | |||||
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', | |||||
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', | |||||
'ed962a97bd972bc82007176b647d4e36' => $vendorDir . '/facade/ignition/src/helpers.php', | |||||
); |
@@ -0,0 +1,10 @@ | |||||
<?php | |||||
// autoload_namespaces.php @generated by Composer | |||||
$vendorDir = dirname(dirname(__FILE__)); | |||||
$baseDir = dirname($vendorDir); | |||||
return array( | |||||
'Mockery' => array($vendorDir . '/mockery/mockery/library'), | |||||
); |
@@ -0,0 +1,88 @@ | |||||
<?php | |||||
// autoload_psr4.php @generated by Composer | |||||
$vendorDir = dirname(dirname(__FILE__)); | |||||
$baseDir = dirname($vendorDir); | |||||
return array( | |||||
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'), | |||||
'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src'), | |||||
'XdgBaseDir\\' => array($vendorDir . '/dnoegel/php-xdg-base-dir/src'), | |||||
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'), | |||||
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), | |||||
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'), | |||||
'Tests\\' => array($baseDir . '/tests'), | |||||
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), | |||||
'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), | |||||
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), | |||||
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), | |||||
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), | |||||
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'), | |||||
'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), | |||||
'Symfony\\Polyfill\\Iconv\\' => array($vendorDir . '/symfony/polyfill-iconv'), | |||||
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), | |||||
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'), | |||||
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), | |||||
'Symfony\\Contracts\\HttpClient\\' => array($vendorDir . '/symfony/http-client-contracts'), | |||||
'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'), | |||||
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), | |||||
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), | |||||
'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), | |||||
'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), | |||||
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), | |||||
'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'), | |||||
'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), | |||||
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), | |||||
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), | |||||
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), | |||||
'Symfony\\Component\\ErrorHandler\\' => array($vendorDir . '/symfony/error-handler'), | |||||
'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), | |||||
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), | |||||
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), | |||||
'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'), | |||||
'Psy\\' => array($vendorDir . '/psy/psysh/src'), | |||||
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), | |||||
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), | |||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), | |||||
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), | |||||
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), | |||||
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), | |||||
'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src/Prophecy'), | |||||
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), | |||||
'PhpOption\\' => array($vendorDir . '/phpoption/phpoption/src/PhpOption'), | |||||
'Opis\\Closure\\' => array($vendorDir . '/opis/closure/src'), | |||||
'NunoMaduro\\Collision\\' => array($vendorDir . '/nunomaduro/collision/src'), | |||||
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'), | |||||
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'), | |||||
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'), | |||||
'League\\CommonMark\\' => array($vendorDir . '/league/commonmark/src'), | |||||
'Laravel\\Tinker\\' => array($vendorDir . '/laravel/tinker/src'), | |||||
'Laravel\\Sanctum\\' => array($vendorDir . '/laravel/sanctum/src'), | |||||
'Laravel\\Sail\\' => array($vendorDir . '/laravel/sail/src'), | |||||
'Illuminate\\Support\\' => array($vendorDir . '/laravel/framework/src/Illuminate/Macroable', $vendorDir . '/laravel/framework/src/Illuminate/Collections'), | |||||
'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'), | |||||
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), | |||||
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), | |||||
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), | |||||
'GrahamCampbell\\ResultType\\' => array($vendorDir . '/graham-campbell/result-type/src'), | |||||
'Fruitcake\\Cors\\' => array($vendorDir . '/fruitcake/laravel-cors/src'), | |||||
'Fideloper\\Proxy\\' => array($vendorDir . '/fideloper/proxy/src'), | |||||
'Faker\\' => array($vendorDir . '/fakerphp/faker/src/Faker'), | |||||
'Facade\\Ignition\\' => array($vendorDir . '/facade/ignition/src'), | |||||
'Facade\\IgnitionContracts\\' => array($vendorDir . '/facade/ignition-contracts/src'), | |||||
'Facade\\FlareClient\\' => array($vendorDir . '/facade/flare-client-php/src'), | |||||
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'), | |||||
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'), | |||||
'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'), | |||||
'Doctrine\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib/Doctrine/Inflector'), | |||||
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'), | |||||
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'), | |||||
'Database\\Seeders\\' => array($baseDir . '/database/seeders'), | |||||
'Database\\Factories\\' => array($baseDir . '/database/factories'), | |||||
'Cron\\' => array($vendorDir . '/dragonmantank/cron-expression/src/Cron'), | |||||
'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'), | |||||
'Brick\\Math\\' => array($vendorDir . '/brick/math/src'), | |||||
'Asm89\\Stack\\' => array($vendorDir . '/asm89/stack-cors/src'), | |||||
'App\\' => array($baseDir . '/app'), | |||||
); |
@@ -0,0 +1,75 @@ | |||||
<?php | |||||
// autoload_real.php @generated by Composer | |||||
class ComposerAutoloaderInit8efbab2422760e769b32f8bfe0e3a518 | |||||
{ | |||||
private static $loader; | |||||
public static function loadClassLoader($class) | |||||
{ | |||||
if ('Composer\Autoload\ClassLoader' === $class) { | |||||
require __DIR__ . '/ClassLoader.php'; | |||||
} | |||||
} | |||||
/** | |||||
* @return \Composer\Autoload\ClassLoader | |||||
*/ | |||||
public static function getLoader() | |||||
{ | |||||
if (null !== self::$loader) { | |||||
return self::$loader; | |||||
} | |||||
require __DIR__ . '/platform_check.php'; | |||||
spl_autoload_register(array('ComposerAutoloaderInit8efbab2422760e769b32f8bfe0e3a518', 'loadClassLoader'), true, true); | |||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); | |||||
spl_autoload_unregister(array('ComposerAutoloaderInit8efbab2422760e769b32f8bfe0e3a518', 'loadClassLoader')); | |||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); | |||||
if ($useStaticLoader) { | |||||
require __DIR__ . '/autoload_static.php'; | |||||
call_user_func(\Composer\Autoload\ComposerStaticInit8efbab2422760e769b32f8bfe0e3a518::getInitializer($loader)); | |||||
} else { | |||||
$map = require __DIR__ . '/autoload_namespaces.php'; | |||||
foreach ($map as $namespace => $path) { | |||||
$loader->set($namespace, $path); | |||||
} | |||||
$map = require __DIR__ . '/autoload_psr4.php'; | |||||
foreach ($map as $namespace => $path) { | |||||
$loader->setPsr4($namespace, $path); | |||||
} | |||||
$classMap = require __DIR__ . '/autoload_classmap.php'; | |||||
if ($classMap) { | |||||
$loader->addClassMap($classMap); | |||||
} | |||||
} | |||||
$loader->register(true); | |||||
if ($useStaticLoader) { | |||||
$includeFiles = Composer\Autoload\ComposerStaticInit8efbab2422760e769b32f8bfe0e3a518::$files; | |||||
} else { | |||||
$includeFiles = require __DIR__ . '/autoload_files.php'; | |||||
} | |||||
foreach ($includeFiles as $fileIdentifier => $file) { | |||||
composerRequire8efbab2422760e769b32f8bfe0e3a518($fileIdentifier, $file); | |||||
} | |||||
return $loader; | |||||
} | |||||
} | |||||
function composerRequire8efbab2422760e769b32f8bfe0e3a518($fileIdentifier, $file) | |||||
{ | |||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { | |||||
require $file; | |||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; | |||||
} | |||||
} |
@@ -0,0 +1,26 @@ | |||||
<?php | |||||
// platform_check.php @generated by Composer | |||||
$issues = array(); | |||||
if (!(PHP_VERSION_ID >= 70300)) { | |||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.3.0". You are running ' . PHP_VERSION . '.'; | |||||
} | |||||
if ($issues) { | |||||
if (!headers_sent()) { | |||||
header('HTTP/1.1 500 Internal Server Error'); | |||||
} | |||||
if (!ini_get('display_errors')) { | |||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { | |||||
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); | |||||
} elseif (!headers_sent()) { | |||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; | |||||
} | |||||
} | |||||
trigger_error( | |||||
'Composer detected issues in your platform: ' . implode(' ', $issues), | |||||
E_USER_ERROR | |||||
); | |||||
} |
@@ -0,0 +1,19 @@ | |||||
Copyright (c) 2014 Daniel Nögel | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in | |||||
all copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
THE SOFTWARE. |
@@ -0,0 +1,41 @@ | |||||
# XDG Base Directory | |||||
[![Latest Stable Version](https://img.shields.io/packagist/v/dnoegel/php-xdg-base-dir.svg?style=flat-square)](https://packagist.org/packages/dnoegel/php-xdg-base-dir) | |||||
[![Total Downloads](https://img.shields.io/packagist/dt/dnoegel/php-xdg-base-dir.svg?style=flat-square)](https://packagist.org/packages/dnoegel/php-xdg-base-dir) | |||||
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) | |||||
[![Build Status](https://img.shields.io/travis/dnoegel/php-xdg-base-dir/master.svg?style=flat-square)](https://travis-ci.org/dnoegel/php-xdg-base-dir) | |||||
Implementation of XDG Base Directory specification for php | |||||
## Install | |||||
Via Composer | |||||
``` bash | |||||
$ composer require dnoegel/php-xdg-base-dir | |||||
``` | |||||
## Usage | |||||
``` php | |||||
$xdg = new \XdgBaseDir\Xdg(); | |||||
echo $xdg->getHomeDir(); | |||||
echo $xdg->getHomeConfigDir(); | |||||
echo $xdg->getHomeDataDir(); | |||||
echo $xdg->getHomeCacheDir(); | |||||
echo $xdg->getRuntimeDir(); | |||||
print_r($xdg->getDataDirs()); // returns array | |||||
print_r($xdg->getConfigDirs()); // returns array | |||||
``` | |||||
## Testing | |||||
``` bash | |||||
$ phpunit | |||||
``` | |||||
## License | |||||
The MIT License (MIT). Please see [License File](https://github.com/dnoegel/php-xdg-base-dir/blob/master/LICENSE) for more information. |
@@ -0,0 +1,17 @@ | |||||
{ | |||||
"name": "dnoegel/php-xdg-base-dir", | |||||
"description": "implementation of xdg base directory specification for php", | |||||
"type": "library", | |||||
"license": "MIT", | |||||
"require": { | |||||
"php": ">=5.3.2" | |||||
}, | |||||
"require-dev": { | |||||
"phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" | |||||
}, | |||||
"autoload": { | |||||
"psr-4": { | |||||
"XdgBaseDir\\": "src/" | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,132 @@ | |||||
<?php | |||||
namespace XdgBaseDir; | |||||
/** | |||||
* Simple implementation of the XDG standard http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html | |||||
* | |||||
* Based on the python implementation https://github.com/takluyver/pyxdg/blob/master/xdg/BaseDirectory.py | |||||
*/ | |||||
class Xdg | |||||
{ | |||||
const S_IFDIR = 040000; // directory | |||||
const S_IRWXO = 00007; // rwx other | |||||
const S_IRWXG = 00056; // rwx group | |||||
const RUNTIME_DIR_FALLBACK = 'php-xdg-runtime-dir-fallback-'; | |||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getHomeDir() | |||||
{ | |||||
return getenv('HOME') ?: (getenv('HOMEDRIVE') . DIRECTORY_SEPARATOR . getenv('HOMEPATH')); | |||||
} | |||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getHomeConfigDir() | |||||
{ | |||||
if ($path = getenv('XDG_CONFIG_HOME')) { | |||||
return $path; | |||||
} | |||||
$homeDir = $this->getHomeDir(); | |||||
$path = DIRECTORY_SEPARATOR === $homeDir ? $homeDir.'.config' : $homeDir . DIRECTORY_SEPARATOR . '.config'; | |||||
return $path; | |||||
} | |||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getHomeDataDir() | |||||
{ | |||||
$path = getenv('XDG_DATA_HOME') ?: $this->getHomeDir() . DIRECTORY_SEPARATOR . '.local' . DIRECTORY_SEPARATOR . 'share'; | |||||
return $path; | |||||
} | |||||
/** | |||||
* @return array | |||||
*/ | |||||
public function getConfigDirs() | |||||
{ | |||||
$configDirs = getenv('XDG_CONFIG_DIRS') ? explode(':', getenv('XDG_CONFIG_DIRS')) : array('/etc/xdg'); | |||||
$paths = array_merge(array($this->getHomeConfigDir()), $configDirs); | |||||
return $paths; | |||||
} | |||||
/** | |||||
* @return array | |||||
*/ | |||||
public function getDataDirs() | |||||
{ | |||||
$dataDirs = getenv('XDG_DATA_DIRS') ? explode(':', getenv('XDG_DATA_DIRS')) : array('/usr/local/share', '/usr/share'); | |||||
$paths = array_merge(array($this->getHomeDataDir()), $dataDirs); | |||||
return $paths; | |||||
} | |||||
/** | |||||
* @return string | |||||
*/ | |||||
public function getHomeCacheDir() | |||||
{ | |||||
$path = getenv('XDG_CACHE_HOME') ?: $this->getHomeDir() . DIRECTORY_SEPARATOR . '.cache'; | |||||
return $path; | |||||
} | |||||
public function getRuntimeDir($strict=true) | |||||
{ | |||||
if ($runtimeDir = getenv('XDG_RUNTIME_DIR')) { | |||||
return $runtimeDir; | |||||
} | |||||
if ($strict) { | |||||
throw new \RuntimeException('XDG_RUNTIME_DIR was not set'); | |||||
} | |||||
$fallback = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::RUNTIME_DIR_FALLBACK . getenv('USER'); | |||||
$create = false; | |||||
if (!is_dir($fallback)) { | |||||
mkdir($fallback, 0700, true); | |||||
} | |||||
$st = lstat($fallback); | |||||
# The fallback must be a directory | |||||
if (!$st['mode'] & self::S_IFDIR) { | |||||
rmdir($fallback); | |||||
$create = true; | |||||
} elseif ($st['uid'] != $this->getUid() || | |||||
$st['mode'] & (self::S_IRWXG | self::S_IRWXO) | |||||
) { | |||||
rmdir($fallback); | |||||
$create = true; | |||||
} | |||||
if ($create) { | |||||
mkdir($fallback, 0700, true); | |||||
} | |||||
return $fallback; | |||||
} | |||||
private function getUid() | |||||
{ | |||||
if (function_exists('posix_getuid')) { | |||||
return posix_getuid(); | |||||
} | |||||
return getmyuid(); | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
Copyright (c) 2006-2015 Doctrine Project | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy of | |||||
this software and associated documentation files (the "Software"), to deal in | |||||
the Software without restriction, including without limitation the rights to | |||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |||||
of the Software, and to permit persons to whom the Software is furnished to do | |||||
so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
SOFTWARE. |
@@ -0,0 +1,8 @@ | |||||
# Doctrine Inflector | |||||
Doctrine Inflector is a small library that can perform string manipulations | |||||
with regard to uppercase/lowercase and singular/plural forms of words. | |||||
[![Build Status](https://travis-ci.org/doctrine/inflector.svg)](https://travis-ci.org/doctrine/inflector) | |||||
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/doctrine/inflector/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/doctrine/inflector/?branch=master) | |||||
[![Code Coverage](https://scrutinizer-ci.com/g/doctrine/inflector/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/doctrine/inflector/?branch=master) |
@@ -0,0 +1,40 @@ | |||||
{ | |||||
"name": "doctrine/inflector", | |||||
"type": "library", | |||||
"description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", | |||||
"keywords": ["php", "strings", "words", "manipulation", "inflector", "inflection", "uppercase", "lowercase", "singular", "plural"], | |||||
"homepage": "https://www.doctrine-project.org/projects/inflector.html", | |||||
"license": "MIT", | |||||
"authors": [ | |||||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, | |||||
{"name": "Roman Borschel", "email": "roman@code-factory.org"}, | |||||
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, | |||||
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, | |||||
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} | |||||
], | |||||
"require": { | |||||
"php": "^7.2 || ^8.0" | |||||
}, | |||||
"require-dev": { | |||||
"doctrine/coding-standard": "^7.0", | |||||
"phpstan/phpstan": "^0.11", | |||||
"phpstan/phpstan-phpunit": "^0.11", | |||||
"phpstan/phpstan-strict-rules": "^0.11", | |||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" | |||||
}, | |||||
"autoload": { | |||||
"psr-4": { | |||||
"Doctrine\\Inflector\\": "lib/Doctrine/Inflector" | |||||
} | |||||
}, | |||||
"autoload-dev": { | |||||
"psr-4": { | |||||
"Doctrine\\Tests\\Inflector\\": "tests/Doctrine/Tests/Inflector" | |||||
} | |||||
}, | |||||
"extra": { | |||||
"branch-alias": { | |||||
"dev-master": "2.0.x-dev" | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,226 @@ | |||||
Introduction | |||||
============ | |||||
The Doctrine Inflector has methods for inflecting text. The features include pluralization, | |||||
singularization, converting between camelCase and under_score and capitalizing | |||||
words. | |||||
Installation | |||||
============ | |||||
You can install the Inflector with composer: | |||||
.. code-block:: console | |||||
$ composer require doctrine/inflector | |||||
Usage | |||||
===== | |||||
Using the inflector is easy, you can create a new ``Doctrine\Inflector\Inflector`` instance by using | |||||
the ``Doctrine\Inflector\InflectorFactory`` class: | |||||
.. code-block:: php | |||||
use Doctrine\Inflector\InflectorFactory; | |||||
$inflector = InflectorFactory::create()->build(); | |||||
By default it will create an English inflector. If you want to use another language, just pass the language | |||||
you want to create an inflector for to the ``createForLanguage()`` method: | |||||
.. code-block:: php | |||||
use Doctrine\Inflector\InflectorFactory; | |||||
use Doctrine\Inflector\Language; | |||||
$inflector = InflectorFactory::createForLanguage(Language::SPANISH)->build(); | |||||
The supported languages are as follows: | |||||
- ``Language::ENGLISH`` | |||||
- ``Language::FRENCH`` | |||||
- ``Language::NORWEGIAN_BOKMAL`` | |||||
- ``Language::PORTUGUESE`` | |||||
- ``Language::SPANISH`` | |||||
- ``Language::TURKISH`` | |||||
If you want to manually construct the inflector instead of using a factory, you can do so like this: | |||||
.. code-block:: php | |||||
use Doctrine\Inflector\CachedWordInflector; | |||||
use Doctrine\Inflector\RulesetInflector; | |||||
use Doctrine\Inflector\Rules\English; | |||||
$inflector = new Inflector( | |||||
new CachedWordInflector(new RulesetInflector( | |||||
English\Rules::getSingularRuleset() | |||||
)), | |||||
new CachedWordInflector(new RulesetInflector( | |||||
English\Rules::getPluralRuleset() | |||||
)) | |||||
); | |||||
Adding Languages | |||||
---------------- | |||||
If you are interested in adding support for your language, take a look at the other languages defined in the | |||||
``Doctrine\Inflector\Rules`` namespace and the tests located in ``Doctrine\Tests\Inflector\Rules``. You can copy | |||||
one of the languages and update the rules for your language. | |||||
Once you have done this, send a pull request to the ``doctrine/inflector`` repository with the additions. | |||||
Custom Setup | |||||
============ | |||||
If you want to setup custom singular and plural rules, you can configure these in the factory: | |||||
.. code-block:: php | |||||
use Doctrine\Inflector\InflectorFactory; | |||||
use Doctrine\Inflector\Rules\Pattern; | |||||
use Doctrine\Inflector\Rules\Patterns; | |||||
use Doctrine\Inflector\Rules\Ruleset; | |||||
use Doctrine\Inflector\Rules\Substitution; | |||||
use Doctrine\Inflector\Rules\Substitutions; | |||||
use Doctrine\Inflector\Rules\Transformation; | |||||
use Doctrine\Inflector\Rules\Transformations; | |||||
use Doctrine\Inflector\Rules\Word; | |||||
$inflector = InflectorFactory::create() | |||||
->withSingularRules( | |||||
new Ruleset( | |||||
new Transformations( | |||||
new Transformation(new Pattern('/^(bil)er$/i'), '\1'), | |||||
new Transformation(new Pattern('/^(inflec|contribu)tors$/i'), '\1ta') | |||||
), | |||||
new Patterns(new Pattern('singulars')), | |||||
new Substitutions(new Substitution(new Word('spins'), new Word('spinor'))) | |||||
) | |||||
) | |||||
->withPluralRules( | |||||
new Ruleset( | |||||
new Transformations( | |||||
new Transformation(new Pattern('^(bil)er$'), '\1'), | |||||
new Transformation(new Pattern('^(inflec|contribu)tors$'), '\1ta') | |||||
), | |||||
new Patterns(new Pattern('noflect'), new Pattern('abtuse')), | |||||
new Substitutions( | |||||
new Substitution(new Word('amaze'), new Word('amazable')), | |||||
new Substitution(new Word('phone'), new Word('phonezes')) | |||||
) | |||||
) | |||||
) | |||||
->build(); | |||||
No operation inflector | |||||
---------------------- | |||||
The ``Doctrine\Inflector\NoopWordInflector`` may be used to configure an inflector that doesn't perform any operation for | |||||
pluralization and/or singularization. If will simply return the input as output. | |||||
This is an implementation of the `Null Object design pattern <https://sourcemaking.com/design_patterns/null_object>`_. | |||||
.. code-block:: php | |||||
use Doctrine\Inflector\Inflector; | |||||
use Doctrine\Inflector\NoopWordInflector; | |||||
$inflector = new Inflector(new NoopWordInflector(), new NoopWordInflector()); | |||||
Tableize | |||||
======== | |||||
Converts ``ModelName`` to ``model_name``: | |||||
.. code-block:: php | |||||
echo $inflector->tableize('ModelName'); // model_name | |||||
Classify | |||||
======== | |||||
Converts ``model_name`` to ``ModelName``: | |||||
.. code-block:: php | |||||
echo $inflector->classify('model_name'); // ModelName | |||||
Camelize | |||||
======== | |||||
This method uses `Classify`_ and then converts the first character to lowercase: | |||||
.. code-block:: php | |||||
echo $inflector->camelize('model_name'); // modelName | |||||
Capitalize | |||||
========== | |||||
Takes a string and capitalizes all of the words, like PHP's built-in | |||||
``ucwords`` function. This extends that behavior, however, by allowing the | |||||
word delimiters to be configured, rather than only separating on | |||||
whitespace. | |||||
Here is an example: | |||||
.. code-block:: php | |||||
$string = 'top-o-the-morning to all_of_you!'; | |||||
echo $inflector->capitalize($string); // Top-O-The-Morning To All_of_you! | |||||
echo $inflector->capitalize($string, '-_ '); // Top-O-The-Morning To All_Of_You! | |||||
Pluralize | |||||
========= | |||||
Returns a word in plural form. | |||||
.. code-block:: php | |||||
echo $inflector->pluralize('browser'); // browsers | |||||
Singularize | |||||
=========== | |||||
Returns a word in singular form. | |||||
.. code-block:: php | |||||
echo $inflector->singularize('browsers'); // browser | |||||
Urlize | |||||
====== | |||||
Generate a URL friendly string from a string of text: | |||||
.. code-block:: php | |||||
echo $inflector->urlize('My first blog post'); // my-first-blog-post | |||||
Unaccent | |||||
======== | |||||
You can unaccent a string of text using the ``unaccent()`` method: | |||||
.. code-block:: php | |||||
echo $inflector->unaccent('año'); // ano | |||||
Legacy API | |||||
========== | |||||
The API present in Inflector 1.x is still available, but will be deprecated in a future release and dropped for 3.0. | |||||
Support for languages other than English is available in the 2.0 API only. | |||||
Acknowledgements | |||||
================ | |||||
The language rules in this library have been adapted from several different sources, including but not limited to: | |||||
- `Ruby On Rails Inflector <http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html>`_ | |||||
- `ICanBoogie Inflector <https://github.com/ICanBoogie/Inflector>`_ | |||||
- `CakePHP Inflector <https://book.cakephp.org/3.0/en/core-libraries/inflector.html>`_ |
@@ -0,0 +1,24 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector; | |||||
class CachedWordInflector implements WordInflector | |||||
{ | |||||
/** @var WordInflector */ | |||||
private $wordInflector; | |||||
/** @var string[] */ | |||||
private $cache = []; | |||||
public function __construct(WordInflector $wordInflector) | |||||
{ | |||||
$this->wordInflector = $wordInflector; | |||||
} | |||||
public function inflect(string $word) : string | |||||
{ | |||||
return $this->cache[$word] ?? $this->cache[$word] = $this->wordInflector->inflect($word); | |||||
} | |||||
} |
@@ -0,0 +1,65 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector; | |||||
use Doctrine\Inflector\Rules\Ruleset; | |||||
use function array_unshift; | |||||
abstract class GenericLanguageInflectorFactory implements LanguageInflectorFactory | |||||
{ | |||||
/** @var Ruleset[] */ | |||||
private $singularRulesets = []; | |||||
/** @var Ruleset[] */ | |||||
private $pluralRulesets = []; | |||||
final public function __construct() | |||||
{ | |||||
$this->singularRulesets[] = $this->getSingularRuleset(); | |||||
$this->pluralRulesets[] = $this->getPluralRuleset(); | |||||
} | |||||
final public function build() : Inflector | |||||
{ | |||||
return new Inflector( | |||||
new CachedWordInflector(new RulesetInflector( | |||||
...$this->singularRulesets | |||||
)), | |||||
new CachedWordInflector(new RulesetInflector( | |||||
...$this->pluralRulesets | |||||
)) | |||||
); | |||||
} | |||||
final public function withSingularRules(?Ruleset $singularRules, bool $reset = false) : LanguageInflectorFactory | |||||
{ | |||||
if ($reset) { | |||||
$this->singularRulesets = []; | |||||
} | |||||
if ($singularRules instanceof Ruleset) { | |||||
array_unshift($this->singularRulesets, $singularRules); | |||||
} | |||||
return $this; | |||||
} | |||||
final public function withPluralRules(?Ruleset $pluralRules, bool $reset = false) : LanguageInflectorFactory | |||||
{ | |||||
if ($reset) { | |||||
$this->pluralRulesets = []; | |||||
} | |||||
if ($pluralRules instanceof Ruleset) { | |||||
array_unshift($this->pluralRulesets, $pluralRules); | |||||
} | |||||
return $this; | |||||
} | |||||
abstract protected function getSingularRuleset() : Ruleset; | |||||
abstract protected function getPluralRuleset() : Ruleset; | |||||
} |
@@ -0,0 +1,506 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector; | |||||
use RuntimeException; | |||||
use function chr; | |||||
use function function_exists; | |||||
use function lcfirst; | |||||
use function mb_strtolower; | |||||
use function ord; | |||||
use function preg_match; | |||||
use function preg_replace; | |||||
use function sprintf; | |||||
use function str_replace; | |||||
use function strlen; | |||||
use function strtolower; | |||||
use function strtr; | |||||
use function trim; | |||||
use function ucwords; | |||||
class Inflector | |||||
{ | |||||
private const ACCENTED_CHARACTERS = [ | |||||
'À' => 'A', | |||||
'Á' => 'A', | |||||
'Â' => 'A', | |||||
'Ã' => 'A', | |||||
'Ä' => 'Ae', | |||||
'Æ' => 'Ae', | |||||
'Å' => 'Aa', | |||||
'æ' => 'a', | |||||
'Ç' => 'C', | |||||
'È' => 'E', | |||||
'É' => 'E', | |||||
'Ê' => 'E', | |||||
'Ë' => 'E', | |||||
'Ì' => 'I', | |||||
'Í' => 'I', | |||||
'Î' => 'I', | |||||
'Ï' => 'I', | |||||
'Ñ' => 'N', | |||||
'Ò' => 'O', | |||||
'Ó' => 'O', | |||||
'Ô' => 'O', | |||||
'Õ' => 'O', | |||||
'Ö' => 'Oe', | |||||
'Ù' => 'U', | |||||
'Ú' => 'U', | |||||
'Û' => 'U', | |||||
'Ü' => 'Ue', | |||||
'Ý' => 'Y', | |||||
'ß' => 'ss', | |||||
'à' => 'a', | |||||
'á' => 'a', | |||||
'â' => 'a', | |||||
'ã' => 'a', | |||||
'ä' => 'ae', | |||||
'å' => 'aa', | |||||
'ç' => 'c', | |||||
'è' => 'e', | |||||
'é' => 'e', | |||||
'ê' => 'e', | |||||
'ë' => 'e', | |||||
'ì' => 'i', | |||||
'í' => 'i', | |||||
'î' => 'i', | |||||
'ï' => 'i', | |||||
'ñ' => 'n', | |||||
'ò' => 'o', | |||||
'ó' => 'o', | |||||
'ô' => 'o', | |||||
'õ' => 'o', | |||||
'ö' => 'oe', | |||||
'ù' => 'u', | |||||
'ú' => 'u', | |||||
'û' => 'u', | |||||
'ü' => 'ue', | |||||
'ý' => 'y', | |||||
'ÿ' => 'y', | |||||
'Ā' => 'A', | |||||
'ā' => 'a', | |||||
'Ă' => 'A', | |||||
'ă' => 'a', | |||||
'Ą' => 'A', | |||||
'ą' => 'a', | |||||
'Ć' => 'C', | |||||
'ć' => 'c', | |||||
'Ĉ' => 'C', | |||||
'ĉ' => 'c', | |||||
'Ċ' => 'C', | |||||
'ċ' => 'c', | |||||
'Č' => 'C', | |||||
'č' => 'c', | |||||
'Ď' => 'D', | |||||
'ď' => 'd', | |||||
'Đ' => 'D', | |||||
'đ' => 'd', | |||||
'Ē' => 'E', | |||||
'ē' => 'e', | |||||
'Ĕ' => 'E', | |||||
'ĕ' => 'e', | |||||
'Ė' => 'E', | |||||
'ė' => 'e', | |||||
'Ę' => 'E', | |||||
'ę' => 'e', | |||||
'Ě' => 'E', | |||||
'ě' => 'e', | |||||
'Ĝ' => 'G', | |||||
'ĝ' => 'g', | |||||
'Ğ' => 'G', | |||||
'ğ' => 'g', | |||||
'Ġ' => 'G', | |||||
'ġ' => 'g', | |||||
'Ģ' => 'G', | |||||
'ģ' => 'g', | |||||
'Ĥ' => 'H', | |||||
'ĥ' => 'h', | |||||
'Ħ' => 'H', | |||||
'ħ' => 'h', | |||||
'Ĩ' => 'I', | |||||
'ĩ' => 'i', | |||||
'Ī' => 'I', | |||||
'ī' => 'i', | |||||
'Ĭ' => 'I', | |||||
'ĭ' => 'i', | |||||
'Į' => 'I', | |||||
'į' => 'i', | |||||
'İ' => 'I', | |||||
'ı' => 'i', | |||||
'IJ' => 'IJ', | |||||
'ij' => 'ij', | |||||
'Ĵ' => 'J', | |||||
'ĵ' => 'j', | |||||
'Ķ' => 'K', | |||||
'ķ' => 'k', | |||||
'ĸ' => 'k', | |||||
'Ĺ' => 'L', | |||||
'ĺ' => 'l', | |||||
'Ļ' => 'L', | |||||
'ļ' => 'l', | |||||
'Ľ' => 'L', | |||||
'ľ' => 'l', | |||||
'Ŀ' => 'L', | |||||
'ŀ' => 'l', | |||||
'Ł' => 'L', | |||||
'ł' => 'l', | |||||
'Ń' => 'N', | |||||
'ń' => 'n', | |||||
'Ņ' => 'N', | |||||
'ņ' => 'n', | |||||
'Ň' => 'N', | |||||
'ň' => 'n', | |||||
'ʼn' => 'N', | |||||
'Ŋ' => 'n', | |||||
'ŋ' => 'N', | |||||
'Ō' => 'O', | |||||
'ō' => 'o', | |||||
'Ŏ' => 'O', | |||||
'ŏ' => 'o', | |||||
'Ő' => 'O', | |||||
'ő' => 'o', | |||||
'Œ' => 'OE', | |||||
'œ' => 'oe', | |||||
'Ø' => 'O', | |||||
'ø' => 'o', | |||||
'Ŕ' => 'R', | |||||
'ŕ' => 'r', | |||||
'Ŗ' => 'R', | |||||
'ŗ' => 'r', | |||||
'Ř' => 'R', | |||||
'ř' => 'r', | |||||
'Ś' => 'S', | |||||
'ś' => 's', | |||||
'Ŝ' => 'S', | |||||
'ŝ' => 's', | |||||
'Ş' => 'S', | |||||
'ş' => 's', | |||||
'Š' => 'S', | |||||
'š' => 's', | |||||
'Ţ' => 'T', | |||||
'ţ' => 't', | |||||
'Ť' => 'T', | |||||
'ť' => 't', | |||||
'Ŧ' => 'T', | |||||
'ŧ' => 't', | |||||
'Ũ' => 'U', | |||||
'ũ' => 'u', | |||||
'Ū' => 'U', | |||||
'ū' => 'u', | |||||
'Ŭ' => 'U', | |||||
'ŭ' => 'u', | |||||
'Ů' => 'U', | |||||
'ů' => 'u', | |||||
'Ű' => 'U', | |||||
'ű' => 'u', | |||||
'Ų' => 'U', | |||||
'ų' => 'u', | |||||
'Ŵ' => 'W', | |||||
'ŵ' => 'w', | |||||
'Ŷ' => 'Y', | |||||
'ŷ' => 'y', | |||||
'Ÿ' => 'Y', | |||||
'Ź' => 'Z', | |||||
'ź' => 'z', | |||||
'Ż' => 'Z', | |||||
'ż' => 'z', | |||||
'Ž' => 'Z', | |||||
'ž' => 'z', | |||||
'ſ' => 's', | |||||
'€' => 'E', | |||||
'£' => '', | |||||
]; | |||||
/** @var WordInflector */ | |||||
private $singularizer; | |||||
/** @var WordInflector */ | |||||
private $pluralizer; | |||||
public function __construct(WordInflector $singularizer, WordInflector $pluralizer) | |||||
{ | |||||
$this->singularizer = $singularizer; | |||||
$this->pluralizer = $pluralizer; | |||||
} | |||||
/** | |||||
* Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. | |||||
*/ | |||||
public function tableize(string $word) : string | |||||
{ | |||||
$tableized = preg_replace('~(?<=\\w)([A-Z])~u', '_$1', $word); | |||||
if ($tableized === null) { | |||||
throw new RuntimeException(sprintf( | |||||
'preg_replace returned null for value "%s"', | |||||
$word | |||||
)); | |||||
} | |||||
return mb_strtolower($tableized); | |||||
} | |||||
/** | |||||
* Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. | |||||
*/ | |||||
public function classify(string $word) : string | |||||
{ | |||||
return str_replace([' ', '_', '-'], '', ucwords($word, ' _-')); | |||||
} | |||||
/** | |||||
* Camelizes a word. This uses the classify() method and turns the first character to lowercase. | |||||
*/ | |||||
public function camelize(string $word) : string | |||||
{ | |||||
return lcfirst($this->classify($word)); | |||||
} | |||||
/** | |||||
* Uppercases words with configurable delimiters between words. | |||||
* | |||||
* Takes a string and capitalizes all of the words, like PHP's built-in | |||||
* ucwords function. This extends that behavior, however, by allowing the | |||||
* word delimiters to be configured, rather than only separating on | |||||
* whitespace. | |||||
* | |||||
* Here is an example: | |||||
* <code> | |||||
* <?php | |||||
* $string = 'top-o-the-morning to all_of_you!'; | |||||
* echo $inflector->capitalize($string); | |||||
* // Top-O-The-Morning To All_of_you! | |||||
* | |||||
* echo $inflector->capitalize($string, '-_ '); | |||||
* // Top-O-The-Morning To All_Of_You! | |||||
* ?> | |||||
* </code> | |||||
* | |||||
* @param string $string The string to operate on. | |||||
* @param string $delimiters A list of word separators. | |||||
* | |||||
* @return string The string with all delimiter-separated words capitalized. | |||||
*/ | |||||
public function capitalize(string $string, string $delimiters = " \n\t\r\0\x0B-") : string | |||||
{ | |||||
return ucwords($string, $delimiters); | |||||
} | |||||
/** | |||||
* Checks if the given string seems like it has utf8 characters in it. | |||||
* | |||||
* @param string $string The string to check for utf8 characters in. | |||||
*/ | |||||
public function seemsUtf8(string $string) : bool | |||||
{ | |||||
for ($i = 0; $i < strlen($string); $i++) { | |||||
if (ord($string[$i]) < 0x80) { | |||||
continue; // 0bbbbbbb | |||||
} | |||||
if ((ord($string[$i]) & 0xE0) === 0xC0) { | |||||
$n = 1; // 110bbbbb | |||||
} elseif ((ord($string[$i]) & 0xF0) === 0xE0) { | |||||
$n = 2; // 1110bbbb | |||||
} elseif ((ord($string[$i]) & 0xF8) === 0xF0) { | |||||
$n = 3; // 11110bbb | |||||
} elseif ((ord($string[$i]) & 0xFC) === 0xF8) { | |||||
$n = 4; // 111110bb | |||||
} elseif ((ord($string[$i]) & 0xFE) === 0xFC) { | |||||
$n = 5; // 1111110b | |||||
} else { | |||||
return false; // Does not match any model | |||||
} | |||||
for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ? | |||||
if (++$i === strlen($string) || ((ord($string[$i]) & 0xC0) !== 0x80)) { | |||||
return false; | |||||
} | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
/** | |||||
* Remove any illegal characters, accents, etc. | |||||
* | |||||
* @param string $string String to unaccent | |||||
* | |||||
* @return string Unaccented string | |||||
*/ | |||||
public function unaccent(string $string) : string | |||||
{ | |||||
if (preg_match('/[\x80-\xff]/', $string) === false) { | |||||
return $string; | |||||
} | |||||
if ($this->seemsUtf8($string)) { | |||||
$string = strtr($string, self::ACCENTED_CHARACTERS); | |||||
} else { | |||||
$characters = []; | |||||
// Assume ISO-8859-1 if not UTF-8 | |||||
$characters['in'] = | |||||
chr(128) | |||||
. chr(131) | |||||
. chr(138) | |||||
. chr(142) | |||||
. chr(154) | |||||
. chr(158) | |||||
. chr(159) | |||||
. chr(162) | |||||
. chr(165) | |||||
. chr(181) | |||||
. chr(192) | |||||
. chr(193) | |||||
. chr(194) | |||||
. chr(195) | |||||
. chr(196) | |||||
. chr(197) | |||||
. chr(199) | |||||
. chr(200) | |||||
. chr(201) | |||||
. chr(202) | |||||
. chr(203) | |||||
. chr(204) | |||||
. chr(205) | |||||
. chr(206) | |||||
. chr(207) | |||||
. chr(209) | |||||
. chr(210) | |||||
. chr(211) | |||||
. chr(212) | |||||
. chr(213) | |||||
. chr(214) | |||||
. chr(216) | |||||
. chr(217) | |||||
. chr(218) | |||||
. chr(219) | |||||
. chr(220) | |||||
. chr(221) | |||||
. chr(224) | |||||
. chr(225) | |||||
. chr(226) | |||||
. chr(227) | |||||
. chr(228) | |||||
. chr(229) | |||||
. chr(231) | |||||
. chr(232) | |||||
. chr(233) | |||||
. chr(234) | |||||
. chr(235) | |||||
. chr(236) | |||||
. chr(237) | |||||
. chr(238) | |||||
. chr(239) | |||||
. chr(241) | |||||
. chr(242) | |||||
. chr(243) | |||||
. chr(244) | |||||
. chr(245) | |||||
. chr(246) | |||||
. chr(248) | |||||
. chr(249) | |||||
. chr(250) | |||||
. chr(251) | |||||
. chr(252) | |||||
. chr(253) | |||||
. chr(255); | |||||
$characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'; | |||||
$string = strtr($string, $characters['in'], $characters['out']); | |||||
$doubleChars = []; | |||||
$doubleChars['in'] = [ | |||||
chr(140), | |||||
chr(156), | |||||
chr(198), | |||||
chr(208), | |||||
chr(222), | |||||
chr(223), | |||||
chr(230), | |||||
chr(240), | |||||
chr(254), | |||||
]; | |||||
$doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th']; | |||||
$string = str_replace($doubleChars['in'], $doubleChars['out'], $string); | |||||
} | |||||
return $string; | |||||
} | |||||
/** | |||||
* Convert any passed string to a url friendly string. | |||||
* Converts 'My first blog post' to 'my-first-blog-post' | |||||
* | |||||
* @param string $string String to urlize. | |||||
* | |||||
* @return string Urlized string. | |||||
*/ | |||||
public function urlize(string $string) : string | |||||
{ | |||||
// Remove all non url friendly characters with the unaccent function | |||||
$unaccented = $this->unaccent($string); | |||||
if (function_exists('mb_strtolower')) { | |||||
$lowered = mb_strtolower($unaccented); | |||||
} else { | |||||
$lowered = strtolower($unaccented); | |||||
} | |||||
$replacements = [ | |||||
'/\W/' => ' ', | |||||
'/([A-Z]+)([A-Z][a-z])/' => '\1_\2', | |||||
'/([a-z\d])([A-Z])/' => '\1_\2', | |||||
'/[^A-Z^a-z^0-9^\/]+/' => '-', | |||||
]; | |||||
$urlized = $lowered; | |||||
foreach ($replacements as $pattern => $replacement) { | |||||
$replaced = preg_replace($pattern, $replacement, $urlized); | |||||
if ($replaced === null) { | |||||
throw new RuntimeException(sprintf( | |||||
'preg_replace returned null for value "%s"', | |||||
$urlized | |||||
)); | |||||
} | |||||
$urlized = $replaced; | |||||
} | |||||
return trim($urlized, '-'); | |||||
} | |||||
/** | |||||
* Returns a word in singular form. | |||||
* | |||||
* @param string $word The word in plural form. | |||||
* | |||||
* @return string The word in singular form. | |||||
*/ | |||||
public function singularize(string $word) : string | |||||
{ | |||||
return $this->singularizer->inflect($word); | |||||
} | |||||
/** | |||||
* Returns a word in plural form. | |||||
* | |||||
* @param string $word The word in singular form. | |||||
* | |||||
* @return string The word in plural form. | |||||
*/ | |||||
public function pluralize(string $word) : string | |||||
{ | |||||
return $this->pluralizer->inflect($word); | |||||
} | |||||
} |
@@ -0,0 +1,45 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector; | |||||
use Doctrine\Inflector\Rules\English; | |||||
use Doctrine\Inflector\Rules\French; | |||||
use Doctrine\Inflector\Rules\NorwegianBokmal; | |||||
use Doctrine\Inflector\Rules\Portuguese; | |||||
use Doctrine\Inflector\Rules\Spanish; | |||||
use Doctrine\Inflector\Rules\Turkish; | |||||
use InvalidArgumentException; | |||||
use function sprintf; | |||||
final class InflectorFactory | |||||
{ | |||||
public static function create() : LanguageInflectorFactory | |||||
{ | |||||
return self::createForLanguage(Language::ENGLISH); | |||||
} | |||||
public static function createForLanguage(string $language) : LanguageInflectorFactory | |||||
{ | |||||
switch ($language) { | |||||
case Language::ENGLISH: | |||||
return new English\InflectorFactory(); | |||||
case Language::FRENCH: | |||||
return new French\InflectorFactory(); | |||||
case Language::NORWEGIAN_BOKMAL: | |||||
return new NorwegianBokmal\InflectorFactory(); | |||||
case Language::PORTUGUESE: | |||||
return new Portuguese\InflectorFactory(); | |||||
case Language::SPANISH: | |||||
return new Spanish\InflectorFactory(); | |||||
case Language::TURKISH: | |||||
return new Turkish\InflectorFactory(); | |||||
default: | |||||
throw new InvalidArgumentException(sprintf( | |||||
'Language "%s" is not supported.', | |||||
$language | |||||
)); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector; | |||||
final class Language | |||||
{ | |||||
public const ENGLISH = 'english'; | |||||
public const FRENCH = 'french'; | |||||
public const NORWEGIAN_BOKMAL = 'norwegian-bokmal'; | |||||
public const PORTUGUESE = 'portuguese'; | |||||
public const SPANISH = 'spanish'; | |||||
public const TURKISH = 'turkish'; | |||||
private function __construct() | |||||
{ | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector; | |||||
use Doctrine\Inflector\Rules\Ruleset; | |||||
interface LanguageInflectorFactory | |||||
{ | |||||
/** | |||||
* Applies custom rules for singularisation | |||||
* | |||||
* @param bool $reset If true, will unset default inflections for all new rules | |||||
* | |||||
* @return $this | |||||
*/ | |||||
public function withSingularRules(?Ruleset $singularRules, bool $reset = false) : self; | |||||
/** | |||||
* Applies custom rules for pluralisation | |||||
* | |||||
* @param bool $reset If true, will unset default inflections for all new rules | |||||
* | |||||
* @return $this | |||||
*/ | |||||
public function withPluralRules(?Ruleset $pluralRules, bool $reset = false) : self; | |||||
/** | |||||
* Builds the inflector instance with all applicable rules | |||||
*/ | |||||
public function build() : Inflector; | |||||
} |
@@ -0,0 +1,13 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector; | |||||
class NoopWordInflector implements WordInflector | |||||
{ | |||||
public function inflect(string $word) : string | |||||
{ | |||||
return $word; | |||||
} | |||||
} |
@@ -0,0 +1,182 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\English; | |||||
use Doctrine\Inflector\Rules\Pattern; | |||||
use Doctrine\Inflector\Rules\Substitution; | |||||
use Doctrine\Inflector\Rules\Transformation; | |||||
use Doctrine\Inflector\Rules\Word; | |||||
class Inflectible | |||||
{ | |||||
/** | |||||
* @return Transformation[] | |||||
*/ | |||||
public static function getSingular() : iterable | |||||
{ | |||||
yield new Transformation(new Pattern('(s)tatuses$'), '\1\2tatus'); | |||||
yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatus'); | |||||
yield new Transformation(new Pattern('(c)ampus$'), '\1\2ampus'); | |||||
yield new Transformation(new Pattern('^(.*)(menu)s$'), '\1\2'); | |||||
yield new Transformation(new Pattern('(quiz)zes$'), '\\1'); | |||||
yield new Transformation(new Pattern('(matr)ices$'), '\1ix'); | |||||
yield new Transformation(new Pattern('(vert|ind)ices$'), '\1ex'); | |||||
yield new Transformation(new Pattern('^(ox)en'), '\1'); | |||||
yield new Transformation(new Pattern('(alias)(es)*$'), '\1'); | |||||
yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)oes$'), '\1o'); | |||||
yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$'), '\1us'); | |||||
yield new Transformation(new Pattern('([ftw]ax)es'), '\1'); | |||||
yield new Transformation(new Pattern('(analys|ax|cris|test|thes)es$'), '\1is'); | |||||
yield new Transformation(new Pattern('(shoe|slave)s$'), '\1'); | |||||
yield new Transformation(new Pattern('(o)es$'), '\1'); | |||||
yield new Transformation(new Pattern('ouses$'), 'ouse'); | |||||
yield new Transformation(new Pattern('([^a])uses$'), '\1us'); | |||||
yield new Transformation(new Pattern('([m|l])ice$'), '\1ouse'); | |||||
yield new Transformation(new Pattern('(x|ch|ss|sh)es$'), '\1'); | |||||
yield new Transformation(new Pattern('(m)ovies$'), '\1\2ovie'); | |||||
yield new Transformation(new Pattern('(s)eries$'), '\1\2eries'); | |||||
yield new Transformation(new Pattern('([^aeiouy]|qu)ies$'), '\1y'); | |||||
yield new Transformation(new Pattern('([lr])ves$'), '\1f'); | |||||
yield new Transformation(new Pattern('(tive)s$'), '\1'); | |||||
yield new Transformation(new Pattern('(hive)s$'), '\1'); | |||||
yield new Transformation(new Pattern('(drive)s$'), '\1'); | |||||
yield new Transformation(new Pattern('(dive)s$'), '\1'); | |||||
yield new Transformation(new Pattern('(olive)s$'), '\1'); | |||||
yield new Transformation(new Pattern('([^fo])ves$'), '\1fe'); | |||||
yield new Transformation(new Pattern('(^analy)ses$'), '\1sis'); | |||||
yield new Transformation(new Pattern('(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$'), '\1\2sis'); | |||||
yield new Transformation(new Pattern('(tax)a$'), '\1on'); | |||||
yield new Transformation(new Pattern('(c)riteria$'), '\1riterion'); | |||||
yield new Transformation(new Pattern('([ti])a$'), '\1um'); | |||||
yield new Transformation(new Pattern('(p)eople$'), '\1\2erson'); | |||||
yield new Transformation(new Pattern('(m)en$'), '\1an'); | |||||
yield new Transformation(new Pattern('(c)hildren$'), '\1\2hild'); | |||||
yield new Transformation(new Pattern('(f)eet$'), '\1oot'); | |||||
yield new Transformation(new Pattern('(n)ews$'), '\1\2ews'); | |||||
yield new Transformation(new Pattern('eaus$'), 'eau'); | |||||
yield new Transformation(new Pattern('s$'), ''); | |||||
} | |||||
/** | |||||
* @return Transformation[] | |||||
*/ | |||||
public static function getPlural() : iterable | |||||
{ | |||||
yield new Transformation(new Pattern('(s)tatus$'), '\1\2tatuses'); | |||||
yield new Transformation(new Pattern('(quiz)$'), '\1zes'); | |||||
yield new Transformation(new Pattern('^(ox)$'), '\1\2en'); | |||||
yield new Transformation(new Pattern('([m|l])ouse$'), '\1ice'); | |||||
yield new Transformation(new Pattern('(matr|vert|ind)(ix|ex)$'), '\1ices'); | |||||
yield new Transformation(new Pattern('(x|ch|ss|sh)$'), '\1es'); | |||||
yield new Transformation(new Pattern('([^aeiouy]|qu)y$'), '\1ies'); | |||||
yield new Transformation(new Pattern('(hive|gulf)$'), '\1s'); | |||||
yield new Transformation(new Pattern('(?:([^f])fe|([lr])f)$'), '\1\2ves'); | |||||
yield new Transformation(new Pattern('sis$'), 'ses'); | |||||
yield new Transformation(new Pattern('([ti])um$'), '\1a'); | |||||
yield new Transformation(new Pattern('(tax)on$'), '\1a'); | |||||
yield new Transformation(new Pattern('(c)riterion$'), '\1riteria'); | |||||
yield new Transformation(new Pattern('(p)erson$'), '\1eople'); | |||||
yield new Transformation(new Pattern('(m)an$'), '\1en'); | |||||
yield new Transformation(new Pattern('(c)hild$'), '\1hildren'); | |||||
yield new Transformation(new Pattern('(f)oot$'), '\1eet'); | |||||
yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)o$'), '\1\2oes'); | |||||
yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$'), '\1i'); | |||||
yield new Transformation(new Pattern('us$'), 'uses'); | |||||
yield new Transformation(new Pattern('(alias)$'), '\1es'); | |||||
yield new Transformation(new Pattern('(analys|ax|cris|test|thes)is$'), '\1es'); | |||||
yield new Transformation(new Pattern('s$'), 's'); | |||||
yield new Transformation(new Pattern('^$'), ''); | |||||
yield new Transformation(new Pattern('$'), 's'); | |||||
} | |||||
/** | |||||
* @return Substitution[] | |||||
*/ | |||||
public static function getIrregular() : iterable | |||||
{ | |||||
yield new Substitution(new Word('atlas'), new Word('atlases')); | |||||
yield new Substitution(new Word('axe'), new Word('axes')); | |||||
yield new Substitution(new Word('beef'), new Word('beefs')); | |||||
yield new Substitution(new Word('brother'), new Word('brothers')); | |||||
yield new Substitution(new Word('cafe'), new Word('cafes')); | |||||
yield new Substitution(new Word('chateau'), new Word('chateaux')); | |||||
yield new Substitution(new Word('niveau'), new Word('niveaux')); | |||||
yield new Substitution(new Word('child'), new Word('children')); | |||||
yield new Substitution(new Word('canvas'), new Word('canvases')); | |||||
yield new Substitution(new Word('cookie'), new Word('cookies')); | |||||
yield new Substitution(new Word('corpus'), new Word('corpuses')); | |||||
yield new Substitution(new Word('cow'), new Word('cows')); | |||||
yield new Substitution(new Word('criterion'), new Word('criteria')); | |||||
yield new Substitution(new Word('curriculum'), new Word('curricula')); | |||||
yield new Substitution(new Word('demo'), new Word('demos')); | |||||
yield new Substitution(new Word('domino'), new Word('dominoes')); | |||||
yield new Substitution(new Word('echo'), new Word('echoes')); | |||||
yield new Substitution(new Word('foot'), new Word('feet')); | |||||
yield new Substitution(new Word('fungus'), new Word('fungi')); | |||||
yield new Substitution(new Word('ganglion'), new Word('ganglions')); | |||||
yield new Substitution(new Word('gas'), new Word('gases')); | |||||
yield new Substitution(new Word('genie'), new Word('genies')); | |||||
yield new Substitution(new Word('genus'), new Word('genera')); | |||||
yield new Substitution(new Word('goose'), new Word('geese')); | |||||
yield new Substitution(new Word('graffito'), new Word('graffiti')); | |||||
yield new Substitution(new Word('hippopotamus'), new Word('hippopotami')); | |||||
yield new Substitution(new Word('hoof'), new Word('hoofs')); | |||||
yield new Substitution(new Word('human'), new Word('humans')); | |||||
yield new Substitution(new Word('iris'), new Word('irises')); | |||||
yield new Substitution(new Word('larva'), new Word('larvae')); | |||||
yield new Substitution(new Word('leaf'), new Word('leaves')); | |||||
yield new Substitution(new Word('lens'), new Word('lenses')); | |||||
yield new Substitution(new Word('loaf'), new Word('loaves')); | |||||
yield new Substitution(new Word('man'), new Word('men')); | |||||
yield new Substitution(new Word('medium'), new Word('media')); | |||||
yield new Substitution(new Word('memorandum'), new Word('memoranda')); | |||||
yield new Substitution(new Word('money'), new Word('monies')); | |||||
yield new Substitution(new Word('mongoose'), new Word('mongooses')); | |||||
yield new Substitution(new Word('motto'), new Word('mottoes')); | |||||
yield new Substitution(new Word('move'), new Word('moves')); | |||||
yield new Substitution(new Word('mythos'), new Word('mythoi')); | |||||
yield new Substitution(new Word('niche'), new Word('niches')); | |||||
yield new Substitution(new Word('nucleus'), new Word('nuclei')); | |||||
yield new Substitution(new Word('numen'), new Word('numina')); | |||||
yield new Substitution(new Word('occiput'), new Word('occiputs')); | |||||
yield new Substitution(new Word('octopus'), new Word('octopuses')); | |||||
yield new Substitution(new Word('opus'), new Word('opuses')); | |||||
yield new Substitution(new Word('ox'), new Word('oxen')); | |||||
yield new Substitution(new Word('passerby'), new Word('passersby')); | |||||
yield new Substitution(new Word('penis'), new Word('penises')); | |||||
yield new Substitution(new Word('person'), new Word('people')); | |||||
yield new Substitution(new Word('plateau'), new Word('plateaux')); | |||||
yield new Substitution(new Word('runner-up'), new Word('runners-up')); | |||||
yield new Substitution(new Word('safe'), new Word('safes')); | |||||
yield new Substitution(new Word('sex'), new Word('sexes')); | |||||
yield new Substitution(new Word('soliloquy'), new Word('soliloquies')); | |||||
yield new Substitution(new Word('son-in-law'), new Word('sons-in-law')); | |||||
yield new Substitution(new Word('syllabus'), new Word('syllabi')); | |||||
yield new Substitution(new Word('testis'), new Word('testes')); | |||||
yield new Substitution(new Word('thief'), new Word('thieves')); | |||||
yield new Substitution(new Word('tooth'), new Word('teeth')); | |||||
yield new Substitution(new Word('tornado'), new Word('tornadoes')); | |||||
yield new Substitution(new Word('trilby'), new Word('trilbys')); | |||||
yield new Substitution(new Word('turf'), new Word('turfs')); | |||||
yield new Substitution(new Word('valve'), new Word('valves')); | |||||
yield new Substitution(new Word('volcano'), new Word('volcanoes')); | |||||
yield new Substitution(new Word('abuse'), new Word('abuses')); | |||||
yield new Substitution(new Word('avalanche'), new Word('avalanches')); | |||||
yield new Substitution(new Word('cache'), new Word('caches')); | |||||
yield new Substitution(new Word('criterion'), new Word('criteria')); | |||||
yield new Substitution(new Word('curve'), new Word('curves')); | |||||
yield new Substitution(new Word('emphasis'), new Word('emphases')); | |||||
yield new Substitution(new Word('foe'), new Word('foes')); | |||||
yield new Substitution(new Word('grave'), new Word('graves')); | |||||
yield new Substitution(new Word('hoax'), new Word('hoaxes')); | |||||
yield new Substitution(new Word('medium'), new Word('media')); | |||||
yield new Substitution(new Word('neurosis'), new Word('neuroses')); | |||||
yield new Substitution(new Word('save'), new Word('saves')); | |||||
yield new Substitution(new Word('wave'), new Word('waves')); | |||||
yield new Substitution(new Word('oasis'), new Word('oases')); | |||||
yield new Substitution(new Word('valve'), new Word('valves')); | |||||
yield new Substitution(new Word('zombie'), new Word('zombies')); | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\English; | |||||
use Doctrine\Inflector\GenericLanguageInflectorFactory; | |||||
use Doctrine\Inflector\Rules\Ruleset; | |||||
final class InflectorFactory extends GenericLanguageInflectorFactory | |||||
{ | |||||
protected function getSingularRuleset() : Ruleset | |||||
{ | |||||
return Rules::getSingularRuleset(); | |||||
} | |||||
protected function getPluralRuleset() : Ruleset | |||||
{ | |||||
return Rules::getPluralRuleset(); | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\English; | |||||
use Doctrine\Inflector\Rules\Patterns; | |||||
use Doctrine\Inflector\Rules\Ruleset; | |||||
use Doctrine\Inflector\Rules\Substitutions; | |||||
use Doctrine\Inflector\Rules\Transformations; | |||||
final class Rules | |||||
{ | |||||
public static function getSingularRuleset() : Ruleset | |||||
{ | |||||
return new Ruleset( | |||||
new Transformations(...Inflectible::getSingular()), | |||||
new Patterns(...Uninflected::getSingular()), | |||||
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() | |||||
); | |||||
} | |||||
public static function getPluralRuleset() : Ruleset | |||||
{ | |||||
return new Ruleset( | |||||
new Transformations(...Inflectible::getPlural()), | |||||
new Patterns(...Uninflected::getPlural()), | |||||
new Substitutions(...Inflectible::getIrregular()) | |||||
); | |||||
} | |||||
} |
@@ -0,0 +1,193 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\English; | |||||
use Doctrine\Inflector\Rules\Pattern; | |||||
final class Uninflected | |||||
{ | |||||
/** | |||||
* @return Pattern[] | |||||
*/ | |||||
public static function getSingular() : iterable | |||||
{ | |||||
yield from self::getDefault(); | |||||
yield new Pattern('.*ss'); | |||||
yield new Pattern('clothes'); | |||||
yield new Pattern('data'); | |||||
yield new Pattern('fascia'); | |||||
yield new Pattern('fuchsia'); | |||||
yield new Pattern('galleria'); | |||||
yield new Pattern('mafia'); | |||||
yield new Pattern('militia'); | |||||
yield new Pattern('pants'); | |||||
yield new Pattern('petunia'); | |||||
yield new Pattern('sepia'); | |||||
yield new Pattern('trivia'); | |||||
yield new Pattern('utopia'); | |||||
} | |||||
/** | |||||
* @return Pattern[] | |||||
*/ | |||||
public static function getPlural() : iterable | |||||
{ | |||||
yield from self::getDefault(); | |||||
yield new Pattern('people'); | |||||
yield new Pattern('trivia'); | |||||
yield new Pattern('\w+ware$'); | |||||
yield new Pattern('media'); | |||||
} | |||||
/** | |||||
* @return Pattern[] | |||||
*/ | |||||
private static function getDefault() : iterable | |||||
{ | |||||
yield new Pattern('\w+media'); | |||||
yield new Pattern('advice'); | |||||
yield new Pattern('aircraft'); | |||||
yield new Pattern('amoyese'); | |||||
yield new Pattern('art'); | |||||
yield new Pattern('audio'); | |||||
yield new Pattern('baggage'); | |||||
yield new Pattern('bison'); | |||||
yield new Pattern('borghese'); | |||||
yield new Pattern('bream'); | |||||
yield new Pattern('breeches'); | |||||
yield new Pattern('britches'); | |||||
yield new Pattern('buffalo'); | |||||
yield new Pattern('butter'); | |||||
yield new Pattern('cantus'); | |||||
yield new Pattern('carp'); | |||||
yield new Pattern('chassis'); | |||||
yield new Pattern('clippers'); | |||||
yield new Pattern('clothing'); | |||||
yield new Pattern('coal'); | |||||
yield new Pattern('cod'); | |||||
yield new Pattern('coitus'); | |||||
yield new Pattern('compensation'); | |||||
yield new Pattern('congoese'); | |||||
yield new Pattern('contretemps'); | |||||
yield new Pattern('coreopsis'); | |||||
yield new Pattern('corps'); | |||||
yield new Pattern('cotton'); | |||||
yield new Pattern('data'); | |||||
yield new Pattern('debris'); | |||||
yield new Pattern('deer'); | |||||
yield new Pattern('diabetes'); | |||||
yield new Pattern('djinn'); | |||||
yield new Pattern('education'); | |||||
yield new Pattern('eland'); | |||||
yield new Pattern('elk'); | |||||
yield new Pattern('emoji'); | |||||
yield new Pattern('equipment'); | |||||
yield new Pattern('evidence'); | |||||
yield new Pattern('faroese'); | |||||
yield new Pattern('feedback'); | |||||
yield new Pattern('fish'); | |||||
yield new Pattern('flounder'); | |||||
yield new Pattern('flour'); | |||||
yield new Pattern('foochowese'); | |||||
yield new Pattern('food'); | |||||
yield new Pattern('furniture'); | |||||
yield new Pattern('gallows'); | |||||
yield new Pattern('genevese'); | |||||
yield new Pattern('genoese'); | |||||
yield new Pattern('gilbertese'); | |||||
yield new Pattern('gold'); | |||||
yield new Pattern('headquarters'); | |||||
yield new Pattern('herpes'); | |||||
yield new Pattern('hijinks'); | |||||
yield new Pattern('homework'); | |||||
yield new Pattern('hottentotese'); | |||||
yield new Pattern('impatience'); | |||||
yield new Pattern('information'); | |||||
yield new Pattern('innings'); | |||||
yield new Pattern('jackanapes'); | |||||
yield new Pattern('jeans'); | |||||
yield new Pattern('jedi'); | |||||
yield new Pattern('kiplingese'); | |||||
yield new Pattern('knowledge'); | |||||
yield new Pattern('kongoese'); | |||||
yield new Pattern('leather'); | |||||
yield new Pattern('love'); | |||||
yield new Pattern('lucchese'); | |||||
yield new Pattern('luggage'); | |||||
yield new Pattern('mackerel'); | |||||
yield new Pattern('Maltese'); | |||||
yield new Pattern('management'); | |||||
yield new Pattern('metadata'); | |||||
yield new Pattern('mews'); | |||||
yield new Pattern('money'); | |||||
yield new Pattern('moose'); | |||||
yield new Pattern('mumps'); | |||||
yield new Pattern('music'); | |||||
yield new Pattern('nankingese'); | |||||
yield new Pattern('news'); | |||||
yield new Pattern('nexus'); | |||||
yield new Pattern('niasese'); | |||||
yield new Pattern('nutrition'); | |||||
yield new Pattern('offspring'); | |||||
yield new Pattern('oil'); | |||||
yield new Pattern('patience'); | |||||
yield new Pattern('pekingese'); | |||||
yield new Pattern('piedmontese'); | |||||
yield new Pattern('pincers'); | |||||
yield new Pattern('pistoiese'); | |||||
yield new Pattern('plankton'); | |||||
yield new Pattern('pliers'); | |||||
yield new Pattern('pokemon'); | |||||
yield new Pattern('police'); | |||||
yield new Pattern('polish'); | |||||
yield new Pattern('portuguese'); | |||||
yield new Pattern('proceedings'); | |||||
yield new Pattern('progress'); | |||||
yield new Pattern('rabies'); | |||||
yield new Pattern('rain'); | |||||
yield new Pattern('research'); | |||||
yield new Pattern('rhinoceros'); | |||||
yield new Pattern('rice'); | |||||
yield new Pattern('salmon'); | |||||
yield new Pattern('sand'); | |||||
yield new Pattern('sarawakese'); | |||||
yield new Pattern('scissors'); | |||||
yield new Pattern('sea[- ]bass'); | |||||
yield new Pattern('series'); | |||||
yield new Pattern('shavese'); | |||||
yield new Pattern('shears'); | |||||
yield new Pattern('sheep'); | |||||
yield new Pattern('siemens'); | |||||
yield new Pattern('silk'); | |||||
yield new Pattern('sms'); | |||||
yield new Pattern('soap'); | |||||
yield new Pattern('social media'); | |||||
yield new Pattern('spam'); | |||||
yield new Pattern('species'); | |||||
yield new Pattern('staff'); | |||||
yield new Pattern('sugar'); | |||||
yield new Pattern('swine'); | |||||
yield new Pattern('talent'); | |||||
yield new Pattern('toothpaste'); | |||||
yield new Pattern('traffic'); | |||||
yield new Pattern('travel'); | |||||
yield new Pattern('trousers'); | |||||
yield new Pattern('trout'); | |||||
yield new Pattern('tuna'); | |||||
yield new Pattern('us'); | |||||
yield new Pattern('vermontese'); | |||||
yield new Pattern('vinegar'); | |||||
yield new Pattern('weather'); | |||||
yield new Pattern('wenchowese'); | |||||
yield new Pattern('wheat'); | |||||
yield new Pattern('whiting'); | |||||
yield new Pattern('wildebeest'); | |||||
yield new Pattern('wood'); | |||||
yield new Pattern('wool'); | |||||
yield new Pattern('yengeese'); | |||||
} | |||||
} |
@@ -0,0 +1,49 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\French; | |||||
use Doctrine\Inflector\Rules\Pattern; | |||||
use Doctrine\Inflector\Rules\Substitution; | |||||
use Doctrine\Inflector\Rules\Transformation; | |||||
use Doctrine\Inflector\Rules\Word; | |||||
class Inflectible | |||||
{ | |||||
/** | |||||
* @return Transformation[] | |||||
*/ | |||||
public static function getSingular() : iterable | |||||
{ | |||||
yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/'), '\1ail'); | |||||
yield new Transformation(new Pattern('/ails$/'), 'ail'); | |||||
yield new Transformation(new Pattern('/(journ|chev)aux$/'), '\1al'); | |||||
yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/'), '\1'); | |||||
yield new Transformation(new Pattern('/s$/'), ''); | |||||
} | |||||
/** | |||||
* @return Transformation[] | |||||
*/ | |||||
public static function getPlural() : iterable | |||||
{ | |||||
yield new Transformation(new Pattern('/(s|x|z)$/'), '\1'); | |||||
yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)ail$/'), '\1aux'); | |||||
yield new Transformation(new Pattern('/ail$/'), 'ails'); | |||||
yield new Transformation(new Pattern('/al$/'), 'aux'); | |||||
yield new Transformation(new Pattern('/(bleu|émeu|landau|lieu|pneu|sarrau)$/'), '\1s'); | |||||
yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)$/'), '\1x'); | |||||
yield new Transformation(new Pattern('/$/'), 's'); | |||||
} | |||||
/** | |||||
* @return Substitution[] | |||||
*/ | |||||
public static function getIrregular() : iterable | |||||
{ | |||||
yield new Substitution(new Word('monsieur'), new Word('messieurs')); | |||||
yield new Substitution(new Word('madame'), new Word('mesdames')); | |||||
yield new Substitution(new Word('mademoiselle'), new Word('mesdemoiselles')); | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\French; | |||||
use Doctrine\Inflector\GenericLanguageInflectorFactory; | |||||
use Doctrine\Inflector\Rules\Ruleset; | |||||
final class InflectorFactory extends GenericLanguageInflectorFactory | |||||
{ | |||||
protected function getSingularRuleset() : Ruleset | |||||
{ | |||||
return Rules::getSingularRuleset(); | |||||
} | |||||
protected function getPluralRuleset() : Ruleset | |||||
{ | |||||
return Rules::getPluralRuleset(); | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\French; | |||||
use Doctrine\Inflector\Rules\Patterns; | |||||
use Doctrine\Inflector\Rules\Ruleset; | |||||
use Doctrine\Inflector\Rules\Substitutions; | |||||
use Doctrine\Inflector\Rules\Transformations; | |||||
final class Rules | |||||
{ | |||||
public static function getSingularRuleset() : Ruleset | |||||
{ | |||||
return new Ruleset( | |||||
new Transformations(...Inflectible::getSingular()), | |||||
new Patterns(...Uninflected::getSingular()), | |||||
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() | |||||
); | |||||
} | |||||
public static function getPluralRuleset() : Ruleset | |||||
{ | |||||
return new Ruleset( | |||||
new Transformations(...Inflectible::getPlural()), | |||||
new Patterns(...Uninflected::getPlural()), | |||||
new Substitutions(...Inflectible::getIrregular()) | |||||
); | |||||
} | |||||
} |
@@ -0,0 +1,34 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\French; | |||||
use Doctrine\Inflector\Rules\Pattern; | |||||
final class Uninflected | |||||
{ | |||||
/** | |||||
* @return Pattern[] | |||||
*/ | |||||
public static function getSingular() : iterable | |||||
{ | |||||
yield from self::getDefault(); | |||||
} | |||||
/** | |||||
* @return Pattern[] | |||||
*/ | |||||
public static function getPlural() : iterable | |||||
{ | |||||
yield from self::getDefault(); | |||||
} | |||||
/** | |||||
* @return Pattern[] | |||||
*/ | |||||
private static function getDefault() : iterable | |||||
{ | |||||
yield new Pattern(''); | |||||
} | |||||
} |
@@ -0,0 +1,40 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\NorwegianBokmal; | |||||
use Doctrine\Inflector\Rules\Pattern; | |||||
use Doctrine\Inflector\Rules\Substitution; | |||||
use Doctrine\Inflector\Rules\Transformation; | |||||
use Doctrine\Inflector\Rules\Word; | |||||
class Inflectible | |||||
{ | |||||
/** | |||||
* @return Transformation[] | |||||
*/ | |||||
public static function getSingular() : iterable | |||||
{ | |||||
yield new Transformation(new Pattern('/re$/i'), 'r'); | |||||
yield new Transformation(new Pattern('/er$/i'), ''); | |||||
} | |||||
/** | |||||
* @return Transformation[] | |||||
*/ | |||||
public static function getPlural() : iterable | |||||
{ | |||||
yield new Transformation(new Pattern('/e$/i'), 'er'); | |||||
yield new Transformation(new Pattern('/r$/i'), 're'); | |||||
yield new Transformation(new Pattern('/$/'), 'er'); | |||||
} | |||||
/** | |||||
* @return Substitution[] | |||||
*/ | |||||
public static function getIrregular() : iterable | |||||
{ | |||||
yield new Substitution(new Word('konto'), new Word('konti')); | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\NorwegianBokmal; | |||||
use Doctrine\Inflector\GenericLanguageInflectorFactory; | |||||
use Doctrine\Inflector\Rules\Ruleset; | |||||
final class InflectorFactory extends GenericLanguageInflectorFactory | |||||
{ | |||||
protected function getSingularRuleset() : Ruleset | |||||
{ | |||||
return Rules::getSingularRuleset(); | |||||
} | |||||
protected function getPluralRuleset() : Ruleset | |||||
{ | |||||
return Rules::getPluralRuleset(); | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\NorwegianBokmal; | |||||
use Doctrine\Inflector\Rules\Patterns; | |||||
use Doctrine\Inflector\Rules\Ruleset; | |||||
use Doctrine\Inflector\Rules\Substitutions; | |||||
use Doctrine\Inflector\Rules\Transformations; | |||||
final class Rules | |||||
{ | |||||
public static function getSingularRuleset() : Ruleset | |||||
{ | |||||
return new Ruleset( | |||||
new Transformations(...Inflectible::getSingular()), | |||||
new Patterns(...Uninflected::getSingular()), | |||||
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions() | |||||
); | |||||
} | |||||
public static function getPluralRuleset() : Ruleset | |||||
{ | |||||
return new Ruleset( | |||||
new Transformations(...Inflectible::getPlural()), | |||||
new Patterns(...Uninflected::getPlural()), | |||||
new Substitutions(...Inflectible::getIrregular()) | |||||
); | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\NorwegianBokmal; | |||||
use Doctrine\Inflector\Rules\Pattern; | |||||
final class Uninflected | |||||
{ | |||||
/** | |||||
* @return Pattern[] | |||||
*/ | |||||
public static function getSingular() : iterable | |||||
{ | |||||
yield from self::getDefault(); | |||||
} | |||||
/** | |||||
* @return Pattern[] | |||||
*/ | |||||
public static function getPlural() : iterable | |||||
{ | |||||
yield from self::getDefault(); | |||||
} | |||||
/** | |||||
* @return Pattern[] | |||||
*/ | |||||
private static function getDefault() : iterable | |||||
{ | |||||
yield new Pattern('barn'); | |||||
yield new Pattern('fjell'); | |||||
yield new Pattern('hus'); | |||||
} | |||||
} |
@@ -0,0 +1,42 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules; | |||||
use function preg_match; | |||||
final class Pattern | |||||
{ | |||||
/** @var string */ | |||||
private $pattern; | |||||
/** @var string */ | |||||
private $regex; | |||||
public function __construct(string $pattern) | |||||
{ | |||||
$this->pattern = $pattern; | |||||
if (isset($this->pattern[0]) && $this->pattern[0] === '/') { | |||||
$this->regex = $this->pattern; | |||||
} else { | |||||
$this->regex = '/' . $this->pattern . '/i'; | |||||
} | |||||
} | |||||
public function getPattern() : string | |||||
{ | |||||
return $this->pattern; | |||||
} | |||||
public function getRegex() : string | |||||
{ | |||||
return $this->regex; | |||||
} | |||||
public function matches(string $word) : bool | |||||
{ | |||||
return preg_match($this->getRegex(), $word) === 1; | |||||
} | |||||
} |
@@ -0,0 +1,34 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules; | |||||
use function array_map; | |||||
use function implode; | |||||
use function preg_match; | |||||
class Patterns | |||||
{ | |||||
/** @var Pattern[] */ | |||||
private $patterns; | |||||
/** @var string */ | |||||
private $regex; | |||||
public function __construct(Pattern ...$patterns) | |||||
{ | |||||
$this->patterns = $patterns; | |||||
$patterns = array_map(static function (Pattern $pattern) : string { | |||||
return $pattern->getPattern(); | |||||
}, $this->patterns); | |||||
$this->regex = '/^(?:' . implode('|', $patterns) . ')$/i'; | |||||
} | |||||
public function matches(string $word) : bool | |||||
{ | |||||
return preg_match($this->regex, $word, $regs) === 1; | |||||
} | |||||
} |
@@ -0,0 +1,104 @@ | |||||
<?php | |||||
declare(strict_types=1); | |||||
namespace Doctrine\Inflector\Rules\Portuguese; | |||||
use Doctrine\Inflector\Rules\Pattern; | |||||
use Doctrine\Inflector\Rules\Substitution; | |||||
use Doctrine\Inflector\Rules\Transformation; | |||||
use Doctrine\Inflector\Rules\Word; | |||||
class Inflectible | |||||
{ | |||||
/** | |||||
* @return Transformation[] | |||||
*/ | |||||
public static function getSingular() : iterable | |||||
{ | |||||
yield new Transformation(new Pattern('/^(g|)ases$/i'), '\1ás'); | |||||
yield new Transformation(new Pattern('/(japon|escoc|ingl|dinamarqu|fregu|portugu)eses$/i'), '\1ês'); | |||||
yield new Transformation(new Pattern('/(ae|ao|oe)s$/'), 'ao'); | |||||
yield new Transformation(new Pattern('/(ãe|ão|õe)s$/'), 'ão'); | |||||
yield new Transformation(new Pattern('/^(.*[^s]s)es$/i'), '\1'); | |||||
yield new Transformation(new Pattern('/sses$/i'), 'sse'); | |||||
yield new Transformation(new Pattern('/ns$/i'), 'm'); | |||||
yield new Transformation(new Pattern('/(r|t|f|v)is$/i'), '\1il'); | |||||
yield new Transformation(new Pattern('/uis$/i'), 'ul'); | |||||
yield new Transformation(new Pattern('/ois$/i'), 'ol'); | |||||
yield new Transformation(new Pattern('/eis$/i'), 'ei'); | |||||
yield new Transformation(new Pattern('/éis$/i'), 'el'); | |||||
yield new Transformation(new Pattern('/([^p])ais$/i'), '\1al'); | |||||
yield new Transformation(new Pattern('/(r|z)es$/i'), '\1'); | |||||
yield new Transformation(new Pattern('/^(á|gá)s$/i'), '\1s'); | |||||
yield new Transformation(new Pattern('/([^ê])s$/i'), '\1'); | |||||
} | |||||
/** | |||||
* @return Transformation[] | |||||
*/ | |||||
public static function getPlural() : iterable | |||||
{ | |||||
yield new Transformation(new Pattern('/^(alem|c|p)ao$/i'), '\1aes'); | |||||
yield new Transformation(new Pattern('/^(irm|m)ao$/i'), '\1aos'); | |||||
yield new Transformation(new Pattern('/ao$/i'), 'oes'); | |||||
yield new Transformation(new Pattern('/^(alem|c|p)ão$/i'), '\1ães'); | |||||
yield new Transformation(new Pattern('/^(irm|m)ão$/i'), '\1ãos'); | |||||
yield new Transformation(new Pattern('/ão$/i'), 'ões'); | |||||
yield new Transformation(new Pattern('/^(|g)ás$/i'), '\1ases'); | |||||
yield new Transformation(new Pattern('/^(japon|escoc|ingl|dinamarqu|fregu|portugu)ês$/i'), '\1eses'); | |||||
yield new Transformation(new Pattern('/m$/i'), 'ns'); | |||||
yield new Transformation(new Pattern('/([^aeou])il$/i'), '\1is'); | |||||
yield new Transformation(new Pattern('/ul$/i'), 'uis'); | |||||
yield new Transformation(new Pattern('/ol$/i'), 'ois'); | |||||
yield new Transformation(new Pattern('/el$/i'), 'eis'); | |||||
yield new Transformation(new Pattern('/al$/i'), 'ais'); | |||||
yield new Transformation(new Pattern('/(z|r)$/i'), '\1es'); | |||||
yield new Transformation(new Pattern('/(s)$/i'), '\1'); | |||||
yield new Transformation(new Pattern('/$/'), 's'); | |||||
} | |||||
/** | |||||
* @return Substitution[] | |||||
*/ | |||||
public static function getIrregular() : iterable | |||||
{ | |||||
yield new Substitution(new Word('abdomen'), new Word('abdomens')); | |||||
yield new Substitution(new Word('alemão'), new Word('alemães')); | |||||
yield new Substitution(new Word('artesã'), new Word('artesãos')); | |||||
yield new Substitution(new Word('álcool'), new Word('álcoois')); | |||||
yield new Substitution(new Word('árvore'), new Word('árvores')); | |||||
yield new Substitution(new Word('bencão'), new Word('bencãos')); | |||||
yield new Substitution(new Word('cão'), new Word('cães')); | |||||
yield new Substitution(new Word('campus'), new Word('campi')); | |||||
yield new Substitution(new Word('cadáver'), new Word('cadáveres')); | |||||
yield new Substitution(new Word('capelão'), new Word('capelães')); | |||||
yield new Substitution(new Word('capitão'), new Word('capitães')); | |||||
yield new Substitution(new Word('chão'), new Word('chãos')); | |||||
yield new Substitution(new Word('charlatão'), new Word('charlatães')); | |||||
yield new Substitution(new Word('cidadão'), new Word('cidadãos')); | |||||
yield new Substitution(new Word('consul'), new Word('consules')); | |||||
yield new Substitution(new Word('cristão'), new Word('cristãos')); | |||||
yield new Substitution(new Word('difícil'), new Word('difíceis')); | |||||
yield new Substitution(new Word('email'), new Word('emails')); | |||||
yield new Substitution(new Word('escrivão'), new Word('escrivães')); | |||||
yield new Substitution(new Word('fóssil'), new Word('fósseis')); | |||||
yield new Substitution(new Word('gás'), new Word('gases')); | |||||
yield new Substitution(new Word('germens'), new Word('germen')); | |||||
yield new Substitution(new Word('grão'), new Word('grãos')); | |||||
yield new Substitution(new Word('hífen'), new Word('hífens')); | |||||
yield new Substitution(new Word('irmão'), new Word('irmãos')); | |||||
yield new Substitution(new Word('liquens'), new Word('liquen')); | |||||
yield new Substitution(new Word('mal'), new Word('males')); | |||||
yield new Substitution(new Word('mão'), new Word('mãos')); | |||||
yield new Substitution(new Word('orfão'), new Word('orfãos')); | |||||
yield new Substitution(new Word('país'), new Word('países')); | |||||
yield new Substitution(new Word('pai'), new Word('pais')); | |||||
yield new Substitution(new Word('pão'), new Word('pães')); | |||||
yield new Substitution(new Word('projétil'), new Word('projéteis')); | |||||
yield new Substitution(new Word('réptil'), new Word('répteis')); | |||||
yield new Substitution(new Word('sacristão'), new Word('sacristães')); | |||||
yield new Substitution(new Word('sotão'), new Word('sotãos')); | |||||
yield new Substitution(new Word('tabelião'), new Word('tabeliães')); | |||||
} | |||||
} |