mirror of
https://gitlab.com/signalytic/client-external/streamline/streamline-emr.git
synced 2026-09-11 10:41:32 +00:00
update dockerfiles based on latest streamline image
The latest images incorporate all changes previously added by the dockerfiles. The arm image however is still missing/unreliable from the registry and must be built independently.
This commit is contained in:
@@ -1 +1,2 @@
|
||||
build-packages
|
||||
.local
|
||||
@@ -1,51 +1 @@
|
||||
# FROM streamlinehealth/streamline:signalytic
|
||||
|
||||
# COPY my.cnf /etc/mysql/my.cnf
|
||||
# COPY mysql-init.sh /var/www/html/.docker/mysql/mysql-init.sh
|
||||
|
||||
FROM php:8.2-fpm-alpine
|
||||
|
||||
# Install necessary packages and cleanup
|
||||
RUN set -ex; \
|
||||
apk update && \
|
||||
apk add --no-cache curl gnupg mysql mysql-client pwgen && \
|
||||
docker-php-ext-install pdo pdo_mysql && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
# Install Composer
|
||||
RUN curl -sS https://getcomposer.org/installer | \
|
||||
php -- --install-dir=/usr/bin/ --filename=composer
|
||||
|
||||
# Set the working directory and copy the application code
|
||||
COPY streamline-src /var/www/html
|
||||
|
||||
COPY my.cnf /etc/mysql/my.cnf
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Create the 'streamline' user and adjust permissions
|
||||
RUN addgroup -g 1000 streamline && adduser -G streamline -g streamline -s /bin/sh -D streamline && \
|
||||
composer install && \
|
||||
chmod -R 777 /var/www/html/storage/ && \
|
||||
chown -R streamline:streamline /var/www/html && \
|
||||
mkdir -p /docker-entrypoint-initdb.d/
|
||||
|
||||
# Initialize MySQL
|
||||
COPY streamline_initial.sql /docker-entrypoint-initdb.d/
|
||||
|
||||
COPY mysql-init.sh /var/www/html/.docker/mysql/mysql-init.sh
|
||||
|
||||
RUN mkdir /scripts && \
|
||||
mkdir /scripts/pre-exec.d && \
|
||||
mkdir /scripts/pre-init.d && \
|
||||
chmod -R 755 /scripts
|
||||
|
||||
VOLUME ["/var/lib/mysql"]
|
||||
|
||||
# Set the startup script as executable
|
||||
RUN chmod +x /var/www/html/start_up.sh
|
||||
|
||||
# Define the entry point and expose port 80, 3306
|
||||
ENTRYPOINT ["/bin/sh", "/var/www/html/start_up.sh"]
|
||||
|
||||
EXPOSE 80 3306
|
||||
FROM streamlinehealth/streamline:signalytic
|
||||
@@ -33,8 +33,6 @@ RUN addgroup -g 1000 streamline && adduser -G streamline -g streamline -s /bin/s
|
||||
# Initialize MySQL
|
||||
COPY streamline_initial.sql /docker-entrypoint-initdb.d/
|
||||
|
||||
COPY mysql-init.sh /var/www/html/.docker/mysql/mysql-init.sh
|
||||
|
||||
RUN mkdir /scripts && \
|
||||
mkdir /scripts/pre-exec.d && \
|
||||
mkdir /scripts/pre-init.d && \
|
||||
@@ -48,4 +46,4 @@ RUN chmod +x /var/www/html/start_up.sh
|
||||
# Define the entry point and expose port 80, 3306
|
||||
ENTRYPOINT ["/bin/sh", "/var/www/html/start_up.sh"]
|
||||
|
||||
EXPOSE 80 3306
|
||||
EXPOSE 80 3306
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script helps to initialise MariaDB and setup fresh database instance
|
||||
|
||||
# execute any pre-init scripts
|
||||
for i in /scripts/pre-init.d/*sh
|
||||
do
|
||||
if [ -e "${i}" ]; then
|
||||
echo "[i] pre-init.d - processing $i"
|
||||
. "${i}"
|
||||
fi
|
||||
done
|
||||
|
||||
# create bin-logs folder
|
||||
if [ ! -d "/var/lib/mysql/logs" ]; then
|
||||
echo "[i] created logs folder."
|
||||
mkdir -p /var/lib/mysql/logs/
|
||||
fi
|
||||
|
||||
if [ -d "/run/mysqld" ]; then
|
||||
echo "[i] mysqld already present, skipping creation"
|
||||
chown -R mysql:mysql /run/mysqld
|
||||
else
|
||||
echo "[i] mysqld not found, creating...."
|
||||
mkdir -p /run/mysqld
|
||||
chown -R mysql:mysql /run/mysqld
|
||||
fi
|
||||
|
||||
if [ -d /var/lib/mysql/mysql ]; then
|
||||
echo "[i] MySQL directory already present, skipping creation"
|
||||
chown -R mysql:mysql /var/lib/mysql
|
||||
else
|
||||
echo "[i] MySQL data directory not found, creating initial DBs"
|
||||
|
||||
chown -R mysql:mysql /var/lib/mysql
|
||||
|
||||
mysql_install_db --user=mysql --data=/var/lib/mysql > /dev/null
|
||||
|
||||
tfile=`mktemp`
|
||||
if [ ! -f "$tfile" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
cat << EOF > $tfile
|
||||
USE mysql;
|
||||
FLUSH PRIVILEGES ;
|
||||
GRANT ALL ON *.* TO 'root'@'%' identified by '$MYSQL_ROOT_PASSWORD' WITH GRANT OPTION ;
|
||||
GRANT ALL ON *.* TO 'root'@'localhost' identified by '$MYSQL_ROOT_PASSWORD' WITH GRANT OPTION ;
|
||||
SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ;
|
||||
DROP DATABASE IF EXISTS test ;
|
||||
FLUSH PRIVILEGES ;
|
||||
EOF
|
||||
|
||||
if [[ "$MYSQL_DATABASE" != "" ]]; then
|
||||
echo "[i] Creating database: $MYSQL_DATABASE"
|
||||
if [ "$MYSQL_CHARSET" != "" ] && [ "$MYSQL_COLLATION" != "" ]; then
|
||||
echo "[i] with character set [$MYSQL_CHARSET] and collation [$MYSQL_COLLATION]"
|
||||
echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` CHARACTER SET $MYSQL_CHARSET COLLATE $MYSQL_COLLATION;" >> $tfile
|
||||
else
|
||||
echo "[i] with character set: 'utf8' and collation: 'utf8_general_ci'"
|
||||
echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` CHARACTER SET utf8 COLLATE utf8_general_ci;" >> $tfile
|
||||
fi
|
||||
|
||||
if [ "$MYSQL_USER" != "" ]; then
|
||||
echo "[i] Creating user: $MYSQL_USER with password $MYSQL_PASSWORD"
|
||||
|
||||
echo "GRANT ALL ON `$MYSQL_DATABASE`.* TO '$MYSQL_USER'@'127.0.0.1' IDENTIFIED BY '$MYSQL_PASSWORD';" >> $tfile;
|
||||
echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* to '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD';" >> $tfile
|
||||
fi
|
||||
fi
|
||||
|
||||
/usr/bin/mysqld --user=mysql --bootstrap --verbose=0 --skip-name-resolve --skip-networking=0 < $tfile
|
||||
# /usr/bin/mysqld --user=mysql --bootstrap --verbose=0 --skip-name-resolve --skip-networking=0 --log-basename=bin --log-bin=/var/lib/mysql/logs/bin < $tfile
|
||||
rm -f $tfile
|
||||
|
||||
# only run if we have a starting MYSQL_DATABASE env variable AND
|
||||
# the /docker-entrypoint-initdb.d/ file is not empty
|
||||
if [ "$MYSQL_DATABASE" != "" ] && [ "$(ls -A /docker-entrypoint-initdb.d 2>/dev/null)" ]; then
|
||||
|
||||
# start the server temporarily so that we can import seed files
|
||||
echo
|
||||
echo "Preparing to process the contents of /docker-entrypoint-initdb.d/"
|
||||
echo
|
||||
TEMP_OUTPUT_LOG=/tmp/mysqld_output
|
||||
/usr/bin/mysqld --user=mysql --skip-name-resolve --skip-networking=0 --silent-startup > "${TEMP_OUTPUT_LOG}" 2>&1 &
|
||||
# /usr/bin/mysqld --user=mysql --skip-name-resolve --skip-networking=0 --log-basename=bin --log-bin=/var/lib/mysql/logs/bin --silent-startup > "${TEMP_OUTPUT_LOG}" 2>&1 &
|
||||
PID="$!"
|
||||
|
||||
# watch the output log until the server is running
|
||||
until tail "${TEMP_OUTPUT_LOG}" | grep -q "Version:"; do
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
# use mysql client to import seed files while temp db is running
|
||||
# use the starting MYSQL_DATABASE so mysql knows where to import
|
||||
MYSQL_CLIENT="/usr/bin/mysql -u root -p$MYSQL_ROOT_PASSWORD"
|
||||
|
||||
# loop through all the files in the seed directory
|
||||
# redirect input (<) from .sql files into the mysql client command line
|
||||
# pipe (|) the output of using `gunzip -c` on .sql.gz files
|
||||
for f in /docker-entrypoint-initdb.d/*; do
|
||||
case "$f" in
|
||||
*.sql) echo " $0: running $f"; eval "${MYSQL_CLIENT} ${MYSQL_DATABASE} < $f"; echo ;;
|
||||
*.sql.gz) echo " $0: running $f"; gunzip -c "$f" | eval "${MYSQL_CLIENT} ${MYSQL_DATABASE}"; echo ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# send the temporary mysqld server a shutdown signal
|
||||
# and wait till it's done before completeing the init process
|
||||
kill -s TERM "${PID}"
|
||||
wait "${PID}"
|
||||
rm -f TEMP_OUTPUT_LOG
|
||||
echo "Completed processing seed files."
|
||||
fi;
|
||||
|
||||
echo
|
||||
echo 'MySQL init process done. Ready for start up.'
|
||||
echo
|
||||
|
||||
echo "exec /usr/bin/mysqld --user=mysql --console --skip-name-resolve --skip-networking=0" "$@"
|
||||
# echo "exec /usr/bin/mysqld --user=mysql --console --skip-name-resolve --skip-networking=0 --log-basename=bin --log-bin=/var/lib/mysql/logs/bin " "$@"
|
||||
fi
|
||||
|
||||
# execute any pre-exec scripts
|
||||
for i in /scripts/pre-exec.d/*sh
|
||||
do
|
||||
if [ -e "${i}" ]; then
|
||||
echo "[i] pre-exec.d - processing $i"
|
||||
. ${i}
|
||||
fi
|
||||
done
|
||||
|
||||
exec /usr/bin/mysqld --user=mysql --console --skip-name-resolve --skip-networking=0 &
|
||||
|
||||
return $?
|
||||
@@ -69,7 +69,8 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
/usr/bin/mysqld --user=mysql --bootstrap --verbose=0 --skip-name-resolve --skip-networking=0 --log-basename=bin --log-bin=/var/lib/mysql/logs/bin < $tfile
|
||||
/usr/bin/mysqld --user=mysql --bootstrap --verbose=0 --skip-name-resolve --skip-networking=0 < $tfile
|
||||
# /usr/bin/mysqld --user=mysql --bootstrap --verbose=0 --skip-name-resolve --skip-networking=0 --log-basename=bin --log-bin=/var/lib/mysql/logs/bin < $tfile
|
||||
rm -f $tfile
|
||||
|
||||
# only run if we have a starting MYSQL_DATABASE env variable AND
|
||||
@@ -81,7 +82,8 @@ EOF
|
||||
echo "Preparing to process the contents of /docker-entrypoint-initdb.d/"
|
||||
echo
|
||||
TEMP_OUTPUT_LOG=/tmp/mysqld_output
|
||||
/usr/bin/mysqld --user=mysql --skip-name-resolve --skip-networking=0 --log-basename=bin --log-bin=/var/lib/mysql/logs/bin --silent-startup > "${TEMP_OUTPUT_LOG}" 2>&1 &
|
||||
/usr/bin/mysqld --user=mysql --skip-name-resolve --skip-networking=0 --silent-startup > "${TEMP_OUTPUT_LOG}" 2>&1 &
|
||||
# /usr/bin/mysqld --user=mysql --skip-name-resolve --skip-networking=0 --log-basename=bin --log-bin=/var/lib/mysql/logs/bin --silent-startup > "${TEMP_OUTPUT_LOG}" 2>&1 &
|
||||
PID="$!"
|
||||
|
||||
# watch the output log until the server is running
|
||||
@@ -115,7 +117,8 @@ EOF
|
||||
echo 'MySQL init process done. Ready for start up.'
|
||||
echo
|
||||
|
||||
echo "exec /usr/bin/mysqld --user=mysql --console --skip-name-resolve --skip-networking=0 --log-basename=bin --log-bin=/var/lib/mysql/logs/bin " "$@"
|
||||
echo "exec /usr/bin/mysqld --user=mysql --console --skip-name-resolve --skip-networking=0" "$@"
|
||||
# echo "exec /usr/bin/mysqld --user=mysql --console --skip-name-resolve --skip-networking=0 --log-basename=bin --log-bin=/var/lib/mysql/logs/bin " "$@"
|
||||
fi
|
||||
|
||||
# execute any pre-exec scripts
|
||||
@@ -127,6 +130,6 @@ do
|
||||
fi
|
||||
done
|
||||
|
||||
exec /usr/bin/mysqld --user=mysql --console --skip-name-resolve --skip-networking=0 --log-basename=bin --log-bin=/var/lib/mysql/logs/bin &
|
||||
exec /usr/bin/mysqld --user=mysql --console --skip-name-resolve --skip-networking=0 &
|
||||
|
||||
return $?
|
||||
@@ -5,4 +5,5 @@ images
|
||||
insurance_members
|
||||
hospital_resources
|
||||
csv_imports
|
||||
users
|
||||
users/*
|
||||
!users/placeholder.png
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,5 @@
|
||||
vendor/
|
||||
tests/Fixtures.php
|
||||
.idea/
|
||||
/.phpunit.result.cache
|
||||
.phpunit.cache/
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Africa's Talking
|
||||
|
||||
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,246 @@
|
||||
# Africa's Talking PHP SDK
|
||||
|
||||
[](https://packagist.org/packages/africastalking/africastalking)
|
||||
|
||||
> This SDK provides convenient access to the Africa's Talking API for applications written in PHP.
|
||||
|
||||
## Documentation
|
||||
|
||||
Take a look at the [API docs here](https://developers.africastalking.com).
|
||||
|
||||
## Install
|
||||
|
||||
You can install the PHP SDK via composer or by downloading the source
|
||||
|
||||
#### Via Composer
|
||||
|
||||
The recommended way to install the SDK is with [Composer](http://getcomposer.org/).
|
||||
|
||||
```bash
|
||||
composer require africastalking/africastalking
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The SDK needs to be instantiated using your username and API key, which you can get from the [dashboard](https://account.africastalking.com).
|
||||
|
||||
> You can use this SDK for either production or sandbox apps. For sandbox, the app username is **ALWAYS** `sandbox`
|
||||
|
||||
```php
|
||||
use AfricasTalking\SDK\AfricasTalking;
|
||||
|
||||
$username = 'YOUR_USERNAME'; // use 'sandbox' for development in the test environment
|
||||
$apiKey = 'YOUR_API_KEY'; // use your sandbox app API key for development in the test environment
|
||||
$AT = new AfricasTalking($username, $apiKey);
|
||||
|
||||
// Get one of the services
|
||||
$sms = $AT->sms();
|
||||
|
||||
// Use the service
|
||||
$result = $sms->send([
|
||||
'to' => '+2XXYYYOOO',
|
||||
'message' => 'Hello World!'
|
||||
]);
|
||||
|
||||
print_r($result);
|
||||
```
|
||||
|
||||
See [example](example/) for more usage examples.
|
||||
|
||||
## Instantiation
|
||||
|
||||
Instantiating the class will give you an object with available methods
|
||||
|
||||
- `$AT = new AfricasTalking($username, $apiKey)`: Instantiate the class
|
||||
- Get available service
|
||||
- [SMS Service](#sms): `$sms = $AT->sms()`
|
||||
- [Content Service](#content): `$content = $AT->content()`
|
||||
- [Airtime Service](#airtime): `$airtime = $AT->airtime()`
|
||||
- [Mobile Data Service](#mobiledata): `$mobileData = $AT->mobileData()`
|
||||
- [Voice Service](#voice): `$voice = $AT->voice()`
|
||||
- [Token Service](#token): `$token = $AT->token()`
|
||||
- [Application Service](#application): `$application = $AT->application()`
|
||||
|
||||
### Application
|
||||
|
||||
- `fetchApplicationData()`: Get app information. e.g balance
|
||||
|
||||
### Airtime
|
||||
|
||||
- `send($parameters, $options)`: Send airtime
|
||||
|
||||
- **$parameters:** associative array with the following keys:
|
||||
|
||||
- `recipients`: An array of arrays containing the following keys
|
||||
- `phoneNumber`: Recipient of airtime. `REQUIRED`
|
||||
- `currencyCode`: 3-digit ISO format currency code (e.g `KES`, `USD`, `UGX` etc). `REQUIRED`
|
||||
- `amount`: Amount to send. `REQUIRED`
|
||||
- **$options:** optional associative array with the following keys:
|
||||
|
||||
- `idempotencyKey`: Key to use when making idempotent requests
|
||||
- `maxNumRetry`: Maximum number of retries in case of failed airtime deliveries due to telco unavailability or any other reason.
|
||||
|
||||
### SMS
|
||||
|
||||
- `send($options)`: Send a message
|
||||
|
||||
- `message`: SMS content. `REQUIRED`
|
||||
- `to`: An array of phone numbers. `REQUIRED`
|
||||
- `from`: Shortcode or alphanumeric ID that is registered with your Africa's Talking account.
|
||||
- `enqueue`: Set to `true` if you would like to deliver as many messages to the API without waiting for an acknowledgement from telcos.
|
||||
- `fetchMessages($options)`: Fetch your messages
|
||||
|
||||
- `lastReceivedId`: This is the id of the message you last processed. Defaults to `0`
|
||||
|
||||
***The followoing methods have been moved to the content service, but, have been maintained on SMS for backwards compatibility:***
|
||||
|
||||
- `sendPremium($options)`: Send a premium SMS. Calls `$content->send($options)`
|
||||
- `createSubscription($options)`: Create a premium subscription. Calls `$content->createSubscription($options)`
|
||||
- `fetchSubscriptions($options)`: Fetch your premium subscription data. Calls `$content->fetchSubscriptions($options)`
|
||||
- `deleteSubscription($options)`: Delete a phone number from a premium subscription. Calls `$content->$deleteSubscription($options)`
|
||||
|
||||
### Content
|
||||
|
||||
- `send($options)`: Send a premium SMS
|
||||
|
||||
- `message`: SMS content. `REQUIRED`
|
||||
- `to`: An array of phone numbers. `REQUIRED`
|
||||
- `from`: Shortcode that is registered with your Africa's Talking account. `REQUIRED`
|
||||
- `keyword`: Your premium product keyword
|
||||
- `linkId`: "[...] We forward the `linkId` to your application when a user sends a message to your onDemand service"
|
||||
- `retryDurationInHours`: "This specifies the number of hours your subscription message should be retried in case it's not delivered to the subscriber"
|
||||
- `createSubscription($options)`: Create a premium subscription
|
||||
|
||||
- `shortCode`: Premium short code mapped to your account. `REQUIRED`
|
||||
- `keyword`: Premium keyword under the above short code and is also mapped to your account. `REQUIRED`
|
||||
- `phoneNumber`: PhoneNumber to be subscribed `REQUIRED`
|
||||
- `fetchSubscriptions($options)`: Fetch your premium subscription data
|
||||
|
||||
- `shortCode`: Premium short code mapped to your account. `REQUIRED`
|
||||
- `keyword`: Premium keyword under the above short code and mapped to your account. `REQUIRED`
|
||||
- `lastReceivedId`: ID of the subscription you believe to be your last. Defaults to `0`
|
||||
- `deleteSubscription($options)`: Delete a phone number from a premium subscription
|
||||
|
||||
- `shortCode`: Premium short code mapped to your account. `REQUIRED`
|
||||
- `keyword`: Premium keyword under the above short code and is also mapped to your account. `REQUIRED`
|
||||
- `phoneNumber`: PhoneNumber to be subscribed `REQUIRED`
|
||||
|
||||
### Mobile Data
|
||||
|
||||
- `send($parameters, $options)`: Send mobile data to customers
|
||||
|
||||
- **$parameters:** associative array with the following keys:
|
||||
|
||||
- `productName`: Payment product on Africa's Talking. `REQUIRED`
|
||||
- `recipients`: A list of recipients. Each recipient has:
|
||||
|
||||
- `phoneNumber`: Customer phone number (in international format). `REQUIRED`
|
||||
- `quantity`: Mobile data amount. `REQUIRED`
|
||||
- `unit`: Mobile data unit. Can either be `MB` or `GB`. `REQUIRED`
|
||||
- `validity`: How long the mobile data is valid for. Must be one of `Day`, `Week` and `Month`. `REQUIRED`
|
||||
- `metadata`: Additional data to associate with the tranasction. `REQUIRED`
|
||||
|
||||
- `isPromoBundle`: This is an optional field that can be either `true` or `false`.
|
||||
|
||||
- **$options:** optional associative array with the following keys:
|
||||
|
||||
- `idempotencyKey`: Key to use when making idempotent requests
|
||||
|
||||
- `findTransaction($parameters)`: Find a particular transaction
|
||||
|
||||
- `transactionId`: ID of trancation to find. `REQUIRED`
|
||||
|
||||
- `fetchWalletBalance()`: Fetch your payment wallet balance
|
||||
|
||||
### Voice
|
||||
|
||||
- `call($options)`: Initiate a phone call
|
||||
|
||||
- `to`: Phone number that you wish to dial (in international format). `REQUIRED`
|
||||
- `from`: Phone number on Africa's Talking (in international format). `REQUIRED`
|
||||
- `clientRequestId`: Variable sent to your Events Callback URL that can be used to tag the call. `OPTIONAL`
|
||||
- `fetchQueuedCalls($options)`: Fetch queued calls on a phone number
|
||||
|
||||
- `phoneNumber`: Phone number mapped to your Africa's Talking account (in international format). `REQUIRED`
|
||||
- `name`: Fetch calls for a specific queue.
|
||||
- `uploadMediaFile($options)`: Upload a voice media file
|
||||
|
||||
- `phoneNumber`: phone number mapped to your Africa's Talking account (in international format). `REQUIRED`
|
||||
- `url`: The url of the file to upload. Should start with `http(s)://`. `REQUIRED`
|
||||
|
||||
#### MessageBuilder
|
||||
|
||||
Build voice xml when callback URL receives a POST from the voice API. Actions can be chained to create an XML string.
|
||||
|
||||
```php
|
||||
$voiceActions = $voice->messageBuilder();
|
||||
$xmlresponse = $voiceActions
|
||||
->getDigits($options)
|
||||
->say($text)
|
||||
->record()
|
||||
->build();
|
||||
```
|
||||
|
||||
- `say($text)`: Add a `Say` action
|
||||
- `text`: Text (in English) that will be read out to the user.
|
||||
- `play($url)`: Add a `Play` action
|
||||
|
||||
- `url`: Public url to an audio file. This file will be played back to user.
|
||||
- `getDigits($options)`: Add a `GetDigits` action
|
||||
|
||||
- `numDigits`: Number of digits should be gotten from the user
|
||||
- `timeout`: Timeout (in seconds) for getting digits from a user.
|
||||
- `finishOnKey`: key which will terminate the action of getting digits.
|
||||
- `callbackUrl`: URL to forward the results of the GetDigits action.
|
||||
- `dial($options)`: Add a `Dial` action
|
||||
|
||||
- `phoneNumbers`: An array of phone numbers (in international format) to call. `REQUIRED`
|
||||
- `record`: Boolean - Whether to record the conversation.
|
||||
- `sequenntial`: Boolean - If many numbers provided for `phoneNumbers`, determines whether the phone numbers will be dialed one after the other or at the same time.
|
||||
- `callerId`: Africa's Talking number you want to dial out with.
|
||||
- `ringBackTone`: URL location of a media playback you would want the user to listen to when the call has been placed before its picked up.
|
||||
- `maxDuration`: maximum amount of time in seconds a call should take.
|
||||
- `conference()`: Add a `Conference` action
|
||||
- `record($options)`: Add a `Record` action
|
||||
|
||||
- `finishOnKey`: Key which will terminate the action of recording.
|
||||
- `maxLength`: Maximum amount of time in seconds a recording should take.
|
||||
- `timeout`: Timeout (in seconds) for getting a recording from a user.
|
||||
- `trimSilence`: Boolean - Specifies whether you want to remove the initial and final parts of a recording where user was silent.
|
||||
- `playBeep`: Boolean - Specifies whether the API should play a beep when recording starts.
|
||||
- `callbackUrl`: URL to forward the results of the Recording action.
|
||||
- `enqueue($options)`: Add an `Enqueue` action
|
||||
|
||||
- `holdMusic`: URL to the file to be played while the user is on hold.
|
||||
- `name`: Name of queue to put call on.
|
||||
- `deqeue($options)`: Add a `Dequeue` acton
|
||||
|
||||
- `phoneNumber`: Phone number mapped to your Africa's Talking account which a user called to join the queue. `REQUIRED`
|
||||
- `name`: Name of queue you want to dequeue from.
|
||||
- `reject()`: Add a `Reject` action
|
||||
- `redirect($url)`: Add a `Redirect` action
|
||||
|
||||
- `url`: URL to transfer control of the call to
|
||||
- `build()`: Build the xml after chaining some of the above actions
|
||||
|
||||
### Token
|
||||
|
||||
- `generateAuthToken()`: Generate an auth token to use for authenticating API requests instead of your API key.
|
||||
|
||||
## Testing the SDK
|
||||
|
||||
The SDK uses [PHPUnit](https://phpunit.de/manual/current/en/index.html) as the test runner.
|
||||
|
||||
To run available tests, from the root of the project run:
|
||||
|
||||
```bash
|
||||
# Configure needed fixtures, e.g sandbox api key, Africa's Talking products
|
||||
cp tests/Fixtures.php.tpl tests/Fixtures.php
|
||||
|
||||
# Run tests
|
||||
phpunit --testdox
|
||||
```
|
||||
|
||||
## Issues
|
||||
|
||||
If you find a bug, please file an issue on [our issue tracker on GitHub](https://github.com/AfricasTalkingLtd/africastalking-php/issues).
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "africastalking/africastalking",
|
||||
"description": "Official Africa's Talking PHP SDK",
|
||||
"keywords": ["sms", "voice", "ussd", "text message", "airtime", "api", "africastalking"],
|
||||
"homepage": "http://github.com/AfricasTalkingLtd/africastalking-php",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Africas's Talking",
|
||||
"email": "support@africastalking.com",
|
||||
"homepage": "https://www.africastalking.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"guzzlehttp/guzzle": "^6.0 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"AfricasTalking\\SDK\\": "src/",
|
||||
"AfricasTalking\\SDK\\Tests\\": "tests"
|
||||
}
|
||||
}
|
||||
}
|
||||
+2350
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Example
|
||||
|
||||
|
||||
**Run**
|
||||
|
||||
First, make sure to put your sandbox api key in `index.php`, then:
|
||||
|
||||
```bash
|
||||
$ composer install
|
||||
$ php -S localhost:9090
|
||||
$ # open http://localhost:9090
|
||||
```
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "africastalking-example",
|
||||
"description": "Simple SDK usage",
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"africastalking/africastalking": "dev-develop",
|
||||
"altorouter/altorouter": "1.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use AfricasTalking\SDK\AfricasTalking;
|
||||
|
||||
$username = "sandbox";
|
||||
$apiKey = getenv("API_KEY");
|
||||
|
||||
$AT = new AfricasTalking($username, $apiKey);
|
||||
|
||||
// Router
|
||||
$router = new AltoRouter();
|
||||
|
||||
$router->map( 'GET', '/', function() {
|
||||
require __DIR__ . '/views/index.php';
|
||||
});
|
||||
|
||||
$router->map( 'POST', '/auth/register/[*:phone]', function ($phone) {
|
||||
global $AT;
|
||||
$sms = $AT->sms();
|
||||
$response = $sms->send(array(
|
||||
"to" => $phone,
|
||||
"from" => "AT2FA",
|
||||
"message" => "Welcome to Awesome Company",
|
||||
));
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
echo json_encode($response);
|
||||
});
|
||||
|
||||
$router->map( 'POST', '/airtime/[*:phone]', function ($phone) {
|
||||
global $AT;
|
||||
$airtime = $AT->airtime();
|
||||
$response = $airtime->send(array(
|
||||
"recipients" => array(
|
||||
array(
|
||||
"phoneNumber" => $phone,
|
||||
"amount" => $_GET['amount'],
|
||||
)
|
||||
)
|
||||
));
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
echo json_encode($response);
|
||||
});
|
||||
|
||||
$match = $router->match();
|
||||
if( $match && is_callable( $match['target'] ) ) {
|
||||
call_user_func_array( $match['target'], $match['params'] );
|
||||
} else {
|
||||
header( $_SERVER["SERVER_PROTOCOL"] . ' 404 Not Found');
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/* Space out content a bit */
|
||||
body {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
/* Everything but the jumbotron gets side spacing for mobile first views */
|
||||
.header,
|
||||
.marketing,
|
||||
.footer {
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
/* Custom page header */
|
||||
.header {
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
/* Make the masthead heading the same height as the navigation */
|
||||
.header h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
line-height: 40px;
|
||||
}
|
||||
/* Custom page footer */
|
||||
.footer {
|
||||
padding-top: 19px;
|
||||
color: #777;
|
||||
}
|
||||
/* Customize container */
|
||||
@media (min-width: 768px) {
|
||||
/*.container {
|
||||
max-width: 1200px;
|
||||
}*/
|
||||
}
|
||||
.container-narrow > hr {
|
||||
margin: 30px 0;
|
||||
}
|
||||
/* Main marketing message and sign up button */
|
||||
.jumbotron {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
/*.jumbotron .btn {
|
||||
padding: 14px 24px;
|
||||
font-size: 21px;
|
||||
}*/
|
||||
/* Supporting marketing content */
|
||||
.marketing {
|
||||
margin: 40px 0;
|
||||
}
|
||||
.marketing p + h4 {
|
||||
margin-top: 28px;
|
||||
}
|
||||
/* Responsive: Portrait tablets and up */
|
||||
@media screen and (min-width: 768px) {
|
||||
/* Remove the padding we set earlier */
|
||||
.header,
|
||||
.marketing,
|
||||
.footer {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
/* Space out the masthead */
|
||||
.header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
/* Remove the bottom border on the jumbotron for visual effect */
|
||||
.jumbotron {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
background: white;
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
"use strict";
|
||||
|
||||
$(function () {
|
||||
|
||||
|
||||
function log(message) {
|
||||
$("#response").text(message);
|
||||
$('pre span').each(function(i, block) {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
}
|
||||
|
||||
$("#signUp").click(() => {
|
||||
let phone = $("#phone").val();
|
||||
if (!phone) {
|
||||
log(JSON.stringify({ error: "Enter a phone number"}, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
log("Sending SMS...");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: `/auth/register/${phone}`,
|
||||
success: (resp) => {
|
||||
try {
|
||||
log(JSON.stringify(resp, null, 2));
|
||||
} catch (ex) {
|
||||
log(resp);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$("#airtime").click(() => {
|
||||
const phone = $("#phone").val();
|
||||
const amount = $("#amount").val();
|
||||
if (!phone) {
|
||||
log(JSON.stringify({ error: "Enter a phone number"}, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!amount) {
|
||||
log(JSON.stringify({ error: "Enter an amount (with currency) e,g, KES 334"}, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
log("Sending Airtime...");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: `/airtime/${phone}?amount=${amount}`,
|
||||
success: (resp) => {
|
||||
try {
|
||||
log(JSON.stringify(resp, null, 2));
|
||||
} catch (ex) {
|
||||
log(resp);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$("#mobileCheckout").click(() => {
|
||||
const phone = $("#phone").val();
|
||||
const amount = $("#mobileCheckoutAmount").val();
|
||||
if (!phone) {
|
||||
log(JSON.stringify({ error: "Enter a phone number"}, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!amount) {
|
||||
log(JSON.stringify({ error: "Enter an amount (with currency) e,g, KES 334"}, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
log("Initiating Mobile Checkout...");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: `/mobile/checkout/${phone}?amount=${amount}`,
|
||||
success: (resp) => {
|
||||
try {
|
||||
log(JSON.stringify(resp, null, 2));
|
||||
} catch (ex) {
|
||||
log(resp);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$("#mobileB2C").click(() => {
|
||||
const phone = $("#phone").val();
|
||||
const amount = $("#mobileB2CAmount").val();
|
||||
if (!phone) {
|
||||
log(JSON.stringify({ error: "Enter a phone number"}, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!amount) {
|
||||
log(JSON.stringify({ error: "Enter an amount (with currency) e,g, KES 334"}, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
log("Initiating Mobile B2C...");
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: `/mobile/b2c/${phone}?amount=${amount}`,
|
||||
success: (resp) => {
|
||||
try {
|
||||
log(JSON.stringify(resp, null, 2));
|
||||
} catch (ex) {
|
||||
log(resp);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Africa's Talking</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/paper/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/tomorrow.min.css" />
|
||||
<link href="public/css/style.css" rel="stylesheet" type="text/css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<div class="header clearfix">
|
||||
<nav>
|
||||
<ul class="nav nav-pills pull-right">
|
||||
<li role="presentation" class="active"><a href="#">Home</a></li>
|
||||
<li role="presentation"><a href="http://docs.africastalking.com" target="_blank">Docs</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<h3 class="text-muted">Africa's Talking</h3>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<div class="col-md-6">
|
||||
<pre><span id="response"></span></pre>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<p class="lead">Try clicking some buttons</p>
|
||||
<div class="col-sm-6 col-offset-sm-3">
|
||||
<input id="phone" type="text" class="form-control" placeholder="Your phone number">
|
||||
</div>
|
||||
<p><a class="btn btn-success btn-sm" role="button" id="signUp">Send SMS</a></p>
|
||||
<div class="col-sm-6 col-offset-sm-3">
|
||||
<input id="amount" type="text" class="form-control" placeholder="Amount e.g. USD 35">
|
||||
</div>
|
||||
<p><a class="btn btn-success btn-sm" role="button" id="airtime">Airtime</a></p>
|
||||
<div class="col-sm-6 col-offset-sm-3">
|
||||
<input id="mobileCheckoutAmount" type="text" class="form-control" placeholder="Amount e.g. KES 4456">
|
||||
</div>
|
||||
<p><a class="btn btn-success btn-sm" role="button" id="mobileCheckout">Mobile Checkout</a></p>
|
||||
<div class="col-sm-6 col-offset-sm-3">
|
||||
<input id="mobileB2CAmount" type="text" class="form-control" placeholder="Amount e.g. KES 4456">
|
||||
</div>
|
||||
<p><a class="btn btn-success btn-sm" role="button" id="mobileB2C">Mobile B2C</a></p>
|
||||
</div>
|
||||
|
||||
</div> <!-- /container -->
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/json.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/javascript.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/languages/xml.min.js"></script>
|
||||
<script src="public/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="./vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
|
||||
<testsuites>
|
||||
<testsuite name="AfricasTalking Test Suite">
|
||||
<directory suffix="Test.php">./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
class AfricasTalking
|
||||
{
|
||||
const BASE_DOMAIN = "africastalking.com";
|
||||
const BASE_SANDBOX_DOMAIN = "sandbox." . self::BASE_DOMAIN;
|
||||
|
||||
protected $username;
|
||||
protected $apiKey;
|
||||
|
||||
protected $client;
|
||||
protected $voiceClient;
|
||||
protected $tokenClient;
|
||||
protected $contentClient;
|
||||
protected $mobileDataClient;
|
||||
|
||||
public $baseUrl;
|
||||
protected $voiceUrl;
|
||||
protected $contentUrl;
|
||||
protected $mobileDataUrl;
|
||||
|
||||
public function __construct($username, $apiKey)
|
||||
{
|
||||
if($username === 'sandbox') {
|
||||
$this->baseDomain = self::BASE_SANDBOX_DOMAIN;
|
||||
} else {
|
||||
$this->baseDomain = self::BASE_DOMAIN;
|
||||
}
|
||||
|
||||
$this->baseUrl = "https://api." . $this->baseDomain . "/version1/";
|
||||
$this->voiceUrl = "https://voice." . $this->baseDomain . "/";
|
||||
$this->mobileDataUrl = "https://bundles." . $this->baseDomain . "/";
|
||||
$this->contentUrl = ($username === "sandbox") ? ($this->baseUrl) : ("https://content." . $this->baseDomain . "/version1/");
|
||||
$this->checkoutTokenUrl = "https://api." . $this->baseDomain . "/";
|
||||
|
||||
if ($username === 'sandbox') {
|
||||
$this->contentUrl = $this->baseUrl;
|
||||
}
|
||||
|
||||
$this->username = $username;
|
||||
$this->apiKey = $apiKey;
|
||||
|
||||
$this->client = new Client([
|
||||
'base_uri' => $this->baseUrl,
|
||||
'headers' => [
|
||||
'apikey' => $this->apiKey,
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'Accept' => 'application/json'
|
||||
]
|
||||
]);
|
||||
|
||||
$this->contentClient = new Client([
|
||||
'base_uri' => $this->contentUrl,
|
||||
'headers' => [
|
||||
'apikey' => $this->apiKey,
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'Accept' => 'application/json'
|
||||
]
|
||||
]);
|
||||
|
||||
$this->voiceClient = new Client([
|
||||
'base_uri' => $this->voiceUrl,
|
||||
'headers' => [
|
||||
'apikey' => $this->apiKey,
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'Accept' => 'application/json'
|
||||
]
|
||||
]);
|
||||
|
||||
$this->mobileDataClient = new Client([
|
||||
'base_uri' => $this->mobileDataUrl,
|
||||
'headers' => [
|
||||
'apikey' => $this->apiKey,
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json'
|
||||
]
|
||||
]);
|
||||
|
||||
$this->tokenClient = new Client([
|
||||
'base_uri' => $this->checkoutTokenUrl,
|
||||
'headers' => [
|
||||
'apikey' => $this->apiKey,
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function sms()
|
||||
{
|
||||
$content = new Content($this->contentClient, $this->username, $this->apiKey);
|
||||
$sms = new SMS($this->client, $this->username, $this->apiKey, $content);
|
||||
return $sms;
|
||||
}
|
||||
|
||||
public function content()
|
||||
{
|
||||
$content = new Content($this->contentClient, $this->username, $this->apiKey);
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function airtime()
|
||||
{
|
||||
$airtime = new Airtime($this->client, $this->username, $this->apiKey);
|
||||
return $airtime;
|
||||
}
|
||||
|
||||
public function voice()
|
||||
{
|
||||
$voice = new Voice($this->voiceClient, $this->username, $this->apiKey);
|
||||
return $voice;
|
||||
}
|
||||
|
||||
public function application()
|
||||
{
|
||||
$application = new Application($this->client, $this->username, $this->apiKey);
|
||||
return $application;
|
||||
}
|
||||
|
||||
public function mobileData()
|
||||
{
|
||||
$mobileData = new MobileData($this->mobileDataClient, $this->username, $this->apiKey);
|
||||
return $mobileData;
|
||||
}
|
||||
|
||||
public function token()
|
||||
{
|
||||
$token = new Token($this->tokenClient, $this->username, $this->apiKey);
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace AfricasTalking\SDK;
|
||||
|
||||
class Airtime extends Service
|
||||
{
|
||||
public function send($parameters, $options = [])
|
||||
{
|
||||
if(empty($parameters['recipients'])) {
|
||||
return $this->error("recipients must be specified");
|
||||
}
|
||||
|
||||
if(!is_array($parameters['recipients'])) {
|
||||
return $this->error("recipients must be an array");
|
||||
}
|
||||
|
||||
foreach ($parameters['recipients'] as $key=>$recipient){
|
||||
|
||||
if(!is_array($recipient)) {
|
||||
return $this->error("every recipient must be an array");
|
||||
}
|
||||
|
||||
if(!array_key_exists('phoneNumber', $recipient) || !array_key_exists('currencyCode', $recipient) || !array_key_exists('amount', $recipient)) {
|
||||
return $this->error("phoneNumber, currencyCode and amount must be specified for each recipient");
|
||||
}
|
||||
|
||||
$currencyCode = $recipient['currencyCode'];
|
||||
if (strlen($currencyCode) != 3) {
|
||||
return $this->error('currencyCode must be in 3-digit ISO format');
|
||||
}
|
||||
|
||||
$recipient['amount'] = $recipient['currencyCode'].' '.$recipient['amount'];
|
||||
|
||||
unset($recipient['currencyCode']);
|
||||
|
||||
$parameters['recipients'][$key] = $recipient;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'username' => $this->username,
|
||||
'recipients' => json_encode($parameters['recipients'])
|
||||
];
|
||||
|
||||
if (isset($options['maxNumRetry']) && is_numeric($options['maxNumRetry']) && $options['maxNumRetry'] > 0) {
|
||||
$data['maxNumRetry'] = $options['maxNumRetry'];
|
||||
}
|
||||
|
||||
$requestOptions = [
|
||||
'form_params' => $data,
|
||||
];
|
||||
|
||||
if(isset($options['idempotencyKey'])) {
|
||||
$requestOptions['headers'] = [
|
||||
'Idempotency-Key' => $options['idempotencyKey'],
|
||||
];
|
||||
}
|
||||
|
||||
$response = $this->client->post('airtime/send', $requestOptions);
|
||||
|
||||
return $this->success($response);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace AfricasTalking\SDK;
|
||||
|
||||
class Application extends Service
|
||||
{
|
||||
public function doFetchApplication()
|
||||
{
|
||||
$response = $this->client->get('user', ['query' => ['username'=> $this->username]]);
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
public function fetchApplicationData()
|
||||
{
|
||||
return $this->doFetchApplication();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace AfricasTalking\SDK;
|
||||
|
||||
class Content extends Service
|
||||
{
|
||||
public function send ($options)
|
||||
{
|
||||
if (empty($options['to']) || empty($options['message'])) {
|
||||
return $this->error('recipient and message must be defined');
|
||||
}
|
||||
|
||||
if (!is_array($options['to'])) {
|
||||
$options['to'] = [$options['to']];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'username' => $this->username,
|
||||
'to' => implode(",", $options['to']),
|
||||
'message' => $options['message']
|
||||
];
|
||||
|
||||
if (array_key_exists('enqueue', $options) && $options['enqueue']) {
|
||||
$data['enqueue'] = 1;
|
||||
}
|
||||
|
||||
if (empty($options['from'])) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'data' => 'from is required for premium SMS'
|
||||
];
|
||||
} else {
|
||||
$data['from'] = $options['from'];
|
||||
}
|
||||
|
||||
if (!empty($options['keyword'])) {
|
||||
$data['keyword'] = $options['keyword'];
|
||||
}
|
||||
|
||||
if (!empty($options['linkId'])) {
|
||||
$data['linkId'] = $options['linkId'];
|
||||
}
|
||||
|
||||
if (!empty($options['retryDurationInHours'])) {
|
||||
$data['retryDurationInHours'] = $options['retryDurationInHours'];
|
||||
}
|
||||
|
||||
// turn off bulk sms mode
|
||||
$data['bulkSMSMode'] = 0;
|
||||
|
||||
$response = $this->client->post('messaging', ['form_params' => $data ]);
|
||||
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
public function createSubscription ($options)
|
||||
{
|
||||
if (empty($options['phoneNumber']) ||
|
||||
empty($options['shortCode']) ||
|
||||
empty($options['keyword'])) {
|
||||
return $this->error("phoneNumber, shortCode and keyword must be specified");
|
||||
}
|
||||
|
||||
$data = [
|
||||
'username' => $this->username,
|
||||
'phoneNumber' => $options['phoneNumber'],
|
||||
'shortCode' => $options['shortCode'],
|
||||
'keyword' => $options['keyword']
|
||||
];
|
||||
|
||||
$response = $this->client->post('subscription/create', ['form_params' => $data ] );
|
||||
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
public function deleteSubscription ($options)
|
||||
{
|
||||
if (empty($options['phoneNumber']) ||
|
||||
empty($options['shortCode']) ||
|
||||
empty($options['keyword'])) {
|
||||
return $this->error("phoneNumber, shortCode and keyword must be specified");
|
||||
}
|
||||
|
||||
$data = [
|
||||
'username' => $this->username,
|
||||
'phoneNumber' => $options['phoneNumber'],
|
||||
'shortCode' => $options['shortCode'],
|
||||
'keyword' => $options['keyword']
|
||||
];
|
||||
|
||||
$response = $this->client->post('subscription/delete', ['form_params' => $data ] );
|
||||
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
public function fetchSubscriptions($options)
|
||||
{
|
||||
if(empty($options['shortCode']) || empty($options['keyword'])) {
|
||||
return $this->error("shortCode and keyword must be specified");
|
||||
}
|
||||
|
||||
if (empty($options['lastReceivedId'])) {
|
||||
$options['lastReceivedId'] = 0;
|
||||
}
|
||||
|
||||
if (!is_numeric($options['lastReceivedId'])) {
|
||||
return $this->error('lastReceivedId must be an integer');
|
||||
}
|
||||
|
||||
$data = [
|
||||
'username' => $this->username,
|
||||
'lastReceivedId' => $options['lastReceivedId'],
|
||||
'shortCode' => $options['shortCode'],
|
||||
'keyword' => $options['keyword']
|
||||
];
|
||||
|
||||
$response = $this->client->get('subscription', ['query' => $data ] );
|
||||
|
||||
return $this->success($response);
|
||||
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK;
|
||||
|
||||
class MobileData extends Service
|
||||
{
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
// First check if method exists
|
||||
if (method_exists($this, 'do' . $method)) {
|
||||
$func = 'do' . $method;
|
||||
if (!isset($args[0])) {
|
||||
$args = [ 0 => ''];
|
||||
}
|
||||
return $this->$func($args[0]);
|
||||
} else {
|
||||
return $this->error($method .' is an invalid Mobile Data SDK Method');
|
||||
}
|
||||
}
|
||||
|
||||
protected function doSend($parameters, $options = [])
|
||||
{
|
||||
// Check if productName is set
|
||||
if (!isset($parameters['productName'])) {
|
||||
return $this->error('productName must be defined');
|
||||
}
|
||||
$productName = $parameters['productName'];
|
||||
|
||||
// Check if recipients array is provided
|
||||
if (!isset($parameters['recipients'])) {
|
||||
return $this->error('recipients must be an array containing phoneNumber, unit, quatity, validity and metadata');
|
||||
} else if (isset($parameters['recipients']) && is_array($parameters['recipients'])) {
|
||||
$recipients = $parameters['recipients'];
|
||||
|
||||
foreach ($recipients as $r) {
|
||||
if (!isset($r['phoneNumber']) ||
|
||||
!isset($r['quantity']) ||
|
||||
!isset($r['unit']) ||
|
||||
!isset($r['validity']) ||
|
||||
!isset($r['metadata'])) {
|
||||
|
||||
return $this->error('recipients must be an array containing phoneNumber, quantity, unit, validity and metadata');
|
||||
}
|
||||
|
||||
if (isset($r['validity'])) {
|
||||
if (!in_array($r['validity'], ['Day', 'Month', 'Week'])) {
|
||||
return $this->error('validity must be one of Day, Week, Month');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($r['unit'])) {
|
||||
if (!in_array($r['unit'], ['MB', 'GB'])) {
|
||||
return $this->error('unit must be one of MB, GB');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make request data array
|
||||
$requestData = [
|
||||
'username' => $this->username,
|
||||
'productName' => $productName,
|
||||
'recipients' => $recipients,
|
||||
];
|
||||
|
||||
if (isset($parameters['isPromoBundles'])) {
|
||||
$requestData['isPromoBundle'] = $parameters['isPromoBundles'];
|
||||
}
|
||||
|
||||
$requestOptions = [
|
||||
'json' => $requestData,
|
||||
];
|
||||
|
||||
if(isset($options['idempotencyKey'])) {
|
||||
$requestOptions['headers'] = [
|
||||
'Idempotency-Key' => $options['idempotencyKey'],
|
||||
];
|
||||
}
|
||||
|
||||
$response = $this->client->post('mobile/data/request', $requestOptions);
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
protected function doFindTransaction($options)
|
||||
{
|
||||
if (!isset($options['transactionId'])) {
|
||||
return $this->error('transactionId must be defined');
|
||||
}
|
||||
|
||||
$requestData = [
|
||||
'username' => $this->username,
|
||||
'transactionId' => $options['transactionId']
|
||||
];
|
||||
|
||||
$response = $this->client->get('query/transaction/find', ['query' => $requestData]);
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
protected function doFetchWalletBalance()
|
||||
{
|
||||
$requestData = [
|
||||
'username' => $this->username
|
||||
];
|
||||
|
||||
$response = $this->client->get('query/wallet/balance', ['query' => $requestData]);
|
||||
return $this->success($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK;
|
||||
|
||||
class SMS extends Service
|
||||
{
|
||||
protected $content;
|
||||
|
||||
public function __construct($client, $username, $apiKey, $content)
|
||||
{
|
||||
parent::__construct($client, $username, $apiKey);
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public function send($options)
|
||||
{
|
||||
if (empty($options['to']) || empty($options['message'])) {
|
||||
return $this->error('recipient and message must be defined');
|
||||
}
|
||||
|
||||
if (!is_array($options['to'])) {
|
||||
$options['to'] = [$options['to']];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'username' => $this->username,
|
||||
'to' => implode(",", $options['to']),
|
||||
'message' => $options['message']
|
||||
];
|
||||
|
||||
if (array_key_exists('enqueue', $options) && $options['enqueue']) {
|
||||
$data['enqueue'] = 1;
|
||||
}
|
||||
|
||||
if (!empty($options['from'])) {
|
||||
$data['from'] = $options['from'];
|
||||
}
|
||||
|
||||
$response = $this->client->post('messaging', ['form_params' => $data ]);
|
||||
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
public function fetchMessages($options = [])
|
||||
{
|
||||
if (empty($options['lastReceivedId'])) {
|
||||
$options['lastReceivedId'] = 0;
|
||||
}
|
||||
|
||||
if (!is_numeric($options['lastReceivedId'])) {
|
||||
return $this->error('lastReceivedId must be an integer');
|
||||
}
|
||||
|
||||
$data = [
|
||||
'username' => $this->username,
|
||||
'lastReceivedId' => $options['lastReceivedId']
|
||||
];
|
||||
|
||||
$response = $this->client->get('messaging', ['query' => $data ] );
|
||||
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
public function sendPremium($options)
|
||||
{
|
||||
return $this->content->send($options);
|
||||
}
|
||||
|
||||
public function createSubscription ($options)
|
||||
{
|
||||
return $this->content->createSubscription($options);
|
||||
}
|
||||
|
||||
public function deleteSubscription ($options)
|
||||
{
|
||||
return $this->content->deleteSubscription($options);
|
||||
}
|
||||
|
||||
public function fetchSubscriptions($options)
|
||||
{
|
||||
return $this->content->fetchSubscriptions($options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK;
|
||||
|
||||
abstract class Service
|
||||
{
|
||||
protected $client;
|
||||
|
||||
protected $username;
|
||||
|
||||
protected $apiKey;
|
||||
|
||||
public function __construct($client, $username, $apiKey)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->username = $username;
|
||||
$this->apiKey = $apiKey;
|
||||
}
|
||||
|
||||
protected static function error($data) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'data' => $data
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
protected static function success($data) {
|
||||
return [
|
||||
'status' => 'success',
|
||||
'data' => json_decode($data->getBody()->getContents())
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace AfricasTalking\SDK;
|
||||
|
||||
class Token extends Service
|
||||
{
|
||||
public function generateAuthToken()
|
||||
{
|
||||
$requestData = json_encode(['username' => $this->username]);
|
||||
$response = $this->client->post('auth-token/generate', ['body' => $requestData ] );
|
||||
return $this->success($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
<?php
|
||||
|
||||
namespace AfricasTalking\SDK;
|
||||
|
||||
class Voice extends Service
|
||||
{
|
||||
private $xmlString;
|
||||
|
||||
public function __call($method, $args)
|
||||
{
|
||||
// First check if method exists
|
||||
if (method_exists($this, 'build' . $method)) {
|
||||
if (!isset($args[0])) {
|
||||
$args = [ 0 => ''];
|
||||
}
|
||||
return $this->stringBuilder('build'. $method, $args[0]);
|
||||
} else if (method_exists($this, 'do' . $method)) {
|
||||
if (!isset($args[0])) {
|
||||
$args = [ 0 => ''];
|
||||
}
|
||||
return $this->apiCall('do' . $method, $args[0]);
|
||||
} else {
|
||||
return $this->error($method .' is an invalid Voice SDK Method');
|
||||
}
|
||||
}
|
||||
|
||||
public function messageBuilder()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function stringBuilder($method, $args)
|
||||
{
|
||||
$result = $this->$method($args);
|
||||
|
||||
if (empty($this->xmlString)) {
|
||||
$this->xmlString = '<?xml version="1.0" encoding="UTF-8"?><Response>'. $result;
|
||||
} else {
|
||||
$this->xmlString .= $result;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function apiCall($method, $args)
|
||||
{
|
||||
return $this->$method($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds XML string from chained voice actions
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
if (empty($this->xmlString)) {
|
||||
return null;
|
||||
}
|
||||
return $this->xmlString . '</Response>';
|
||||
}
|
||||
|
||||
protected function doCall($options)
|
||||
{
|
||||
if (!isset($options['to']) || !isset($options['from'])) {
|
||||
return $this->error('The parameters to and from must be defined');
|
||||
}
|
||||
|
||||
// Validate callTo
|
||||
$checkCallTo = strpos($options['to'], '+');
|
||||
if ($checkCallTo === false || $checkCallTo !== 0) {
|
||||
return $this->error('callTo must be in the format \'+2XXYYYYYYYYY\'');
|
||||
}
|
||||
|
||||
// Validate callFrom
|
||||
$checkCallFrom = strpos($options['from'], '+');
|
||||
if ($checkCallFrom === false || $checkCallFrom !== 0) {
|
||||
return $this->error('callFrom must be in the format \'+2XXYYYYYYYYY\'');
|
||||
}
|
||||
|
||||
$requestData = [
|
||||
'username' => $this->username,
|
||||
'to' => $options['to'],
|
||||
'from' => $options['from']
|
||||
];
|
||||
|
||||
if (isset($options['clientRequestId']) && !empty($options['clientRequestId'])) {
|
||||
$requestData['clientRequestId'] = (string) $options['clientRequestId'];
|
||||
}
|
||||
|
||||
$response = $this->client->post('call', ['form_params' => $requestData ] );
|
||||
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
protected function doUploadMediaFile($options)
|
||||
{
|
||||
// Check and validate phoneNumber
|
||||
if (!isset($options['phoneNumber'])) {
|
||||
return $this->error('Phone number is required and must be in the format \'+2XXYYYYYYYYY\'');
|
||||
}
|
||||
$phoneNumber = $options['phoneNumber'];
|
||||
|
||||
$checkPhoneNumber = strpos($phoneNumber, '+');
|
||||
if ($checkPhoneNumber === false || $checkPhoneNumber != 0) {
|
||||
return $this->error('Phone number must be in the format \'+2XXYYYYYYYYY\'');
|
||||
}
|
||||
|
||||
if (!isset($options['url'])) {
|
||||
return $this->error('url must be defined');
|
||||
}
|
||||
$url = $options['url'];
|
||||
|
||||
// Check if valid URL passed
|
||||
if (filter_var($url, FILTER_VALIDATE_URL) === false) {
|
||||
return $this->error('URL not valid');
|
||||
}
|
||||
|
||||
$requestData = [
|
||||
'username' => $this->username,
|
||||
'phoneNumber' => $phoneNumber,
|
||||
'url' => $url
|
||||
];
|
||||
|
||||
$response = $this->client->post('mediaUpload', ['form_params' => $requestData]);
|
||||
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
protected function dofetchQueuedCalls($options)
|
||||
{
|
||||
// Check and validate phoneNumber
|
||||
if (!isset($options['phoneNumber'])) {
|
||||
return $this->error('Phone number is required and must be in the format \'+2XXYYYYYYYYY\'');
|
||||
}
|
||||
$phoneNumber = $options['phoneNumber'];
|
||||
|
||||
$checkPhoneNumber = strpos($phoneNumber, '+');
|
||||
if ($checkPhoneNumber === false || $checkPhoneNumber != 0) {
|
||||
return $this->error('Phone number must be in the format \'+2XXYYYYYYYYY\'');
|
||||
}
|
||||
|
||||
$requestData = [
|
||||
'username' => $this->username,
|
||||
'phoneNumbers' => $phoneNumber
|
||||
];
|
||||
|
||||
if(isset($options['name'])) {
|
||||
$requestData['name'] = $options['name'];
|
||||
}
|
||||
|
||||
$response = $this->client->post('queueStatus', ['form_params' => $requestData]);
|
||||
return $this->success($response);
|
||||
}
|
||||
|
||||
protected function buildSay($options)
|
||||
{
|
||||
|
||||
if (is_string($options)) {
|
||||
return "<Say>$options</Say>";
|
||||
}
|
||||
|
||||
// Check for text
|
||||
if (!isset($options['text'])) {
|
||||
return $this->error('Please set text to be read out');
|
||||
}
|
||||
$text = $options['text'];
|
||||
|
||||
// Check if read out voice has been set
|
||||
if (isset($options['voice'])) {
|
||||
$voice = $options['voice'];
|
||||
}
|
||||
|
||||
// Check if playBeep option has been set
|
||||
if (isset($options['playBeep'])) {
|
||||
$playBeep = $options['playBeep'];
|
||||
}
|
||||
|
||||
if (isset($options['voice']) && isset($options['playBeep'])) {
|
||||
$sayString = '<Say voice="' . $voice . '" playBeep="'. $playBeep .'">'. $text .'</Say>';
|
||||
} else if (isset($options['voice'])) {
|
||||
$sayString = '<Say voice="' . $voice . '">'. $text .'</Say>';
|
||||
} else if (isset($options['playBeep'])) {
|
||||
$sayString = '<Say playBeep="'. $playBeep .'">'. $text .'</Say>';
|
||||
} else {
|
||||
$sayString = "<Say>$text</Say>";
|
||||
}
|
||||
|
||||
return $sayString;
|
||||
}
|
||||
|
||||
protected function buildPlay($url)
|
||||
{
|
||||
if (!$this->isValidURL($url))
|
||||
return $this->error('Play URL is not valid');
|
||||
|
||||
$playString = '<Play url="'. $url . '"/>';
|
||||
|
||||
return $playString;
|
||||
}
|
||||
|
||||
protected function buildGetDigits($options) {
|
||||
|
||||
// Check for text
|
||||
if (!isset($options['text'])) {
|
||||
return $this->error('Please set text to be read out');
|
||||
}
|
||||
$text = $options['text'];
|
||||
|
||||
// Check if URL is set
|
||||
if (isset($options['url'])) {
|
||||
$url = $options['url'];
|
||||
}
|
||||
|
||||
// Get number of digits
|
||||
if(isset($options['numDigits'])) {
|
||||
$numDigits = $options['numDigits'];
|
||||
if (!is_numeric($numDigits)) {
|
||||
return $this->error('Please set a number value for the timeout');
|
||||
}
|
||||
}
|
||||
|
||||
// Get timeout
|
||||
if(isset($options['timeout'])) {
|
||||
$timeout = $options['timeout'];
|
||||
if (!is_numeric($timeout)) {
|
||||
return $this->error('Please set a number value for the timeout');
|
||||
}
|
||||
}
|
||||
|
||||
// Get finishOnKey
|
||||
$finishOnKey = $options['finishOnKey'];
|
||||
|
||||
// Get callbackURL
|
||||
if (isset($options['callBackUrl'])) {
|
||||
$callBackUrl = $options['callBackUrl'];
|
||||
if (!$this->isValidURL($callBackUrl)) {
|
||||
return $this->error('Please set a valid callback URL');
|
||||
}
|
||||
}
|
||||
|
||||
// -- NOW TO BUILD STRING
|
||||
|
||||
// Build opening tag
|
||||
$getDigitsString = '<GetDigits ';
|
||||
if (!empty($finishOnKey)) {
|
||||
$getDigitsString .= ' finishOnKey="'. $finishOnKey .'"';
|
||||
}
|
||||
if (!empty($timeout)) {
|
||||
$getDigitsString .= ' timeout="'. $timeout .'"';
|
||||
}
|
||||
if (!empty($numDigits)) {
|
||||
$getDigitsString .= ' numDigits="'. $numDigits .'"';
|
||||
}
|
||||
if (!empty($callBackUrl)) {
|
||||
$getDigitsString .= ' callBackUrl="'. $callBackUrl .'"';
|
||||
}
|
||||
$getDigitsString .= '>';
|
||||
|
||||
// ... add child element
|
||||
if (!empty($text)) {
|
||||
$getDigitsString .= $this->buildSay($text);
|
||||
}
|
||||
|
||||
if (!empty($url)) {
|
||||
$getDigitsString .= $this->buildPlay($url);
|
||||
}
|
||||
|
||||
$getDigitsString .= '</GetDigits>';
|
||||
|
||||
return $getDigitsString;
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected function buildDial($options)
|
||||
{
|
||||
|
||||
// Validate phoneNumber
|
||||
if (!isset($options['phoneNumbers'])) {
|
||||
return $this->error('Please specifiy at least one number to dial');
|
||||
}
|
||||
$phoneNumbers = implode(",", $options["phoneNumbers"]);
|
||||
|
||||
// Check if ringback tone is set
|
||||
if (isset($options['ringbackTone'])) {
|
||||
if (!$this->isValidURL($options['ringbackTone'])) {
|
||||
return $this->error('ringbackTone not a valid URL');
|
||||
}
|
||||
$ringbackTone = $options['ringbackTone'];
|
||||
}
|
||||
|
||||
// Check if record is set
|
||||
if (!isset($options['record']) || !is_bool($options['record'])) {
|
||||
$record = false;
|
||||
} else {
|
||||
$record = $options['record'];
|
||||
}
|
||||
// change record to true or false string
|
||||
$record ? $record = "true" : $record ="false";
|
||||
|
||||
// Check if sequential
|
||||
if (!isset($options['sequential']) || !is_bool($options['sequential'])) {
|
||||
$sequential = false;
|
||||
} else {
|
||||
$sequential = $options['sequential'];
|
||||
}
|
||||
// change sequential to true or false string
|
||||
$sequential ? $sequential = "true" : $sequential ="false";
|
||||
|
||||
// Check if callerId is set
|
||||
|
||||
if (!isset($options['callerId']) || !($options['callerId'])) {
|
||||
$callerId = false;
|
||||
} else {
|
||||
$callerId = $options['callerId'];
|
||||
}
|
||||
// change callerId to number provided or false string
|
||||
|
||||
$callerId ? $callerId = $callerId : $callerId ="false";
|
||||
|
||||
// Check if maxDuration is set
|
||||
if(isset($options['maxDuration'])) {
|
||||
$maxDuration = $options['maxDuration'];
|
||||
if (!is_integer($maxDuration) || $maxDuration < 0) {
|
||||
return $this->error('Max duration must be an integer value');
|
||||
}
|
||||
}
|
||||
|
||||
$dialString = '<Dial phoneNumbers="'. $phoneNumbers . '"';
|
||||
if (!empty($record)) {
|
||||
$dialString .= ' record="'. $record .'"';
|
||||
}
|
||||
if (!empty($sequential)) {
|
||||
$dialString .= ' sequential="'. $sequential .'"';
|
||||
}
|
||||
if (!empty($callerId)) {
|
||||
$dialString .= ' callerId="'. $callerId .'"';
|
||||
}
|
||||
if (!empty($ringbackTone)) {
|
||||
$dialString .= ' ringbackTone="'. $ringbackTone .'"';
|
||||
}
|
||||
if (!empty($maxDuration)) {
|
||||
$dialString .= ' maxDuration="'. $maxDuration .'"';
|
||||
}
|
||||
$dialString .= ' />';
|
||||
|
||||
return $dialString;
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected function buildRecord($options)
|
||||
{
|
||||
|
||||
/** Terminal Recording **/
|
||||
|
||||
if (empty($options)) {
|
||||
return '<Record />';
|
||||
}
|
||||
|
||||
/** Partial Recording **/
|
||||
|
||||
// Get finishOnKey
|
||||
$finishOnKey = $options['finishOnKey'];
|
||||
|
||||
// Get Max Length
|
||||
if (isset($options['maxLength'])) {
|
||||
$maxLength = $options['maxLength'];
|
||||
if (!is_numeric($maxLength)) {
|
||||
return $this->error('Please set a number value for the timeout');
|
||||
}
|
||||
}
|
||||
|
||||
// Get timeout
|
||||
if (isset($options['timeout'])) {
|
||||
$timeout = $options['timeout'];
|
||||
if (!is_numeric($timeout)) {
|
||||
return $this->error('Please set a number value for the timeout');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if trimSilence is set
|
||||
if (!isset($options['trimSilence']) || !is_bool($options['trimSilence'])) {
|
||||
$trimSilence = false;
|
||||
} else {
|
||||
$trimSilence = $options['trimSilence'];
|
||||
}
|
||||
|
||||
|
||||
// change trimSilence to true or false string
|
||||
$trimSilence ? $trimSilence = "true" : $trimSilence ="false";
|
||||
|
||||
// Check if playBeep option has been set
|
||||
if (!isset($options['playBeep']) || !is_bool($options['playBeep'])) {
|
||||
$playBeep = false;
|
||||
} else {
|
||||
$playBeep = $options['playBeep'];
|
||||
}
|
||||
// change playBeep to true or false string
|
||||
$playBeep ? $playBeep = "true" : $playBeep ="false";
|
||||
|
||||
// Get callbackURL
|
||||
if (isset($options['callBackUrl'])) {
|
||||
$callBackUrl = $options['callBackUrl'];
|
||||
if (!$this->isValidURL($callBackUrl)) {
|
||||
return $this->error('Please set a valid callback URL');
|
||||
}
|
||||
}
|
||||
|
||||
// Build opening tag
|
||||
$recordString = '<Record';
|
||||
if (!empty($finishOnKey)) {
|
||||
$recordString .= ' finishOnKey="'. $finishOnKey .'"';
|
||||
}
|
||||
if (!empty($maxLength)) {
|
||||
$recordString .= ' maxLength="'. $maxLength .'"';
|
||||
}
|
||||
if (!empty($timeout)) {
|
||||
$recordString .= ' timeout="'. $timeout .'"';
|
||||
}
|
||||
if (!empty($trimSilence)) {
|
||||
$recordString .= ' trimSilence="'. $trimSilence .'"';
|
||||
}
|
||||
if (!empty($playBeep)) {
|
||||
$playBeep .= ' playBeep="'. $playBeep .'"';
|
||||
}
|
||||
if (!empty($callBackUrl)) {
|
||||
$getDigitsString .= ' callBackUrl="'. $callBackUrl .'"';
|
||||
}
|
||||
$recordString .= ' />';
|
||||
|
||||
return $recordString;
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected function buildEnqueue($options)
|
||||
{
|
||||
// Check if holdMusic option has been set
|
||||
if (isset($options['holdMusic'])) {
|
||||
$holdMusic = $options['holdMusic'];
|
||||
if (!$this->isValidURL($holdMusic)) {
|
||||
return $this->error('Please set a valid URL value for holdMusic');
|
||||
}
|
||||
}
|
||||
|
||||
// Build opening tag
|
||||
$enqueueString = '<Enqueue';
|
||||
if (!empty($holdMusic)) {
|
||||
$enqueueString .= ' holdMusic="'. $holdMusic .'"';
|
||||
}
|
||||
|
||||
if (isset($options['name'])) {
|
||||
$name = $options['name'];
|
||||
$enqueueString .= ' name="'. $name .'"';
|
||||
}
|
||||
$enqueueString .= ' />';
|
||||
|
||||
return $enqueueString;
|
||||
|
||||
}
|
||||
|
||||
protected function buildDequeue($options)
|
||||
{
|
||||
// Check if holdMusic option has been set
|
||||
if (!isset($options['phoneNumber'])) {
|
||||
return $this->error('Please enter a valid phone number');
|
||||
}
|
||||
$phoneNumber = $options['phoneNumber'];
|
||||
|
||||
|
||||
// Build opening tag
|
||||
$dequeueString = '<Dequeue phoneNumber="'. $phoneNumber . '"';
|
||||
if (isset($options['name'])) {
|
||||
$name = $options['name'];
|
||||
$dequeueString .= ' name="'. $name .'"';
|
||||
}
|
||||
$dequeueString .= ' />';
|
||||
|
||||
return $dequeueString;
|
||||
|
||||
}
|
||||
|
||||
protected function buildConference()
|
||||
{
|
||||
return '<Conference />';
|
||||
}
|
||||
|
||||
protected function buildRedirect($url)
|
||||
{
|
||||
return '<Redirect>'. $url.'</Redirect>';
|
||||
}
|
||||
|
||||
protected function buildReject($url)
|
||||
{
|
||||
return '<Reject />';
|
||||
}
|
||||
|
||||
private function isValidURL($url)
|
||||
{
|
||||
// If no url passed then it can't be valid
|
||||
if (empty($url)) {
|
||||
return false;
|
||||
}
|
||||
return !filter_var($url, FILTER_VALIDATE_URL) === false;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK\Tests;
|
||||
|
||||
use AfricasTalking\SDK\AfricasTalking;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
class AfricasTalkingTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->username = Fixtures::$username;
|
||||
$this->apiKey = Fixtures::$apiKey;
|
||||
|
||||
$this->client = new AfricasTalking($this->username, $this->apiKey);
|
||||
}
|
||||
|
||||
public function testSMSClass()
|
||||
{
|
||||
$this->assertInstanceOf(\AfricasTalking\SDK\SMS::class, $this->client->sms());
|
||||
}
|
||||
|
||||
public function testContentClass()
|
||||
{
|
||||
$this->assertInstanceOf(\AfricasTalking\SDK\Content::class, $this->client->content());
|
||||
}
|
||||
|
||||
public function testAirtimeClass()
|
||||
{
|
||||
$this->assertInstanceOf(\AfricasTalking\SDK\Airtime::class, $this->client->airtime());
|
||||
}
|
||||
|
||||
public function testVoiceClass()
|
||||
{
|
||||
$this->assertInstanceOf(\AfricasTalking\SDK\Voice::class, $this->client->voice());
|
||||
}
|
||||
|
||||
public function testApplicationClass()
|
||||
{
|
||||
$this->assertInstanceOf(\AfricasTalking\SDK\Application::class, $this->client->application());
|
||||
}
|
||||
|
||||
public function testMobileDataClass()
|
||||
{
|
||||
$this->assertInstanceOf(\AfricasTalking\SDK\MobileData::class, $this->client->mobileData());
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK\Tests;
|
||||
|
||||
use AfricasTalking\SDK\AfricasTalking;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
class AirtimeTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function setup(): void
|
||||
{
|
||||
$this->username = Fixtures::$username;
|
||||
$this->apiKey = Fixtures::$apiKey;
|
||||
|
||||
$at = new AfricasTalking($this->username, $this->apiKey);
|
||||
|
||||
$this->client = $at->airtime();
|
||||
}
|
||||
|
||||
public function testSendAirtimeToOne()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'recipients' => [[
|
||||
'phoneNumber' => Fixtures::$phoneNumber,
|
||||
'currencyCode' => Fixtures::$currencyCode,
|
||||
'amount' => Fixtures::$amount
|
||||
]]
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('responses', $response['data']);
|
||||
}
|
||||
|
||||
public function testSendAirtimeIdempotency()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'recipients' => [[
|
||||
'phoneNumber' => Fixtures::$phoneNumber,
|
||||
'currencyCode' => Fixtures::$currencyCode,
|
||||
'amount' => Fixtures::$amount
|
||||
]]
|
||||
], [
|
||||
'idempotencyKey' => 'req-' . mt_rand(10, 100),
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('responses', $response['data']);
|
||||
}
|
||||
|
||||
public function testSendAirtimeToMany()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'recipients' => [[
|
||||
'phoneNumber' => Fixtures::$phoneNumber,
|
||||
'currencyCode' => Fixtures::$currencyCode,
|
||||
'amount' => Fixtures::$amount
|
||||
], [
|
||||
'phoneNumber' => '+2347038151149',
|
||||
'currencyCode' => 'NGN',
|
||||
'amount' => '10000'
|
||||
]]
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('responses', $response['data']);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK\Tests;
|
||||
|
||||
use AfricasTalking\SDK\AfricasTalking;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
class ApplicationTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->username = Fixtures::$username;
|
||||
$this->apiKey = Fixtures::$apiKey;
|
||||
|
||||
$at = new AfricasTalking($this->username, $this->apiKey);
|
||||
|
||||
$this->client = $at->application();
|
||||
}
|
||||
|
||||
public function testFetchAplication()
|
||||
{
|
||||
$response = $this->client->fetchApplicationData();
|
||||
$this->assertObjectHasProperty('UserData', $response['data']);
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK\Tests;
|
||||
|
||||
use AfricasTalking\SDK\AfricasTalking;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
class ContentTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->username = Fixtures::$username;
|
||||
$this->apiKey = Fixtures::$apiKey;
|
||||
|
||||
$at = new AfricasTalking($this->username, $this->apiKey);
|
||||
|
||||
$this->client = $at->content();
|
||||
$this->tokenClient = $at->token();
|
||||
}
|
||||
|
||||
public function send()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'to' => Fixtures::$multiplePhoneNumbersSMS,
|
||||
'linkId' => 'messageLinkId',
|
||||
'keyword' => Fixtures::$keyword,
|
||||
'from' => Fixtures::$shortCode,
|
||||
'message' => 'Testing Premium...'
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('SMSMessageData', $response['data']);
|
||||
}
|
||||
|
||||
public function testCreateSubscription()
|
||||
{
|
||||
$response = $this->client->createSubscription([
|
||||
'phoneNumber' => Fixtures::$phoneNumber,
|
||||
'shortCode' => Fixtures::$shortCode,
|
||||
'keyword' => Fixtures::$keyword,
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('status',$response);
|
||||
$this->assertEquals('success',$response['status']);
|
||||
}
|
||||
|
||||
public function testDeleteSubscription()
|
||||
{
|
||||
$response = $this->client->deleteSubscription([
|
||||
'phoneNumber' => Fixtures::$phoneNumber,
|
||||
'shortCode' => Fixtures::$shortCode,
|
||||
'keyword' => Fixtures::$keyword
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('status',$response);
|
||||
$this->assertEquals('success',$response['status']);
|
||||
}
|
||||
|
||||
public function testFetchSubscriptions()
|
||||
{
|
||||
$response = $this->client->fetchSubscriptions([
|
||||
'shortCode' => Fixtures::$shortCode,
|
||||
'keyword' => Fixtures::$keyword
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('responses', $response['data']);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK\Tests;
|
||||
|
||||
class Fixtures
|
||||
{
|
||||
public static $username = 'sandbox';
|
||||
public static $apiKey = '';
|
||||
public static $dateOfBirth = '';
|
||||
public static $accountName = 'Test Bank Account';
|
||||
public static $accountNumber = '0123456789';
|
||||
public static $phoneNumber = '+254724486439';
|
||||
public static $multiplePhoneNumbersSMS = ['+254724486439', '+254724567567', '+254724567569'];
|
||||
public static $voicePhoneNumber = '+254711082489';
|
||||
public static $voicePhoneNumber2 = '+254724486439';
|
||||
public static $narration = 'Test Payment';
|
||||
public static $shortCode = '';
|
||||
public static $keyword = 'Test';
|
||||
public static $alphanumeric = 'Test';
|
||||
public static $targetProductCode = 1411;
|
||||
public static $mediaUrl = 'http://thesoundeffect.com/music/mp3/AintTooProudToBeg.mp3';
|
||||
public static $amount = '60';
|
||||
public static $currencyCode = 'KES';
|
||||
public static $currencyCode2 = 'NGN';
|
||||
public static $productName = 'TestProduct';
|
||||
public static $provider = 'ATHENA';
|
||||
public static $transferType = '';
|
||||
public static $startDate = '2018-01-01';
|
||||
public static $endDate = '2018-12-30';
|
||||
public static $transactionId = 'ATPid_SampleTxnId1';
|
||||
public static $otp = '1234';
|
||||
|
||||
public static $MobileDataRecipients = array([
|
||||
'phoneNumber' => '25471xxxxxxx',
|
||||
'quantity' => 15,
|
||||
'unit' => 'MB',
|
||||
'validity' => 'Day',
|
||||
'metadata' => ['notes' => 'Data for January 2018']
|
||||
]);
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK\Tests;
|
||||
|
||||
use AfricasTalking\SDK\AfricasTalking;
|
||||
use AfricasTalking\SDK\MobileData;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
class MobileDataTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->username = Fixtures::$username;
|
||||
$this->apiKey = Fixtures::$apiKey;
|
||||
|
||||
$at = new AfricasTalking($this->username, $this->apiKey);
|
||||
|
||||
$this->client = $at->mobileData();
|
||||
}
|
||||
|
||||
|
||||
public function testSend()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'productName' => Fixtures::$productName,
|
||||
'recipients' => Fixtures::$MobileDataRecipients,
|
||||
]);
|
||||
$this->assertArrayHasKey('status', $response);
|
||||
}
|
||||
|
||||
public function testFindTransaction()
|
||||
{
|
||||
$response = $this->client->findTransaction([
|
||||
'transactionId' => Fixtures::$transactionId
|
||||
]);
|
||||
$this->assertEquals('Failure', $response['data']->status);
|
||||
}
|
||||
|
||||
public function testFetchWalletBalance()
|
||||
{
|
||||
$response = $this->client->fetchWalletBalance();
|
||||
$this->assertEquals('Success', $response['data']->status);
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK\Tests;
|
||||
|
||||
use AfricasTalking\SDK\AfricasTalking;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
class SMSTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->username = Fixtures::$username;
|
||||
$this->apiKey = Fixtures::$apiKey;
|
||||
|
||||
$at = new AfricasTalking($this->username, $this->apiKey);
|
||||
|
||||
$this->client = $at->sms();
|
||||
$this->tokenClient = $at->token();
|
||||
}
|
||||
|
||||
public function testSMSWithEmptyMessage()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'to' => Fixtures::$multiplePhoneNumbersSMS,
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('status',$response);
|
||||
$this->assertEquals('error',$response['status']);
|
||||
}
|
||||
|
||||
public function testSMSWithEmptyRecipient()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'message' => 'Testing...'
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('status',$response);
|
||||
$this->assertEquals('error',$response['status']);
|
||||
}
|
||||
|
||||
public function testSingleSMSSending()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'to' => Fixtures::$phoneNumber,
|
||||
'message' => 'Testing SMS...'
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('SMSMessageData', $response['data']);
|
||||
}
|
||||
|
||||
public function testMultipleSMSSending()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'to' => Fixtures::$multiplePhoneNumbersSMS,
|
||||
'message' => 'Testing multiple sending...'
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('SMSMessageData', $response['data']);
|
||||
}
|
||||
|
||||
public function testSMSSendingWithShortcode()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'to' => Fixtures::$multiplePhoneNumbersSMS,
|
||||
'message' => 'Testing with short code...',
|
||||
'from' => Fixtures::$shortCode
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('SMSMessageData', $response['data']);
|
||||
}
|
||||
|
||||
public function testSMSSendingWithAlphanumeric()
|
||||
{
|
||||
$response = $this->client->send([
|
||||
'to' => Fixtures::$multiplePhoneNumbersSMS,
|
||||
'message' => 'Testing with AlphaNumeric...',
|
||||
'from' => Fixtures::$alphanumeric
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('SMSMessageData', $response['data']);
|
||||
}
|
||||
|
||||
public function testPremiumSMSSending()
|
||||
{
|
||||
$response = $this->client->sendPremium([
|
||||
'to' => Fixtures::$multiplePhoneNumbersSMS,
|
||||
'linkId' => 'messageLinkId',
|
||||
'keyword' => Fixtures::$keyword,
|
||||
'from' => Fixtures::$shortCode,
|
||||
'message' => 'Testing Premium...'
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('SMSMessageData', $response['data']);
|
||||
}
|
||||
|
||||
public function testFetchMessages()
|
||||
{
|
||||
$response = $this->client->fetchMessages(['lastReceivedId' => '8796']);
|
||||
|
||||
$this->assertObjectHasProperty('SMSMessageData', $response['data']);
|
||||
}
|
||||
|
||||
public function testCreateSubscription()
|
||||
{
|
||||
$response = $this->client->createSubscription([
|
||||
'phoneNumber' => Fixtures::$phoneNumber,
|
||||
'shortCode' => Fixtures::$shortCode,
|
||||
'keyword' => Fixtures::$keyword,
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('status',$response);
|
||||
$this->assertEquals('success',$response['status']);
|
||||
}
|
||||
|
||||
public function testDeleteSubscription()
|
||||
{
|
||||
$response = $this->client->deleteSubscription([
|
||||
'phoneNumber' => Fixtures::$phoneNumber,
|
||||
'shortCode' => Fixtures::$shortCode,
|
||||
'keyword' => Fixtures::$keyword
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('status',$response);
|
||||
$this->assertEquals('success',$response['status']);
|
||||
}
|
||||
|
||||
public function testFetchSubscriptions()
|
||||
{
|
||||
$response = $this->client->fetchSubscriptions([
|
||||
'shortCode' => Fixtures::$shortCode,
|
||||
'keyword' => Fixtures::$keyword
|
||||
]);
|
||||
|
||||
$this->assertObjectHasProperty('responses', $response['data']);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK\Tests;
|
||||
|
||||
use AfricasTalking\SDK\AfricasTalking;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
class TokenTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->username = Fixtures::$username;
|
||||
$this->apiKey = Fixtures::$apiKey;
|
||||
|
||||
$at = new AfricasTalking($this->username, $this->apiKey);
|
||||
|
||||
$this->client = $at->token();
|
||||
}
|
||||
|
||||
public function testGenerateAuthToken()
|
||||
{
|
||||
$response = $this->client->generateAuthToken();
|
||||
$this->assertEquals(3600, $response['data']->lifetimeInSeconds);
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
namespace AfricasTalking\SDK\Tests;
|
||||
|
||||
use AfricasTalking\SDK\AfricasTalking;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
class VoiceTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->username = Fixtures::$username;
|
||||
$this->apiKey = Fixtures::$apiKey;
|
||||
|
||||
$at = new AfricasTalking($this->username, $this->apiKey);
|
||||
|
||||
$this->client = $at->voice();
|
||||
}
|
||||
|
||||
public function testCall()
|
||||
{
|
||||
$response = $this->client->call([
|
||||
'from' => Fixtures::$voicePhoneNumber,
|
||||
'to' => Fixtures::$voicePhoneNumber2
|
||||
]);
|
||||
$this->assertObjectHasProperty('entries', $response['data']);
|
||||
|
||||
}
|
||||
|
||||
public function testCallsMustHaveRequiredAttributes()
|
||||
{
|
||||
$response = $this->client->call([
|
||||
'from' => Fixtures::$voicePhoneNumber
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('status',$response);
|
||||
$this->assertEquals('error',$response['status']);
|
||||
}
|
||||
|
||||
public function testFetchQueuedCalls()
|
||||
{
|
||||
$response = $this->client->fetchQueuedCalls([
|
||||
'phoneNumber' => Fixtures::$voicePhoneNumber,
|
||||
'name' => 'someQueueName'
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('status', $response);
|
||||
}
|
||||
|
||||
public function testFetchQueuedCallsMustHaveRequiredAttributes()
|
||||
{
|
||||
$response = $this->client->fetchQueuedCalls();
|
||||
|
||||
$this->assertArrayHasKey('status',$response);
|
||||
$this->assertEquals('error',$response['status']);
|
||||
}
|
||||
|
||||
public function testUploadMediaFile()
|
||||
{
|
||||
$response = $this->client->uploadMediaFile([
|
||||
'phoneNumber' => Fixtures::$voicePhoneNumber,
|
||||
'url' => Fixtures::$mediaUrl
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('status', $response);
|
||||
}
|
||||
|
||||
public function testuploadMediaFileMustHaveRequiredAttributes()
|
||||
{
|
||||
$response = $this->client->uploadMediaFile([
|
||||
'url' => 'test@google'
|
||||
]);
|
||||
|
||||
$this->assertArrayHasKey('status',$response);
|
||||
$this->assertEquals('error',$response['status']);
|
||||
}
|
||||
|
||||
public function testuploadMediaFileCannotBeEmpty()
|
||||
{
|
||||
$response = $this->client->uploadMediaFile();
|
||||
|
||||
$this->assertArrayHasKey('status',$response);
|
||||
$this->assertEquals('error',$response['status']);
|
||||
}
|
||||
|
||||
// public function testMessageBuilder()
|
||||
// {
|
||||
// // TODO
|
||||
// }
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit379a36e429894b61b55afc3b84fa116a::getLoader();
|
||||
@@ -0,0 +1,15 @@
|
||||
; This file is for unifying the coding style for different editors and IDEs.
|
||||
; More information at http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018
|
||||
|
||||
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,50 @@
|
||||
{
|
||||
"name": "barryvdh/laravel-snappy",
|
||||
"description": "Snappy PDF/Image for Laravel",
|
||||
"keywords": ["laravel","snappy", "pdf", "image", "wkhtmltopdf", "wkhtmltoimage"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Barry vd. Heuvel",
|
||||
"email": "barryvdh@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"illuminate/support": "^9|^10",
|
||||
"illuminate/filesystem": "^9|^10",
|
||||
"knplabs/knp-snappy": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "^7|^8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Barryvdh\\Snappy\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Barryvdh\\Snappy\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Barryvdh\\Snappy\\ServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"PDF": "Barryvdh\\Snappy\\Facades\\SnappyPdf",
|
||||
"SnappyImage": "Barryvdh\\Snappy\\Facades\\SnappyImage"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Snappy PDF / Image Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option contains settings for PDF generation.
|
||||
|
|
||||
| Enabled:
|
||||
|
|
||||
| Whether to load PDF / Image generation.
|
||||
|
|
||||
| Binary:
|
||||
|
|
||||
| The file path of the wkhtmltopdf / wkhtmltoimage executable.
|
||||
|
|
||||
| Timout:
|
||||
|
|
||||
| The amount of time to wait (in seconds) before PDF / Image generation is stopped.
|
||||
| Setting this to false disables the timeout (unlimited processing time).
|
||||
|
|
||||
| Options:
|
||||
|
|
||||
| The wkhtmltopdf command options. These are passed directly to wkhtmltopdf.
|
||||
| See https://wkhtmltopdf.org/usage/wkhtmltopdf.txt for all options.
|
||||
|
|
||||
| Env:
|
||||
|
|
||||
| The environment variables to set while running the wkhtmltopdf process.
|
||||
|
|
||||
*/
|
||||
|
||||
'pdf' => [
|
||||
'enabled' => true,
|
||||
'binary' => env('WKHTML_PDF_BINARY', '/usr/local/bin/wkhtmltopdf'),
|
||||
'timeout' => false,
|
||||
'options' => [],
|
||||
'env' => [],
|
||||
],
|
||||
|
||||
'image' => [
|
||||
'enabled' => true,
|
||||
'binary' => env('WKHTML_IMG_BINARY', '/usr/local/bin/wkhtmltoimage'),
|
||||
'timeout' => false,
|
||||
'options' => [],
|
||||
'env' => [],
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,209 @@
|
||||
## Snappy PDF/Image Wrapper for Laravel
|
||||
[](https://github.com/barryvdh/laravel-snappy/actions)
|
||||
[](http://choosealicense.com/licenses/mit/)
|
||||
[](https://packagist.org/packages/barryvdh/laravel-snappy)
|
||||
[](https://packagist.org/packages/barryvdh/laravel-snappy)
|
||||
[](https://fruitcake.nl/)
|
||||
|
||||
This package is a ServiceProvider for Snappy: [https://github.com/KnpLabs/snappy](https://github.com/KnpLabs/snappy).
|
||||
|
||||
### Wkhtmltopdf Installation
|
||||
|
||||
Choose one of the following options to install wkhtmltopdf/wkhtmltoimage.
|
||||
|
||||
1. Download wkhtmltopdf from [here](http://wkhtmltopdf.org/downloads.html);
|
||||
2. Or install as a composer dependency. See [wkhtmltopdf binary as composer dependencies](https://github.com/KnpLabs/snappy#wkhtmltopdf-binary-as-composer-dependencies) for more information.
|
||||
|
||||
#### Attention! Please note that some dependencies (libXrender for example) may not be present on your system and may require manual installation.
|
||||
|
||||
### Testing the wkhtmltopdf installation
|
||||
|
||||
After installing you should be able to run wkhtmltopdf from the command line / shell.
|
||||
|
||||
If you went for the second option the binaries will be at `/vendor/h4cc/wkhtmltoimage-amd64/bin` and `/vendor/h4cc/wkhtmltopdf-amd64/bin`.
|
||||
|
||||
#### Attention vagrant users!
|
||||
|
||||
Move the binaries to a path that is not in a synced folder, for example:
|
||||
|
||||
```bash
|
||||
cp vendor/h4cc/wkhtmltoimage-amd64/bin/wkhtmltoimage-amd64 /usr/local/bin/
|
||||
cp vendor/h4cc/wkhtmltopdf-amd64/bin/wkhtmltopdf-amd64 /usr/local/bin/
|
||||
```
|
||||
|
||||
and make it executable:
|
||||
|
||||
```bash
|
||||
chmod +x /usr/local/bin/wkhtmltoimage-amd64
|
||||
chmod +x /usr/local/bin/wkhtmltopdf-amd64
|
||||
```
|
||||
|
||||
This will prevent the error 126.
|
||||
|
||||
### Package Installation
|
||||
|
||||
Require this package in your composer.json and update composer.
|
||||
|
||||
```bash
|
||||
composer require barryvdh/laravel-snappy
|
||||
```
|
||||
|
||||
### Laravel
|
||||
|
||||
**Laravel 5.5** uses Package Auto-Discovery, so doesn't require you to manually add the ServiceProvider/Facade. If you also use laravel-dompdf, watch out for conflicts. It could be better to manually register the Facade.
|
||||
|
||||
After updating composer, add the ServiceProvider to the providers array in config/app.php
|
||||
|
||||
```php
|
||||
Barryvdh\Snappy\ServiceProvider::class,
|
||||
```
|
||||
|
||||
Optionally you can use the Facade for shorter code. Add this to your facades:
|
||||
|
||||
```php
|
||||
'PDF' => Barryvdh\Snappy\Facades\SnappyPdf::class,
|
||||
'SnappyImage' => Barryvdh\Snappy\Facades\SnappyImage::class,
|
||||
```
|
||||
|
||||
Finally you can publish the config file:
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --provider="Barryvdh\Snappy\ServiceProvider"
|
||||
```
|
||||
|
||||
### Snappy config file
|
||||
|
||||
The main change to this config file (config/snappy.php) will be the path to the binaries.
|
||||
|
||||
For example, when loaded with composer, the line should look like:
|
||||
|
||||
```php
|
||||
'binary' => base_path('vendor/h4cc/wkhtmltopdf-amd64/bin/wkhtmltopdf-amd64'),
|
||||
```
|
||||
|
||||
If you followed the vagrant steps, the line should look like:
|
||||
|
||||
```php
|
||||
'binary' => '/usr/local/bin/wkhtmltopdf-amd64',
|
||||
```
|
||||
|
||||
For windows users you'll have to add double quotes to the bin path for wkhtmltopdf:
|
||||
|
||||
```php
|
||||
'binary' => '"C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf"'
|
||||
```
|
||||
|
||||
### Lumen
|
||||
In `bootstrap/app.php` add:
|
||||
|
||||
```php
|
||||
class_alias('Barryvdh\Snappy\Facades\SnappyPdf', 'PDF');
|
||||
$app->register(Barryvdh\Snappy\LumenServiceProvider::class);
|
||||
```
|
||||
|
||||
Optionally, add the facades like so:
|
||||
|
||||
```php
|
||||
class_alias(Barryvdh\Snappy\Facades\SnappyPdf::class, 'PDF');
|
||||
class_alias(Barryvdh\Snappy\Facades\SnappyImage::class, 'SnappyImage');
|
||||
```
|
||||
|
||||
To customise the configuration file, copy the file `/vendor/barryvdh/laravel-snappy/config/snappy.php` to the `/config` folder.
|
||||
|
||||
### Usage
|
||||
|
||||
You can create a new Snappy PDF/Image instance and load a HTML string, file or view name. You can save it to a file, or inline (show in browser) or download.
|
||||
|
||||
Using the App container:
|
||||
|
||||
```php
|
||||
$snappy = App::make('snappy.pdf');
|
||||
//To file
|
||||
$html = '<h1>Bill</h1><p>You owe me money, dude.</p>';
|
||||
$snappy->generateFromHtml($html, '/tmp/bill-123.pdf');
|
||||
$snappy->generate('http://www.github.com', '/tmp/github.pdf');
|
||||
//Or output:
|
||||
return new Response(
|
||||
$snappy->getOutputFromHtml($html),
|
||||
200,
|
||||
array(
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="file.pdf"'
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
Using the wrapper:
|
||||
|
||||
```php
|
||||
$pdf = App::make('snappy.pdf.wrapper');
|
||||
$pdf->loadHTML('<h1>Test</h1>');
|
||||
return $pdf->inline();
|
||||
```
|
||||
|
||||
Or use the facade:
|
||||
|
||||
```php
|
||||
$pdf = PDF::loadView('pdf.invoice', $data);
|
||||
return $pdf->download('invoice.pdf');
|
||||
```
|
||||
|
||||
You can chain the methods:
|
||||
|
||||
```php
|
||||
return PDF::loadFile('http://www.github.com')->inline('github.pdf');
|
||||
```
|
||||
|
||||
You can change the orientation and paper size
|
||||
|
||||
```php
|
||||
PDF::loadHTML($html)->setPaper('a4')->setOrientation('landscape')->setOption('margin-bottom', 0)->save('myfile.pdf')
|
||||
```
|
||||
|
||||
If you need the output as a string, you can get the rendered PDF with the output() function, so you can save/output it yourself.
|
||||
|
||||
See the [wkhtmltopdf manual](http://wkhtmltopdf.org/usage/wkhtmltopdf.txt) for more information/settings.
|
||||
|
||||
### Testing - PDF fake
|
||||
|
||||
As an alternative to mocking, you may use the `PDF` facade's `fake` method. When using fakes, assertions are made after the code under test is executed:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
use PDF;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
public function testPrintOrderShipping()
|
||||
{
|
||||
PDF::fake();
|
||||
|
||||
// Perform order shipping...
|
||||
|
||||
PDF::assertViewIs('view-pdf-order-shipping');
|
||||
PDF::assertSee('Name');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Other available assertions:
|
||||
|
||||
```php
|
||||
PDF::assertViewIs($value);
|
||||
PDF::assertViewHas($key, $value = null);
|
||||
PDF::assertViewHasAll(array $bindings);
|
||||
PDF::assertViewMissing($key);
|
||||
PDF::assertSee($value);
|
||||
PDF::assertSeeText($value);
|
||||
PDF::assertDontSee($value);
|
||||
PDF::assertDontSeeText($value);
|
||||
PDF::assertFileNameIs($value);
|
||||
```
|
||||
|
||||
### License
|
||||
|
||||
This Snappy Wrapper for Laravel is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace Barryvdh\Snappy\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade as BaseFacade;
|
||||
|
||||
class SnappyImage extends BaseFacade {
|
||||
|
||||
/**
|
||||
* Get the registered name of the component.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getFacadeAccessor() { return 'snappy.image.wrapper'; }
|
||||
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
namespace Barryvdh\Snappy\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade as BaseFacade;
|
||||
use Barryvdh\Snappy\PdfFaker;
|
||||
|
||||
|
||||
/**
|
||||
* @method static \Barryvdh\Snappy\PdfWrapper setPaper($paper, $orientation = 'portrait')
|
||||
* @method static \Barryvdh\Snappy\PdfWrapper setWarnings($warnings)
|
||||
* @method static \Barryvdh\Snappy\PdfWrapper setOptions(array $options)
|
||||
* @method static \Barryvdh\Snappy\PdfWrapper loadView($view, $data = array(), $mergeData = array(), $encoding = null)
|
||||
* @method static \Barryvdh\Snappy\PdfWrapper loadHTML($string, $encoding = null)
|
||||
* @method static \Barryvdh\Snappy\PdfWrapper loadFile($file)
|
||||
* @method static string output($options = [])
|
||||
* @method static \Barryvdh\Snappy\PdfWrapper save()
|
||||
* @method static \Illuminate\Http\Response download($filename = 'document.pdf')
|
||||
* @method static \Illuminate\Http\Response inline($filename = 'document.pdf')
|
||||
*
|
||||
*/
|
||||
class SnappyPdf extends BaseFacade {
|
||||
|
||||
/**
|
||||
* Get the registered name of the component.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getFacadeAccessor() { return 'snappy.pdf.wrapper'; }
|
||||
|
||||
/**
|
||||
* Replace the bound instance with a fake.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function fake()
|
||||
{
|
||||
static::swap(new PdfFaker(app('snappy.pdf')));
|
||||
}
|
||||
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<?php namespace Barryvdh\Snappy;
|
||||
|
||||
use Knp\Snappy\Image;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
||||
class IlluminateSnappyImage extends Image {
|
||||
|
||||
/**
|
||||
* @param \Illuminate\Filesystem\Filesystem
|
||||
* @param string $binary
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(Filesystem $fs, $binary, array $options, array $env)
|
||||
{
|
||||
parent::__construct($binary, $options, $env);
|
||||
|
||||
$this->fs = $fs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "file_get_contents" function
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFileContents($filename)
|
||||
{
|
||||
return $this->fs->get($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "file_exists" function
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function fileExists($filename)
|
||||
{
|
||||
return $this->fs->exists($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "is_file" method
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isFile($filename)
|
||||
{
|
||||
return $this->fs->isFile($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "filesize" function
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return integer or FALSE on failure
|
||||
*/
|
||||
protected function filesize($filename)
|
||||
{
|
||||
return $this->fs->size($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "unlink" function
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function unlink($filename)
|
||||
{
|
||||
return $this->fs->delete($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "is_dir" function
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isDir($filename)
|
||||
{
|
||||
return $this->fs->isDirectory($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the mkdir function
|
||||
*
|
||||
* @param string $pathname
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function mkdir($pathname)
|
||||
{
|
||||
return $this->fs->makeDirectory($pathname, 0777, true, true);
|
||||
}
|
||||
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?php namespace Barryvdh\Snappy;
|
||||
|
||||
use Knp\Snappy\Pdf;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
||||
class IlluminateSnappyPdf extends Pdf {
|
||||
/**
|
||||
* @var \Illuminate\Filesystem\Filesystem
|
||||
*/
|
||||
protected $fs;
|
||||
|
||||
/**
|
||||
* @param \Illuminate\Filesystem\Filesystem
|
||||
* @param string $binary
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(Filesystem $fs, $binary, array $options, array $env)
|
||||
{
|
||||
parent::__construct($binary, $options, $env);
|
||||
|
||||
$this->fs = $fs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "file_get_contents" function
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFileContents($filename)
|
||||
{
|
||||
return $this->fs->get($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "file_exists" function
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function fileExists($filename)
|
||||
{
|
||||
return $this->fs->exists($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "is_file" method
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isFile($filename)
|
||||
{
|
||||
return strlen($filename) <= PHP_MAXPATHLEN && $this->fs->isFile($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "filesize" function
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return integer or FALSE on failure
|
||||
*/
|
||||
protected function filesize($filename)
|
||||
{
|
||||
return $this->fs->size($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "unlink" function
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function unlink($filename)
|
||||
{
|
||||
return $this->fs->delete($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the "is_dir" function
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isDir($filename)
|
||||
{
|
||||
return $this->fs->isDirectory($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the mkdir function
|
||||
*
|
||||
* @param string $pathname
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function mkdir($pathname)
|
||||
{
|
||||
return $this->fs->makeDirectory($pathname, 0777, true, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php namespace Barryvdh\Snappy;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
use Knp\Snappy\Image as SnappyImage;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
/**
|
||||
* A Laravel wrapper for SnappyImage
|
||||
*
|
||||
* @package laravel-snappy
|
||||
* @author Killian Blais
|
||||
*/
|
||||
class ImageWrapper {
|
||||
|
||||
/**
|
||||
* @var \Knp\Snappy\Image
|
||||
*/
|
||||
protected $snappy;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options = array();
|
||||
|
||||
|
||||
/**
|
||||
* @param \Knp\Snappy\Image $snappy
|
||||
*/
|
||||
public function __construct(SnappyImage $snappy)
|
||||
{
|
||||
$this->snappy = $snappy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Snappy instance.
|
||||
*
|
||||
* @return \Knp\Snappy\Image
|
||||
*/
|
||||
public function snappy()
|
||||
{
|
||||
return $this->snappy;
|
||||
}
|
||||
|
||||
public function setOption($name, $value)
|
||||
{
|
||||
$this->snappy->setOption($name, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setOptions($options)
|
||||
{
|
||||
$this->snappy->setOptions($options);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a HTML string
|
||||
*
|
||||
* @param string $string
|
||||
* @return static
|
||||
*/
|
||||
public function loadHTML($string)
|
||||
{
|
||||
$this->html = (string) $string;
|
||||
$this->file = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a HTML file
|
||||
*
|
||||
* @param string $file
|
||||
* @return static
|
||||
*/
|
||||
public function loadFile($file)
|
||||
{
|
||||
$this->html = null;
|
||||
$this->file = $file;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function loadView($view, $data = array(), $mergeData = array())
|
||||
{
|
||||
$this->html = View::make($view, $data, $mergeData)->render();
|
||||
$this->file = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the PDF as a string.
|
||||
*
|
||||
* @return string The rendered PDF as string
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
if ($this->html)
|
||||
{
|
||||
return $this->snappy->getOutputFromHtml($this->html, $this->options);
|
||||
}
|
||||
|
||||
if ($this->file)
|
||||
{
|
||||
return $this->snappy->getOutput($this->file, $this->options);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Image Generator requires a html or file in order to produce output.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the image to a file
|
||||
*
|
||||
* @param $filename
|
||||
* @return static
|
||||
*/
|
||||
public function save($filename, $overwrite = false)
|
||||
{
|
||||
|
||||
if ($this->html)
|
||||
{
|
||||
$this->snappy->generateFromHtml($this->html, $filename, $this->options, $overwrite);
|
||||
}
|
||||
elseif ($this->file)
|
||||
{
|
||||
$this->snappy->generate($this->file, $filename, $this->options, $overwrite);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the image downloadable by the user
|
||||
*
|
||||
* @param string $filename
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function download($filename = 'image.jpg')
|
||||
{
|
||||
return new Response($this->output(), 200, array(
|
||||
'Content-Type' => 'image/jpeg',
|
||||
'Content-Disposition' => 'attachment; filename="'.$filename.'"'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a response with the image to show in the browser
|
||||
*
|
||||
* @param string $filename
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function inline($filename = 'image.jpg')
|
||||
{
|
||||
return new Response($this->output(), 200, array(
|
||||
'Content-Type' => 'image/jpeg',
|
||||
'Content-Disposition' => 'inline; filename="'.$filename.'"',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a response with the image to show in the browser
|
||||
*
|
||||
* @deprecated Use inline() instead
|
||||
* @param string $filename
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function stream($filename = 'image.jpg')
|
||||
{
|
||||
return new StreamedResponse(function() {
|
||||
echo $this->output();
|
||||
}, 200, array(
|
||||
'Content-Type' => 'image/jpeg',
|
||||
'Content-Disposition' => 'inline; filename="'.$filename.'"',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Snappy instance.
|
||||
*
|
||||
* Also shortcut's
|
||||
* ->html => loadHtml
|
||||
* ->view => loadView
|
||||
* ->file => loadFile
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($name, array $arguments)
|
||||
{
|
||||
$method = 'load' . ucfirst($name);
|
||||
if (method_exists($this, $method))
|
||||
{
|
||||
return call_user_func_array(array($this, $method), $arguments);
|
||||
}
|
||||
|
||||
return call_user_func_array (array($this->snappy, $name), $arguments);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?php namespace Barryvdh\Snappy;
|
||||
|
||||
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
|
||||
|
||||
class LumenServiceProvider extends BaseServiceProvider
|
||||
{
|
||||
/**
|
||||
* Indicates if loading of the provider is deferred.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $defer = false;
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->configure('snappy');
|
||||
|
||||
$configPath = __DIR__ . '/../config/snappy.php';
|
||||
$this->mergeConfigFrom($configPath, 'snappy');
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
if ($this->app['config']->get('snappy.pdf.enabled')) {
|
||||
$this->app->bind('snappy.pdf',function ($app) {
|
||||
$binary = $app['config']->get('snappy.pdf.binary', '/usr/local/bin/wkhtmltopdf');
|
||||
$options = $app['config']->get('snappy.pdf.options', array());
|
||||
$env = $app['config']->get('snappy.pdf.env', array());
|
||||
$timeout = $app['config']->get('snappy.pdf.timeout', false);
|
||||
|
||||
$snappy = new IlluminateSnappyPdf($app['files'], $binary, $options, $env);
|
||||
if (false !== $timeout) {
|
||||
$snappy->setTimeout($timeout);
|
||||
}
|
||||
|
||||
return $snappy;
|
||||
});
|
||||
|
||||
$this->app->bind('snappy.pdf.wrapper',function ($app) {
|
||||
return new PdfWrapper($app['snappy.pdf']);
|
||||
});
|
||||
$this->app->alias('snappy.pdf.wrapper', 'Barryvdh\Snappy\PdfWrapper');
|
||||
}
|
||||
|
||||
if ($this->app['config']->get('snappy.image.enabled')) {
|
||||
$this->app->bind('snappy.image',function ($app) {
|
||||
$binary = $app['config']->get('snappy.image.binary', '/usr/local/bin/wkhtmltoimage');
|
||||
$options = $app['config']->get('snappy.image.options', array());
|
||||
$env = $app['config']->get('snappy.image.env', array());
|
||||
$timeout = $app['config']->get('snappy.image.timeout', false);
|
||||
|
||||
$image = new IlluminateSnappyImage($app['files'], $binary, $options, $env);
|
||||
if (false !== $timeout) {
|
||||
$image->setTimeout($timeout);
|
||||
}
|
||||
|
||||
return $image;
|
||||
});
|
||||
|
||||
$this->app->bind('snappy.image.wrapper',function ($app) {
|
||||
return new ImageWrapper($app['snappy.image']);
|
||||
});
|
||||
$this->app->alias('snappy.image.wrapper', 'Barryvdh\Snappy\ImageWrapper');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provides()
|
||||
{
|
||||
return array('snappy.pdf', 'snappy.pdf.wrapper', 'snappy.image', 'snappy.image.wrapper');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
<?php namespace Barryvdh\Snappy;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use PHPUnit\Framework\Assert as PHPUnit;
|
||||
use Illuminate\Support\Facades\View as ViewFacade;
|
||||
|
||||
|
||||
class PdfFaker extends PdfWrapper
|
||||
{
|
||||
protected $view;
|
||||
protected $filename;
|
||||
|
||||
/**
|
||||
* Load a View and convert to HTML
|
||||
*
|
||||
* @param string $view
|
||||
* @param array $data
|
||||
* @param array $mergeData
|
||||
* @return $this
|
||||
*/
|
||||
public function loadView($view, $data = array(), $mergeData = array())
|
||||
{
|
||||
$this->view = ViewFacade::make($view, $data, $mergeData);
|
||||
return parent::loadView($view, $data, $mergeData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensure that the response has a view as its original content.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function ensureResponseHasView()
|
||||
{
|
||||
if (! isset($this->view) || ! $this->view instanceof View) {
|
||||
return PHPUnit::fail('The response is not a view.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function assertViewIs($value)
|
||||
{
|
||||
PHPUnit::assertEquals($value, $this->view->getName());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the response view has a given piece of bound data.
|
||||
*
|
||||
* @param string|array $key
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertViewHas($key, $value = null)
|
||||
{
|
||||
if (is_array($key)) {
|
||||
return $this->assertViewHasAll($key);
|
||||
}
|
||||
|
||||
$this->ensureResponseHasView();
|
||||
|
||||
if (is_null($value)) {
|
||||
PHPUnit::assertArrayHasKey($key, $this->view->getData());
|
||||
} elseif ($value instanceof \Closure) {
|
||||
PHPUnit::assertTrue($value($this->view->$key));
|
||||
} else {
|
||||
PHPUnit::assertEquals($value, $this->view->$key);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the response view has a given list of bound data.
|
||||
*
|
||||
* @param array $bindings
|
||||
* @return $this
|
||||
*/
|
||||
public function assertViewHasAll(array $bindings)
|
||||
{
|
||||
foreach ($bindings as $key => $value) {
|
||||
if (is_int($key)) {
|
||||
$this->assertViewHas($value);
|
||||
} else {
|
||||
$this->assertViewHas($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the response view is missing a piece of bound data.
|
||||
*
|
||||
* @param string $key
|
||||
* @return $this
|
||||
*/
|
||||
public function assertViewMissing($key)
|
||||
{
|
||||
$this->ensureResponseHasView();
|
||||
|
||||
PHPUnit::assertArrayNotHasKey($key, $this->view->getData());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given string is contained within the response.
|
||||
*
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertSee($value)
|
||||
{
|
||||
PHPUnit::assertStringContainsString($value, $this->html);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given string is contained within the response text.
|
||||
*
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertSeeText($value)
|
||||
{
|
||||
PHPUnit::assertStringContainsString($value, strip_tags($this->html));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given string is not contained within the response.
|
||||
*
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertDontSee($value)
|
||||
{
|
||||
PHPUnit::assertStringNotContainsString($value, $this->html);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given string is not contained within the response text.
|
||||
*
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertDontSeeText($value)
|
||||
{
|
||||
PHPUnit::assertStringNotContainsString($value, strip_tags($this->html));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given string is equal to the saved filename.
|
||||
*
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertFileNameIs($value)
|
||||
{
|
||||
PHPUnit::assertEquals($value, $this->filename);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function output()
|
||||
{
|
||||
return '%PDF-1.3
|
||||
%?????????
|
||||
4 0 obj
|
||||
<< /Length 5 0 R /Filter /FlateDecode >>
|
||||
stream
|
||||
x+T(T0BSKS=#
|
||||
|
||||
C=
|
||||
K??T?p?<}?bC??bC0,N??5?34???05j1?7)N?
|
||||
z/?
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
73
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 595.28 841.89]
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<< /ProcSet [ /PDF ] /ColorSpace << /Cs1 7 0 R >> >>
|
||||
endobj
|
||||
8 0 obj
|
||||
<< /Length 9 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >>
|
||||
stream
|
||||
x??wTS??Ͻ7??" %?z ?;HQ?I?P??&vDF)VdT?G?"cE
|
||||
??b? ?P??QDE?k ?5?ޚ??Y?????g?}P???tX?4?X???\???X??ffG?D???=???HƳ??.?d??,?P&s???"7C$
|
||||
E?6<~&??S??2????)2?12? ??"?įl???+?ɘ?&?Y??4???Pޚ%ᣌ?\?%?g?|e?TI???(????L0?_??&?l?2E???9?r??9h?x?g??Ib?טi???f??S?b1+??M?xL???
|
||||
?0??o?E%Ym?h?????Y??h????~S?=?z?U?&?ϞA??Y?l?/??$Z????U?m@??O? ??ޜ??l^???
|
||||
\'
|
||||
???ls?k.+?7???oʿ?9?????V;???#I3eE妧?KD??
|
||||
??d?????9i???,?????UQ? ??h??<?X?.d
|
||||
???6\'~?khu_}?9P?I?o=C#$n?z}?[1
|
||||
Ⱦ?h???s?2z???
|
||||
\?n?LA"S??
|
||||
?dr%?,?߄l??t?
|
||||
?3p? ??H?.Hi@?A>?
|
||||
p1?v?jpԁz?N?6p\W?
|
||||
|
||||
?G@
|
||||
???ٰG???Dx????J?>???,?_@?FDB?X$!k?"??E????H?q???a???Y??bVa?bJ0c?VL?6f3????bձ?X\'??&?x?*???s?b|!??`[????a?;???p~?\2n5??????
|
||||
ߏƿ\'? Zk?!? $l$???4Q??Ot"?y?\b)???A?I&N?I?$R$)???TIj"]&=&?!??:dGrY@^O?$? _%??P?n?X????ZO?D}J}/G?3???ɭ???k??{%O?חw?_.?\'_!J????Q?@?S???V?F??=?IE???b?b?b?b??5?Q%?????O?@??%?!BӥyҸ?M?:?e?0G7??ӓ????? e%e[?(????R?0`?3R????????4?????6?i^??)??*n*|?"?f????LUo?՝?m?O?0j&jaj?j??.??ϧ?w?ϝ_4????갺?z??j???=???U?4?5?n?ɚ??4ǴhZ
|
||||
?Z?Z?^0????Tf%??9?????-?>?ݫ=?c??Xg?N??]?.[7A?\?SwBOK/X/_?Q?>Q?????G?[??? ?`?A???????a?a??c#????*?Z?;?8c?q??>?[&???I?I??MS???T`?ϴ?
|
||||
k?h&4?5?Ǣ??YY?F֠9?<?|?y??+
|
||||
=?X???_,?,S-?,Y)YXm?????Ěk]c}džj?c?Φ?浭?-?v??};?]???N????"?&?1=?x????tv(??}???????\'{\'??I?ߝY?Σ?
|
||||
|
||||
??-r?q?r?.d.?_xp??Uە?Z???M?v?m???=????+K?G?ǔ????
|
||||
^???W?W????b?j?>:>?>?>?v??}/?a??v?????????O8? ?
|
||||
?FV>
|
||||
2 u?????/?_$\?B?Cv?< 5
|
||||
]?s.,4?&?y?Ux~xw-bEDCĻH????G??KwF?G?E?GME{E?EK?X,Y??F?Z? ?=$vr????K????
|
||||
??.3\????r???Ϯ?_?Yq*??©?L??_?w?ד??????+??]?e???????D??]?cI?II?OA??u?_?䩔???)3?ѩ?i?????B%a??+]3=\'?/?4?0C??i??U?@ёL(sYf????L?H?$?%?Y
|
||||
?j??gGe??Q?????n?????~5f5wug?v????5?k??֮\۹Nw]??????m mH???Fˍen???Q?Q??`h????B?BQ?-?[l?ll??f??jۗ"^??b???O%ܒ??Y}W???????????w?w????X?bY^?Ю?]?????W?Va[q`i?d??2???J?jGէ??????{??????m???>???Pk?Am?a?????꺿g_D?H??G?G??u?;??7?7?6?Ʊ?q?o???C{??P3???8!9?????
|
||||
<?y?}??\'?????Z?Z???։??6i{L{??ӝ?-???|??????gKϑ???9?w~?Bƅ??:Wt>???ҝ????ˁ??^?r?۽??U??g?9];}?}????????_?~i??m??p???㭎?}??]?/???}?????.?{?^?=?}????^??z8?h?c??\'
|
||||
O*????????f?????`ϳ?g???C/????O?ϩ?+F?F?G?Gό???z????ˌ??ㅿ)????ѫ?~w??gb???k???Jި?9???m?d???wi獵?ޫ???????c?Ǒ??O?O????w| ??x&mf??????
|
||||
endstream
|
||||
endobj
|
||||
9 0 obj
|
||||
2612
|
||||
endobj
|
||||
7 0 obj
|
||||
[ /ICCBased 8 0 R ]
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Type /Pages /MediaBox [0 0 595.28 841.89] /Count 1 /Kids [ 2 0 R ] >>
|
||||
endobj
|
||||
10 0 obj
|
||||
<< /Type /Catalog /Pages 3 0 R >>
|
||||
endobj
|
||||
11 0 obj
|
||||
(Leeg)
|
||||
endobj
|
||||
12 0 obj
|
||||
(Mac OS X 10.13.3 Quartz PDFContext)
|
||||
endobj
|
||||
13 0 obj
|
||||
(Pages)
|
||||
endobj
|
||||
14 0 obj
|
||||
(D:20180330091154Z00\'00\')
|
||||
endobj
|
||||
1 0 obj
|
||||
<< /Title 11 0 R /Producer 12 0 R /Creator 13 0 R /CreationDate 14 0 R /ModDate
|
||||
14 0 R >>
|
||||
endobj
|
||||
xref
|
||||
0 15
|
||||
0000000000 65535 f
|
||||
0000003414 00000 n
|
||||
0000000187 00000 n
|
||||
0000003133 00000 n
|
||||
0000000022 00000 n
|
||||
0000000169 00000 n
|
||||
0000000297 00000 n
|
||||
0000003098 00000 n
|
||||
0000000365 00000 n
|
||||
0000003078 00000 n
|
||||
0000003222 00000 n
|
||||
0000003272 00000 n
|
||||
0000003295 00000 n
|
||||
0000003348 00000 n
|
||||
0000003372 00000 n
|
||||
trailer
|
||||
<< /Size 15 /Root 10 0 R /Info 1 0 R /ID [ <ea5d2c625a19688ce1ff05174d8a0261>
|
||||
<ea5d2c625a19688ce1ff05174d8a0261> ] >>
|
||||
startxref
|
||||
3519
|
||||
%%EOF';
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the PDF to a file
|
||||
*
|
||||
* @param $filename
|
||||
* @return $this
|
||||
*/
|
||||
public function save($filename, $overwrite = false)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php namespace Barryvdh\Snappy;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
use Knp\Snappy\Pdf as SnappyPDF;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Contracts\Support\Renderable;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
/**
|
||||
* A Laravel wrapper for SnappyPDF
|
||||
*
|
||||
* @package laravel-snappy
|
||||
* @author Barry vd. Heuvel
|
||||
*/
|
||||
class PdfWrapper{
|
||||
|
||||
/**
|
||||
* @var \Knp\Snappy\Pdf
|
||||
*/
|
||||
protected $snappy;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $html;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* @param \Knp\Snappy\Pdf $snappy
|
||||
*/
|
||||
public function __construct(SnappyPDF $snappy)
|
||||
{
|
||||
$this->snappy = $snappy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Snappy instance.
|
||||
*
|
||||
* @return \Knp\Snappy\Pdf
|
||||
*/
|
||||
public function snappy()
|
||||
{
|
||||
return $this->snappy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set temporary folder
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function setTemporaryFolder($path)
|
||||
{
|
||||
$this->snappy->setTemporaryFolder($path);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the paper size (default A4)
|
||||
*
|
||||
* @param string $paper
|
||||
* @param string $orientation
|
||||
* @return $this
|
||||
*/
|
||||
public function setPaper($paper, $orientation=null)
|
||||
{
|
||||
$this->snappy->setOption('page-size', $paper);
|
||||
if($orientation){
|
||||
$this->snappy->setOption('orientation', $orientation);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the orientation (default portrait)
|
||||
*
|
||||
* @param string $orientation
|
||||
* @return $this
|
||||
*/
|
||||
public function setOrientation($orientation)
|
||||
{
|
||||
$this->snappy->setOption('orientation', $orientation);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide warnings
|
||||
*
|
||||
* @param bool $warnings
|
||||
* @return $this
|
||||
* @deprecated
|
||||
*/
|
||||
public function setWarnings($warnings)
|
||||
{
|
||||
//Doesn't do anything
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function setOption($name, $value)
|
||||
{
|
||||
if ($value instanceof Renderable) {
|
||||
$value = $value->render();
|
||||
}
|
||||
$this->snappy->setOption($name, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return $this
|
||||
*/
|
||||
public function setOptions($options)
|
||||
{
|
||||
$this->snappy->setOptions($options);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a HTML string
|
||||
*
|
||||
* @param Array|string|Renderable $html
|
||||
* @return $this
|
||||
*/
|
||||
public function loadHTML($html)
|
||||
{
|
||||
if ($html instanceof Renderable) {
|
||||
$html = $html->render();
|
||||
}
|
||||
$this->html = $html;
|
||||
$this->file = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a HTML file
|
||||
*
|
||||
* @param string $file
|
||||
* @return $this
|
||||
*/
|
||||
public function loadFile($file)
|
||||
{
|
||||
$this->html = null;
|
||||
$this->file = $file;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a View and convert to HTML
|
||||
*
|
||||
* @param string $view
|
||||
* @param array $data
|
||||
* @param array $mergeData
|
||||
* @return $this
|
||||
*/
|
||||
public function loadView($view, $data = array(), $mergeData = array())
|
||||
{
|
||||
$view = View::make($view, $data, $mergeData);
|
||||
|
||||
return $this->loadHTML($view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the PDF as a string.
|
||||
*
|
||||
* @return string The rendered PDF as string
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
if ($this->html)
|
||||
{
|
||||
return $this->snappy->getOutputFromHtml($this->html, $this->options);
|
||||
}
|
||||
|
||||
if ($this->file)
|
||||
{
|
||||
return $this->snappy->getOutput($this->file, $this->options);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('PDF Generator requires a html or file in order to produce output.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the PDF to a file
|
||||
*
|
||||
* @param $filename
|
||||
* @return $this
|
||||
*/
|
||||
public function save($filename, $overwrite = false)
|
||||
{
|
||||
|
||||
if ($this->html)
|
||||
{
|
||||
$this->snappy->generateFromHtml($this->html, $filename, $this->options, $overwrite);
|
||||
}
|
||||
elseif ($this->file)
|
||||
{
|
||||
$this->snappy->generate($this->file, $filename, $this->options, $overwrite);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the PDF downloadable by the user
|
||||
*
|
||||
* @param string $filename
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function download($filename = 'document.pdf')
|
||||
{
|
||||
return new Response($this->output(), 200, array(
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="'.$filename.'"'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a response with the PDF to show in the browser
|
||||
*
|
||||
* @param string $filename
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function inline($filename = 'document.pdf')
|
||||
{
|
||||
return new Response($this->output(), 200, array(
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'inline; filename="'.$filename.'"',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a response with the PDF to show in the browser
|
||||
*
|
||||
* @param string $filename
|
||||
* @return \Symfony\Component\HttpFoundation\StreamedResponse
|
||||
* @deprecated use inline() instead
|
||||
*/
|
||||
public function stream($filename = 'document.pdf')
|
||||
{
|
||||
return new StreamedResponse(function() {
|
||||
echo $this->output();
|
||||
}, 200, array(
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'inline; filename="'.$filename.'"',
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Snappy instance.
|
||||
*
|
||||
* Also shortcut's
|
||||
* ->html => loadHtml
|
||||
* ->view => loadView
|
||||
* ->file => loadFile
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($name, array $arguments)
|
||||
{
|
||||
$method = 'load' . ucfirst($name);
|
||||
if (method_exists($this, $method))
|
||||
{
|
||||
return call_user_func_array(array($this, $method), $arguments);
|
||||
}
|
||||
|
||||
return call_user_func_array (array($this->snappy, $name), $arguments);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php namespace Barryvdh\Snappy;
|
||||
|
||||
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
|
||||
|
||||
class ServiceProvider extends BaseServiceProvider {
|
||||
|
||||
/**
|
||||
* Indicates if loading of the provider is deferred.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $defer = false;
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$configPath = __DIR__ . '/../config/snappy.php';
|
||||
$this->mergeConfigFrom($configPath, 'snappy');
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
$configPath = __DIR__ . '/../config/snappy.php';
|
||||
$this->publishes([$configPath => config_path('snappy.php')], 'config');
|
||||
|
||||
if($this->app['config']->get('snappy.pdf.enabled')){
|
||||
$this->app->bind('snappy.pdf', function($app)
|
||||
{
|
||||
$binary = $app['config']->get('snappy.pdf.binary', '/usr/local/bin/wkhtmltopdf');
|
||||
$options = $app['config']->get('snappy.pdf.options', array());
|
||||
$env = $app['config']->get('snappy.pdf.env', array());
|
||||
$timeout = $app['config']->get('snappy.pdf.timeout', false);
|
||||
|
||||
$snappy = new IlluminateSnappyPdf($app['files'], $binary, $options, $env);
|
||||
if (false !== $timeout) {
|
||||
$snappy->setTimeout($timeout);
|
||||
}
|
||||
|
||||
return $snappy;
|
||||
});
|
||||
|
||||
$this->app->bind('snappy.pdf.wrapper', function($app)
|
||||
{
|
||||
return new PdfWrapper($app['snappy.pdf']);
|
||||
});
|
||||
$this->app->alias('snappy.pdf.wrapper', 'Barryvdh\Snappy\PdfWrapper');
|
||||
}
|
||||
|
||||
|
||||
if($this->app['config']->get('snappy.image.enabled')){
|
||||
$this->app->bind('snappy.image', function($app)
|
||||
{
|
||||
$binary = $app['config']->get('snappy.image.binary', '/usr/local/bin/wkhtmltoimage');
|
||||
$options = $app['config']->get('snappy.image.options', array());
|
||||
$env = $app['config']->get('snappy.image.env', array());
|
||||
$timeout = $app['config']->get('snappy.image.timeout', false);
|
||||
|
||||
$image = new IlluminateSnappyImage($app['files'], $binary, $options, $env);
|
||||
if (false !== $timeout) {
|
||||
$image->setTimeout($timeout);
|
||||
}
|
||||
|
||||
return $image;
|
||||
});
|
||||
|
||||
$this->app->bind('snappy.image.wrapper', function($app)
|
||||
{
|
||||
return new ImageWrapper($app['snappy.image']);
|
||||
});
|
||||
$this->app->alias('snappy.image.wrapper', 'Barryvdh\Snappy\ImageWrapper');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provides()
|
||||
{
|
||||
return array('snappy.pdf', 'snappy.pdf.wrapper', 'snappy.image', 'snappy.image.wrapper');
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../nesbot/carbon/bin/carbon)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon';
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../doctrine/dbal/bin/doctrine-dbal)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/doctrine/dbal/bin/doctrine-dbal');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/doctrine/dbal/bin/doctrine-dbal';
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../symfony/error-handler/Resources/bin/patch-type-declarations)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/symfony/error-handler/Resources/bin/patch-type-declarations';
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../nikic/php-parser/bin/php-parse)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse';
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../squizlabs/php_codesniffer/bin/phpcbf)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcbf';
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../squizlabs/php_codesniffer/bin/phpcs)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/squizlabs/php_codesniffer/bin/phpcs';
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../phpstan/phpstan/phpstan)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan';
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../phpstan/phpstan/phpstan.phar)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/phpstan/phpstan/phpstan.phar';
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../phpunit/phpunit/phpunit)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
$GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] = $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'] = array(realpath(__DIR__ . '/..'.'/phpunit/phpunit/phpunit'));
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = 'phpvfscomposer://'.$this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
$data = str_replace('__DIR__', var_export(dirname($this->realpath), true), $data);
|
||||
$data = str_replace('__FILE__', var_export($this->realpath, true), $data);
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../psy/psysh/bin/psysh)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/psy/psysh/bin/psysh');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/psy/psysh/bin/psysh';
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../symfony/var-dumper/Resources/bin/var-dump-server)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server';
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Support bash to support `source` with fallback on $0 if this does not run with bash
|
||||
# https://stackoverflow.com/a/35006505/6512
|
||||
selfArg="$BASH_SOURCE"
|
||||
if [ -z "$selfArg" ]; then
|
||||
selfArg="$0"
|
||||
fi
|
||||
|
||||
self=$(realpath $selfArg 2> /dev/null)
|
||||
if [ -z "$self" ]; then
|
||||
self="$selfArg"
|
||||
fi
|
||||
|
||||
dir=$(cd "${self%[/\\]*}" > /dev/null; cd '../h4cc/wkhtmltoimage-amd64/bin' && pwd)
|
||||
|
||||
if [ -d /proc/cygdrive ]; then
|
||||
case $(which php) in
|
||||
$(readlink -n /proc/cygdrive)/*)
|
||||
# We are in Cygwin using Windows php, so the path must be translated
|
||||
dir=$(cygpath -m "$dir");
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)"
|
||||
|
||||
# If bash is sourcing this file, we have to source the target as well
|
||||
bashSource="$BASH_SOURCE"
|
||||
if [ -n "$bashSource" ]; then
|
||||
if [ "$bashSource" != "$0" ]; then
|
||||
source "${dir}/wkhtmltoimage-amd64" "$@"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
"${dir}/wkhtmltoimage-amd64" "$@"
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Support bash to support `source` with fallback on $0 if this does not run with bash
|
||||
# https://stackoverflow.com/a/35006505/6512
|
||||
selfArg="$BASH_SOURCE"
|
||||
if [ -z "$selfArg" ]; then
|
||||
selfArg="$0"
|
||||
fi
|
||||
|
||||
self=$(realpath $selfArg 2> /dev/null)
|
||||
if [ -z "$self" ]; then
|
||||
self="$selfArg"
|
||||
fi
|
||||
|
||||
dir=$(cd "${self%[/\\]*}" > /dev/null; cd '../h4cc/wkhtmltopdf-amd64/bin' && pwd)
|
||||
|
||||
if [ -d /proc/cygdrive ]; then
|
||||
case $(which php) in
|
||||
$(readlink -n /proc/cygdrive)/*)
|
||||
# We are in Cygwin using Windows php, so the path must be translated
|
||||
dir=$(cygpath -m "$dir");
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)"
|
||||
|
||||
# If bash is sourcing this file, we have to source the target as well
|
||||
bashSource="$BASH_SOURCE"
|
||||
if [ -n "$bashSource" ]; then
|
||||
if [ "$bashSource" != "$0" ]; then
|
||||
source "${dir}/wkhtmltopdf-amd64" "$@"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
"${dir}/wkhtmltopdf-amd64" "$@"
|
||||
@@ -0,0 +1,445 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.11.0](https://github.com/brick/math/releases/tag/0.11.0) - 2023-01-16
|
||||
|
||||
💥 **Breaking changes**
|
||||
|
||||
- Minimum PHP version is now 8.0
|
||||
- Methods accepting a union of types are now strongly typed<sup>*</sup>
|
||||
- `MathException` now extends `Exception` instead of `RuntimeException`
|
||||
|
||||
<sup>* You may now run into type errors if you were passing `Stringable` objects to `of()` or any of the methods
|
||||
internally calling `of()`, with `strict_types` enabled. You can fix this by casting `Stringable` objects to `string`
|
||||
first.</sup>
|
||||
|
||||
## [0.10.2](https://github.com/brick/math/releases/tag/0.10.2) - 2022-08-11
|
||||
|
||||
👌 **Improvements**
|
||||
|
||||
- `BigRational::toFloat()` now simplifies the fraction before performing division (#73) thanks to @olsavmic
|
||||
|
||||
## [0.10.1](https://github.com/brick/math/releases/tag/0.10.1) - 2022-08-02
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::gcdMultiple()` returns the GCD of multiple `BigInteger` numbers
|
||||
|
||||
## [0.10.0](https://github.com/brick/math/releases/tag/0.10.0) - 2022-06-18
|
||||
|
||||
💥 **Breaking changes**
|
||||
|
||||
- Minimum PHP version is now 7.4
|
||||
|
||||
## [0.9.3](https://github.com/brick/math/releases/tag/0.9.3) - 2021-08-15
|
||||
|
||||
🚀 **Compatibility with PHP 8.1**
|
||||
|
||||
- Support for custom object serialization; this removes a warning on PHP 8.1 due to the `Serializable` interface being deprecated (#60) thanks @TRowbotham
|
||||
|
||||
## [0.9.2](https://github.com/brick/math/releases/tag/0.9.2) - 2021-01-20
|
||||
|
||||
🐛 **Bug fix**
|
||||
|
||||
- Incorrect results could be returned when using the BCMath calculator, with a default scale set with `bcscale()`, on PHP >= 7.2 (#55).
|
||||
|
||||
## [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.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.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.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 anymore
|
||||
- `toBigRational()` does not simplify the fraction anymore; 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` anymore, 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.
|
||||
|
||||
+20
@@ -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,34 @@
|
||||
{
|
||||
"name": "brick/math",
|
||||
"description": "Arbitrary-precision arithmetic library",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"Brick",
|
||||
"Math",
|
||||
"Arbitrary-precision",
|
||||
"Arithmetic",
|
||||
"BigInteger",
|
||||
"BigDecimal",
|
||||
"BigRational",
|
||||
"Bignum"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0",
|
||||
"php-coveralls/php-coveralls": "^2.2",
|
||||
"vimeo/psalm": "5.0.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Brick\\Math\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Brick\\Math\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,786 @@
|
||||
<?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.
|
||||
*/
|
||||
private string $value;
|
||||
|
||||
/**
|
||||
* The scale (number of digits after the decimal point) of this decimal number.
|
||||
*
|
||||
* This must be zero or more.
|
||||
*/
|
||||
private int $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.
|
||||
*
|
||||
* @throws MathException If the value cannot be converted to a BigDecimal.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function of(BigNumber|int|float|string $value) : BigDecimal
|
||||
{
|
||||
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.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the scale is negative.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function ofUnscaledValue(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
||||
*/
|
||||
public function plus(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
||||
*/
|
||||
public function minus(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
|
||||
*/
|
||||
public function multipliedBy(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @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(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @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(BigNumber|int|float|string $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`.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
||||
*/
|
||||
public function quotient(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
||||
*/
|
||||
public function remainder(BigNumber|int|float|string $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(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function abs() : BigDecimal
|
||||
{
|
||||
return $this->isNegative() ? $this->negated() : $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the negated value of this number.
|
||||
*/
|
||||
public function negated() : BigDecimal
|
||||
{
|
||||
return new BigDecimal(Calculator::get()->neg($this->value), $this->scale);
|
||||
}
|
||||
|
||||
public function compareTo(BigNumber|int|float|string $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);
|
||||
}
|
||||
|
||||
public function getSign() : int
|
||||
{
|
||||
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
|
||||
}
|
||||
|
||||
public function getUnscaledValue() : BigInteger
|
||||
{
|
||||
return self::newBigInteger($this->value);
|
||||
}
|
||||
|
||||
public function getScale() : int
|
||||
{
|
||||
return $this->scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representing the integral part of this decimal number.
|
||||
*
|
||||
* Example: `-123.456` => `-123`.
|
||||
*/
|
||||
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` => ''.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function hasNonZeroFractionalPart() : bool
|
||||
{
|
||||
return $this->getFractionalPart() !== \str_repeat('0', $this->scale);
|
||||
}
|
||||
|
||||
public function toBigInteger() : BigInteger
|
||||
{
|
||||
$zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0);
|
||||
|
||||
return self::newBigInteger($zeroScaleDecimal->value);
|
||||
}
|
||||
|
||||
public function toBigDecimal() : BigDecimal
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toBigRational() : BigRational
|
||||
{
|
||||
$numerator = self::newBigInteger($this->value);
|
||||
$denominator = self::newBigInteger('1' . \str_repeat('0', $this->scale));
|
||||
|
||||
return self::newBigRational($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
||||
{
|
||||
if ($scale === $this->scale) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode);
|
||||
}
|
||||
|
||||
public function toInt() : int
|
||||
{
|
||||
return $this->toBigInteger()->toInt();
|
||||
}
|
||||
|
||||
public function toFloat() : float
|
||||
{
|
||||
return (float) (string) $this;
|
||||
}
|
||||
|
||||
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 for serializing the object and SHOULD NOT be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array{value: string, scale: int}
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return ['value' => $this->value, 'scale' => $this->scale];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-suppress RedundantPropertyInitializationCheck
|
||||
*
|
||||
* @param array{value: string, scale: int} $data
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
if (isset($this->value)) {
|
||||
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
|
||||
}
|
||||
|
||||
$this->value = $data['value'];
|
||||
$this->scale = $data['scale'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required by interface Serializable and SHOULD NOT be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return array{string, 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];
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,512 @@
|
||||
<?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
|
||||
*
|
||||
* @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(BigNumber|int|float|string $value) : BigNumber
|
||||
{
|
||||
if ($value instanceof BigNumber) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (\is_int($value)) {
|
||||
return new BigInteger((string) $value);
|
||||
}
|
||||
|
||||
$value = \is_float($value) ? self::floatToString($value) : $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 fn(string $value): ?string => (($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
|
||||
*
|
||||
* @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 BigInteger's protected constructor from sibling classes.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
protected function newBigInteger(string $value) : BigInteger
|
||||
{
|
||||
return new BigInteger($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy method to access BigDecimal's protected constructor from sibling classes.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal
|
||||
{
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy method to access BigRational's protected constructor from sibling classes.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-pure
|
||||
*/
|
||||
protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational
|
||||
{
|
||||
return new BigRational($numerator, $denominator, $checkDenominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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(BigNumber|int|float|string ...$values) : static
|
||||
{
|
||||
$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.
|
||||
*
|
||||
* @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(BigNumber|int|float|string ...$values) : static
|
||||
{
|
||||
$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.
|
||||
*
|
||||
* @throws \InvalidArgumentException If no values are given.
|
||||
* @throws MathException If an argument is not valid.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function sum(BigNumber|int|float|string ...$values) : static
|
||||
{
|
||||
/** @var static|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...)
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public function isEqualTo(BigNumber|int|float|string $that) : bool
|
||||
{
|
||||
return $this->compareTo($that) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly lower than the given one.
|
||||
*/
|
||||
public function isLessThan(BigNumber|int|float|string $that) : bool
|
||||
{
|
||||
return $this->compareTo($that) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is lower than or equal to the given one.
|
||||
*/
|
||||
public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool
|
||||
{
|
||||
return $this->compareTo($that) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly greater than the given one.
|
||||
*/
|
||||
public function isGreaterThan(BigNumber|int|float|string $that) : bool
|
||||
{
|
||||
return $this->compareTo($that) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is greater than or equal to the given one.
|
||||
*/
|
||||
public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool
|
||||
{
|
||||
return $this->compareTo($that) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number equals zero.
|
||||
*/
|
||||
public function isZero() : bool
|
||||
{
|
||||
return $this->getSign() === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly negative.
|
||||
*/
|
||||
public function isNegative() : bool
|
||||
{
|
||||
return $this->getSign() < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is negative or zero.
|
||||
*/
|
||||
public function isNegativeOrZero() : bool
|
||||
{
|
||||
return $this->getSign() <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly positive.
|
||||
*/
|
||||
public function isPositive() : bool
|
||||
{
|
||||
return $this->getSign() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is positive or zero.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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(BigNumber|int|float|string $that) : int;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigInteger.
|
||||
*
|
||||
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
|
||||
*/
|
||||
abstract public function toBigInteger() : BigInteger;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigDecimal.
|
||||
*
|
||||
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
|
||||
*/
|
||||
abstract public function toBigDecimal() : BigDecimal;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigRational.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
abstract public function __toString() : string;
|
||||
|
||||
public function jsonSerialize() : string
|
||||
{
|
||||
return $this->__toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
<?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.
|
||||
*/
|
||||
private BigInteger $numerator;
|
||||
|
||||
/**
|
||||
* The denominator. Always strictly positive.
|
||||
*/
|
||||
private BigInteger $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.
|
||||
*
|
||||
* @throws MathException If the value cannot be converted to a BigRational.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function of(BigNumber|int|float|string $value) : BigRational
|
||||
{
|
||||
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.
|
||||
*
|
||||
* @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(
|
||||
BigNumber|int|float|string $numerator,
|
||||
BigNumber|int|float|string $denominator,
|
||||
) : BigRational {
|
||||
$numerator = BigInteger::of($numerator);
|
||||
$denominator = BigInteger::of($denominator);
|
||||
|
||||
return new BigRational($numerator, $denominator, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigRational representing zero.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
public function getNumerator() : BigInteger
|
||||
{
|
||||
return $this->numerator;
|
||||
}
|
||||
|
||||
public function getDenominator() : BigInteger
|
||||
{
|
||||
return $this->denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quotient of the division of the numerator by the denominator.
|
||||
*/
|
||||
public function quotient() : BigInteger
|
||||
{
|
||||
return $this->numerator->quotient($this->denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remainder of the division of the numerator by the denominator.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @throws MathException If the number is not valid.
|
||||
*/
|
||||
public function plus(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @throws MathException If the number is not valid.
|
||||
*/
|
||||
public function minus(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @throws MathException If the multiplier is not a valid number.
|
||||
*/
|
||||
public function multipliedBy(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid number, or is zero.
|
||||
*/
|
||||
public function dividedBy(BigNumber|int|float|string $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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public function abs() : BigRational
|
||||
{
|
||||
return new BigRational($this->numerator->abs(), $this->denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the negated value of this BigRational.
|
||||
*/
|
||||
public function negated() : BigRational
|
||||
{
|
||||
return new BigRational($this->numerator->negated(), $this->denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the simplified value of this 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);
|
||||
}
|
||||
|
||||
public function compareTo(BigNumber|int|float|string $that) : int
|
||||
{
|
||||
return $this->minus($that)->getSign();
|
||||
}
|
||||
|
||||
public function getSign() : int
|
||||
{
|
||||
return $this->numerator->getSign();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function toBigDecimal() : BigDecimal
|
||||
{
|
||||
return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator);
|
||||
}
|
||||
|
||||
public function toBigRational() : BigRational
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
||||
{
|
||||
return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
|
||||
}
|
||||
|
||||
public function toInt() : int
|
||||
{
|
||||
return $this->toBigInteger()->toInt();
|
||||
}
|
||||
|
||||
public function toFloat() : float
|
||||
{
|
||||
$simplified = $this->simplified();
|
||||
return $simplified->numerator->toFloat() / $simplified->denominator->toFloat();
|
||||
}
|
||||
|
||||
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 for serializing the object and SHOULD NOT be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array{numerator: BigInteger, denominator: BigInteger}
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return ['numerator' => $this->numerator, 'denominator' => $this->denominator];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-suppress RedundantPropertyInitializationCheck
|
||||
*
|
||||
* @param array{numerator: BigInteger, denominator: BigInteger} $data
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
if (isset($this->numerator)) {
|
||||
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
|
||||
}
|
||||
|
||||
$this->numerator = $data['numerator'];
|
||||
$this->denominator = $data['denominator'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required by interface Serializable and SHOULD NOT be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a division by zero occurs.
|
||||
*/
|
||||
class DivisionByZeroException extends MathException
|
||||
{
|
||||
/**
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function divisionByZero() : DivisionByZeroException
|
||||
{
|
||||
return new self('Division by zero.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function modulusMustNotBeZero() : DivisionByZeroException
|
||||
{
|
||||
return new self('The modulus must not be zero.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function denominatorMustNotBeZero() : DivisionByZeroException
|
||||
{
|
||||
return new self('The denominator of a rational number cannot be zero.');
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
use Brick\Math\BigInteger;
|
||||
|
||||
/**
|
||||
* Exception thrown when an integer overflow occurs.
|
||||
*/
|
||||
class IntegerOverflowException extends MathException
|
||||
{
|
||||
/**
|
||||
* @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,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Base class for all math exceptions.
|
||||
*/
|
||||
class MathException extends \Exception
|
||||
{
|
||||
}
|
||||
+12
@@ -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
|
||||
{
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?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.
|
||||
*
|
||||
* @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));
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* @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,676 @@
|
||||
<?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.
|
||||
*/
|
||||
private static ?Calculator $instance = null;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return array{bool, bool, string, 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.
|
||||
*/
|
||||
final public function abs(string $n) : string
|
||||
{
|
||||
return ($n[0] === '-') ? \substr($n, 1) : $n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negates a number.
|
||||
*/
|
||||
final public function neg(string $n) : string
|
||||
{
|
||||
if ($n === '0') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
if ($n[0] === '-') {
|
||||
return \substr($n, 1);
|
||||
}
|
||||
|
||||
return '-' . $n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two numbers.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
abstract public function add(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Subtracts two numbers.
|
||||
*/
|
||||
abstract public function sub(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Multiplies two numbers.
|
||||
*/
|
||||
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 array{string, 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 $b The modulus; must not be zero.
|
||||
*/
|
||||
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 $m The modulus; must not be negative or zero.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
[$g, $x] = $this->gcdExtended($modVal, $m);
|
||||
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{string, string, string} GCD, X, Y
|
||||
*/
|
||||
private function gcdExtended(string $a, string $b) : array
|
||||
{
|
||||
if ($a === '0') {
|
||||
return [$b, '0', '1'];
|
||||
}
|
||||
|
||||
[$gcd, $x1, $y1] = $this->gcdExtended($this->mod($b, $a), $a);
|
||||
|
||||
$x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1));
|
||||
$y = $x1;
|
||||
|
||||
return [$gcd, $x, $y];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the rounding mode is invalid.
|
||||
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
|
||||
*
|
||||
* @psalm-suppress ImpureFunctionCall
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function xor(string $a, string $b) : string
|
||||
{
|
||||
return $this->bitwise('xor', $a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a bitwise operation on a decimal number.
|
||||
*
|
||||
* @param 'and'|'or'|'xor' $operator The operator to use.
|
||||
* @param string $a The left operand.
|
||||
* @param string $b The right operand.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $number A positive, binary number.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
<?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
|
||||
{
|
||||
public function add(string $a, string $b) : string
|
||||
{
|
||||
return \bcadd($a, $b, 0);
|
||||
}
|
||||
|
||||
public function sub(string $a, string $b) : string
|
||||
{
|
||||
return \bcsub($a, $b, 0);
|
||||
}
|
||||
|
||||
public function mul(string $a, string $b) : string
|
||||
{
|
||||
return \bcmul($a, $b, 0);
|
||||
}
|
||||
|
||||
public function divQ(string $a, string $b) : string
|
||||
{
|
||||
return \bcdiv($a, $b, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress InvalidNullableReturnType
|
||||
* @psalm-suppress NullableReturnStatement
|
||||
*/
|
||||
public function divR(string $a, string $b) : string
|
||||
{
|
||||
return \bcmod($a, $b, 0);
|
||||
}
|
||||
|
||||
public function divQR(string $a, string $b) : array
|
||||
{
|
||||
$q = \bcdiv($a, $b, 0);
|
||||
$r = \bcmod($a, $b, 0);
|
||||
|
||||
assert($r !== null);
|
||||
|
||||
return [$q, $r];
|
||||
}
|
||||
|
||||
public function pow(string $a, int $e) : string
|
||||
{
|
||||
return \bcpow($a, (string) $e, 0);
|
||||
}
|
||||
|
||||
public function modPow(string $base, string $exp, string $mod) : string
|
||||
{
|
||||
return \bcpowmod($base, $exp, $mod, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress InvalidNullableReturnType
|
||||
* @psalm-suppress NullableReturnStatement
|
||||
*/
|
||||
public function sqrt(string $n) : string
|
||||
{
|
||||
return \bcsqrt($n, 0);
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?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
|
||||
{
|
||||
public function add(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_add($a, $b));
|
||||
}
|
||||
|
||||
public function sub(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_sub($a, $b));
|
||||
}
|
||||
|
||||
public function mul(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_mul($a, $b));
|
||||
}
|
||||
|
||||
public function divQ(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_div_q($a, $b));
|
||||
}
|
||||
|
||||
public function divR(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_div_r($a, $b));
|
||||
}
|
||||
|
||||
public function divQR(string $a, string $b) : array
|
||||
{
|
||||
[$q, $r] = \gmp_div_qr($a, $b);
|
||||
|
||||
return [
|
||||
\gmp_strval($q),
|
||||
\gmp_strval($r)
|
||||
];
|
||||
}
|
||||
|
||||
public function pow(string $a, int $e) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_pow($a, $e));
|
||||
}
|
||||
|
||||
public function modInverse(string $x, string $m) : ?string
|
||||
{
|
||||
$result = \gmp_invert($x, $m);
|
||||
|
||||
if ($result === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \gmp_strval($result);
|
||||
}
|
||||
|
||||
public function modPow(string $base, string $exp, string $mod) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_powm($base, $exp, $mod));
|
||||
}
|
||||
|
||||
public function gcd(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_gcd($a, $b));
|
||||
}
|
||||
|
||||
public function fromBase(string $number, int $base) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_init($number, $base));
|
||||
}
|
||||
|
||||
public function toBase(string $number, int $base) : string
|
||||
{
|
||||
return \gmp_strval($number, $base);
|
||||
}
|
||||
|
||||
public function and(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_and($a, $b));
|
||||
}
|
||||
|
||||
public function or(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_or($a, $b));
|
||||
}
|
||||
|
||||
public function xor(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_xor($a, $b));
|
||||
}
|
||||
|
||||
public function sqrt(string $n) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_sqrt($n));
|
||||
}
|
||||
}
|
||||
+581
@@ -0,0 +1,581 @@
|
||||
<?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.
|
||||
*
|
||||
* In 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)
|
||||
*/
|
||||
private int $maxDigits;
|
||||
|
||||
/**
|
||||
* @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.');
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function sub(string $a, string $b) : string
|
||||
{
|
||||
return $this->add($a, $this->neg($b));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function divQ(string $a, string $b) : string
|
||||
{
|
||||
return $this->divQR($a, $b)[0];
|
||||
}
|
||||
|
||||
public function divR(string $a, string $b): string
|
||||
{
|
||||
return $this->divQR($a, $b)[1];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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/
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return array{string, string, 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,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Carbon
|
||||
|
||||
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,14 @@
|
||||
# carbonphp/carbon-doctrine-types
|
||||
|
||||
Types to use Carbon in Doctrine
|
||||
|
||||
## Documentation
|
||||
|
||||
[Check how to use in the official Carbon documentation](https://carbon.nesbot.com/symfony/)
|
||||
|
||||
This package is an externalization of [src/Carbon/Doctrine](https://github.com/briannesbitt/Carbon/tree/2.71.0/src/Carbon/Doctrine)
|
||||
from `nestbot/carbon` package.
|
||||
|
||||
Externalization allows to better deal with different versions of dbal. With
|
||||
version 4.0 of dbal, it no longer sustainable to be compatible with all version
|
||||
using a single code.
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "carbonphp/carbon-doctrine-types",
|
||||
"description": "Types to use Carbon in Doctrine",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"date",
|
||||
"time",
|
||||
"DateTime",
|
||||
"Carbon",
|
||||
"Doctrine"
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/dbal": "^3.7.0",
|
||||
"nesbot/carbon": "^2.71.0 || ^3.0.0",
|
||||
"phpunit/phpunit": "^10.3"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/dbal": "<3.7.0 || >=4.0.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "KyleKatarn",
|
||||
"email": "kylekatarnls@gmail.com"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Carbon\Doctrine;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
interface CarbonDoctrineType
|
||||
{
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform);
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform);
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform);
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Carbon\Doctrine;
|
||||
|
||||
class CarbonImmutableType extends DateTimeImmutableType implements CarbonDoctrineType
|
||||
{
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Carbon\Doctrine;
|
||||
|
||||
class CarbonType extends DateTimeType implements CarbonDoctrineType
|
||||
{
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Carbon\Doctrine;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Platforms\DB2Platform;
|
||||
use Doctrine\DBAL\Platforms\OraclePlatform;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
use Doctrine\DBAL\Platforms\SQLServerPlatform;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @template T of CarbonInterface
|
||||
*/
|
||||
trait CarbonTypeConverter
|
||||
{
|
||||
/**
|
||||
* This property differentiates types installed by carbonphp/carbon-doctrine-types
|
||||
* from the ones embedded previously in nesbot/carbon source directly.
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
public bool $external = true;
|
||||
|
||||
/**
|
||||
* @return class-string<T>
|
||||
*/
|
||||
protected function getCarbonClassName(): string
|
||||
{
|
||||
return Carbon::class;
|
||||
}
|
||||
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
|
||||
{
|
||||
$precision = min(
|
||||
$fieldDeclaration['precision'] ?? DateTimeDefaultPrecision::get(),
|
||||
$this->getMaximumPrecision($platform),
|
||||
);
|
||||
|
||||
$type = parent::getSQLDeclaration($fieldDeclaration, $platform);
|
||||
|
||||
if (!$precision) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
if (str_contains($type, '(')) {
|
||||
return preg_replace('/\(\d+\)/', "($precision)", $type);
|
||||
}
|
||||
|
||||
[$before, $after] = explode(' ', "$type ");
|
||||
|
||||
return trim("$before($precision) $after");
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*
|
||||
* @return T|null
|
||||
*/
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
$class = $this->getCarbonClassName();
|
||||
|
||||
if ($value === null || is_a($value, $class)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof DateTimeInterface) {
|
||||
return $class::instance($value);
|
||||
}
|
||||
|
||||
$date = null;
|
||||
$error = null;
|
||||
|
||||
try {
|
||||
$date = $class::parse($value);
|
||||
} catch (Exception $exception) {
|
||||
$error = $exception;
|
||||
}
|
||||
|
||||
if (!$date) {
|
||||
throw ConversionException::conversionFailedFormat(
|
||||
$value,
|
||||
$this->getTypeName(),
|
||||
'Y-m-d H:i:s.u or any format supported by '.$class.'::parse()',
|
||||
$error
|
||||
);
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
|
||||
{
|
||||
if ($value === null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof DateTimeInterface) {
|
||||
return $value->format('Y-m-d H:i:s.u');
|
||||
}
|
||||
|
||||
throw ConversionException::conversionFailedInvalidType(
|
||||
$value,
|
||||
$this->getTypeName(),
|
||||
['null', 'DateTime', 'Carbon']
|
||||
);
|
||||
}
|
||||
|
||||
private function getTypeName(): string
|
||||
{
|
||||
$chunks = explode('\\', static::class);
|
||||
$type = preg_replace('/Type$/', '', end($chunks));
|
||||
|
||||
return strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $type));
|
||||
}
|
||||
|
||||
private function getMaximumPrecision(AbstractPlatform $platform): int
|
||||
{
|
||||
if ($platform instanceof DB2Platform) {
|
||||
return 12;
|
||||
}
|
||||
|
||||
if ($platform instanceof OraclePlatform) {
|
||||
return 9;
|
||||
}
|
||||
|
||||
if ($platform instanceof SQLServerPlatform || $platform instanceof SqlitePlatform) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Carbon\Doctrine;
|
||||
|
||||
class DateTimeDefaultPrecision
|
||||
{
|
||||
private static $precision = 6;
|
||||
|
||||
/**
|
||||
* Change the default Doctrine datetime and datetime_immutable precision.
|
||||
*
|
||||
* @param int $precision
|
||||
*/
|
||||
public static function set(int $precision): void
|
||||
{
|
||||
self::$precision = $precision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default Doctrine datetime and datetime_immutable precision.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function get(): int
|
||||
{
|
||||
return self::$precision;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Carbon\Doctrine;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Doctrine\DBAL\Types\VarDateTimeImmutableType;
|
||||
|
||||
class DateTimeImmutableType extends VarDateTimeImmutableType implements CarbonDoctrineType
|
||||
{
|
||||
/** @use CarbonTypeConverter<CarbonImmutable> */
|
||||
use CarbonTypeConverter;
|
||||
|
||||
/**
|
||||
* @return class-string<CarbonImmutable>
|
||||
*/
|
||||
protected function getCarbonClassName(): string
|
||||
{
|
||||
return CarbonImmutable::class;
|
||||
}
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Carbon\Doctrine;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Doctrine\DBAL\Types\VarDateTimeType;
|
||||
|
||||
class DateTimeType extends VarDateTimeType implements CarbonDoctrineType
|
||||
{
|
||||
/** @use CarbonTypeConverter<Carbon> */
|
||||
use CarbonTypeConverter;
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
<?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
|
||||
{
|
||||
/** @var \Closure(string):void */
|
||||
private static $includeFile;
|
||||
|
||||
/** @var string|null */
|
||||
private $vendorDir;
|
||||
|
||||
// PSR-4
|
||||
/**
|
||||
* @var array<string, array<string, int>>
|
||||
*/
|
||||
private $prefixLengthsPsr4 = array();
|
||||
/**
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $prefixDirsPsr4 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
/**
|
||||
* List of PSR-0 prefixes
|
||||
*
|
||||
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
|
||||
*
|
||||
* @var array<string, array<string, list<string>>>
|
||||
*/
|
||||
private $prefixesPsr0 = array();
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
/** @var bool */
|
||||
private $useIncludePath = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $classMap = array();
|
||||
|
||||
/** @var bool */
|
||||
private $classMapAuthoritative = false;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $missingClasses = array();
|
||||
|
||||
/** @var string|null */
|
||||
private $apcuPrefix;
|
||||
|
||||
/**
|
||||
* @var array<string, self>
|
||||
*/
|
||||
private static $registeredLoaders = array();
|
||||
|
||||
/**
|
||||
* @param string|null $vendorDir
|
||||
*/
|
||||
public function __construct($vendorDir = null)
|
||||
{
|
||||
$this->vendorDir = $vendorDir;
|
||||
self::initializeIncludeClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string> Array of classname => path
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $classMap Class to filename map
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 list<string>|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
$paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
$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 list<string>|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
$paths = (array) $paths;
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
$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] = $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
$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 list<string>|string $paths The PSR-0 base directories
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 list<string>|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
|
||||
if (null === $this->vendorDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($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.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
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 true|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 keyed by their corresponding vendor directories.
|
||||
*
|
||||
* @return array<string, self>
|
||||
*/
|
||||
public static function getRegisteredLoaders()
|
||||
{
|
||||
return self::$registeredLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $ext
|
||||
* @return string|false
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
<?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;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use Composer\Semver\VersionParser;
|
||||
|
||||
/**
|
||||
* This class is copied in every Composer installed project and available to all
|
||||
*
|
||||
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
|
||||
*
|
||||
* To require its presence, you can require `composer-runtime-api ^2.0`
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
* @var array[]
|
||||
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static $installedByVendor = array();
|
||||
|
||||
/**
|
||||
* Returns a list of all package names which are present, either by being installed, replaced or provided
|
||||
*
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackages()
|
||||
{
|
||||
$packages = array();
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
$packages[] = array_keys($installed['versions']);
|
||||
}
|
||||
|
||||
if (1 === \count($packages)) {
|
||||
return $packages[0];
|
||||
}
|
||||
|
||||
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all package names with a specific type e.g. 'library'
|
||||
*
|
||||
* @param string $type
|
||||
* @return string[]
|
||||
* @psalm-return list<string>
|
||||
*/
|
||||
public static function getInstalledPackagesByType($type)
|
||||
{
|
||||
$packagesByType = array();
|
||||
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
foreach ($installed['versions'] as $name => $package) {
|
||||
if (isset($package['type']) && $package['type'] === $type) {
|
||||
$packagesByType[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $packagesByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package is installed
|
||||
*
|
||||
* This also returns true if the package name is provided or replaced by another package
|
||||
*
|
||||
* @param string $packageName
|
||||
* @param bool $includeDevRequirements
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInstalled($packageName, $includeDevRequirements = true)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (isset($installed['versions'][$packageName])) {
|
||||
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given package satisfies a version constraint
|
||||
*
|
||||
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
|
||||
*
|
||||
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
|
||||
*
|
||||
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
|
||||
* @param string $packageName
|
||||
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
|
||||
* @return bool
|
||||
*/
|
||||
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||
{
|
||||
$constraint = $parser->parseConstraints((string) $constraint);
|
||||
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||
|
||||
return $provided->matches($constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version constraint representing all the range(s) which are installed for a given package
|
||||
*
|
||||
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
|
||||
* whether a given version of a package is installed, and not just whether it exists
|
||||
*
|
||||
* @param string $packageName
|
||||
* @return string Version constraint usable with composer/semver
|
||||
*/
|
||||
public static function getVersionRanges($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ranges = array();
|
||||
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||
}
|
||||
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||
}
|
||||
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||
}
|
||||
|
||||
return implode(' || ', $ranges);
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||
*/
|
||||
public static function getPrettyVersion($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['pretty_version'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
|
||||
*/
|
||||
public static function getReference($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $installed['versions'][$packageName]['reference'];
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
|
||||
*/
|
||||
public static function getInstallPath($packageName)
|
||||
{
|
||||
foreach (self::getInstalled() as $installed) {
|
||||
if (!isset($installed['versions'][$packageName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
|
||||
*/
|
||||
public static function getRootPackage()
|
||||
{
|
||||
$installed = self::getInstalled();
|
||||
|
||||
return $installed[0]['root'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw installed.php data for custom implementations
|
||||
*
|
||||
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
|
||||
* @return array[]
|
||||
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||
*/
|
||||
public static function getRawData()
|
||||
{
|
||||
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
self::$installed = include __DIR__ . '/installed.php';
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
return self::$installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw data of all installed.php which are currently loaded for custom implementations
|
||||
*
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
public static function getAllRawData()
|
||||
{
|
||||
return self::getInstalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets you reload the static array from another file
|
||||
*
|
||||
* This is only useful for complex integrations in which a project needs to use
|
||||
* this class but then also needs to execute another project's autoloader in process,
|
||||
* and wants to ensure both projects have access to their version of installed.php.
|
||||
*
|
||||
* A typical case would be PHPUnit, where it would need to make sure it reads all
|
||||
* the data it needs from this class, then call reload() with
|
||||
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
|
||||
* the project in which it runs can then also use this class safely, without
|
||||
* interference between PHPUnit's dependencies and the project's dependencies.
|
||||
*
|
||||
* @param array[] $data A vendor/composer/installed.php data set
|
||||
* @return void
|
||||
*
|
||||
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
|
||||
*/
|
||||
public static function reload($data)
|
||||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||
*/
|
||||
private static function getInstalled()
|
||||
{
|
||||
if (null === self::$canGetVendors) {
|
||||
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||
}
|
||||
|
||||
$installed = array();
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = $required;
|
||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $installed[count($installed) - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === self::$installed) {
|
||||
// only require the installed.php file if this file is loaded from its dumped location,
|
||||
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require __DIR__ . '/installed.php';
|
||||
self::$installed = $required;
|
||||
} else {
|
||||
self::$installed = array();
|
||||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array()) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
return $installed;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
|
||||
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.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
// autoload_files.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
||||
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
||||
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
|
||||
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
|
||||
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
|
||||
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
|
||||
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
|
||||
'662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
|
||||
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||
'09f6b20656683369174dd6fa83b7e5fb' => $vendorDir . '/symfony/polyfill-uuid/bootstrap.php',
|
||||
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
'47e1160838b5e5a10346ac4084b58c23' => $vendorDir . '/laravel/prompts/src/helpers.php',
|
||||
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
|
||||
'35a6ad97d21e794e7e22a17d806652e4' => $vendorDir . '/nunomaduro/termwind/src/Functions.php',
|
||||
'801c31d8ed748cfa537fa45402288c95' => $vendorDir . '/psy/psysh/src/functions.php',
|
||||
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
|
||||
'e23faeee409e941dc9b4c80386209c39' => $vendorDir . '/laracasts/flash/src/Laracasts/Flash/functions.php',
|
||||
'265b4faa2b3a9766332744949e83bf97' => $vendorDir . '/laravel/framework/src/Illuminate/Collections/helpers.php',
|
||||
'c7a3c339e7e14b60e06a2d7fcce9476b' => $vendorDir . '/laravel/framework/src/Illuminate/Events/functions.php',
|
||||
'f57d353b41eb2e234b26064d63d8c5dd' => $vendorDir . '/laravel/framework/src/Illuminate/Filesystem/functions.php',
|
||||
'f0906e6318348a765ffb6eb24e0d0938' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php',
|
||||
'58571171fd5812e6e447dce228f52f4d' => $vendorDir . '/laravel/framework/src/Illuminate/Support/helpers.php',
|
||||
'17d016dc52a631c1e74d2eb8fdd57342' => $vendorDir . '/laravel/helpers/src/helpers.php',
|
||||
'f18cc91337d49233e5754e93f3ed9ec3' => $vendorDir . '/laravelcollective/html/src/helpers.php',
|
||||
'c72349b1fe8d0deeedd3a52e8aa814d8' => $vendorDir . '/mockery/mockery/library/helpers.php',
|
||||
'ce9671a430e4846b44e1c68c7611f9f5' => $vendorDir . '/mockery/mockery/library/Mockery.php',
|
||||
'9f394da3192a168c4633675768d80428' => $vendorDir . '/nwidart/laravel-modules/src/helpers.php',
|
||||
'9b38cf48e83f5d8f60375221cd213eee' => $vendorDir . '/phpstan/phpstan/bootstrap.php',
|
||||
'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php',
|
||||
'377b22b161c09ed6e5152de788ca020a' => $vendorDir . '/spatie/laravel-permission/src/helpers.php',
|
||||
'646961a8eab48144f6c03fc7c3185753' => $baseDir . '/app/Http/Helpers/Functions.php',
|
||||
'e3d6ff15e3a00433920bff18fbed7a52' => $baseDir . '/app/Http/Helpers/Finance.php',
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Milon\\Barcode' => array($vendorDir . '/milon/barcode/src'),
|
||||
'Laracasts\\Flash' => array($vendorDir . '/laracasts/flash/src'),
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user