PHPFUI
PHP Wrapper for the Foundation CSS Framework
PHPFUI, PHP Foundation User Interface, is a modern PHP library that produces HTML formated for Foundation. It does everything you need for a fully functional Foundation page, with the power of an OO language. It currently uses Foundation 6.6.
"I was surprised that people were prepared to write HTML. In my initial requirements for this thing, I had assumed, as an absolute pre-condition, that nobody would have to do HTML or deal with URLs. If you use the original World Wide Web program, you never see a URL or have to deal with HTML. You're presented with the raw information. You then input more information. So you are linking information to information--like using a word processor. That was a surprise to me--that people were prepared to painstakingly write HTML."
Sir Tim Berners-Lee, inventor of the World Wide Web
Using PHPFUI for view output will produce 100% valid HTML and insulate you from future changes to Foundation, your custom HMTL layouts, CSS and JS library changes. You write to an abstract concept (I want a checkbox here), and the library will output a checkbox formatted for Foundation. You can inherit from CheckBox and add your own take on a checkbox, and when the graphic designer decides they have the most awesome checkbox ever, you simply change your CheckBox class, and it is changed on every page system wide.
Don't write HTML by hand!
Usage
namespace PHPFUI;
$page = new Page();
$form = new Form($page);
$fieldset = new FieldSet('A basic input form');
$time = new Input\Time($page, 'time', 'Enter A Time in 15 minute increments');
$time->setRequired();
$date = new Input\Date($page, 'date', 'Pick A Date');
$fieldset->add(new MultiColumn($time, $date));
$fieldset->add(new Input\TextArea('text', 'Enter some text'));
$fieldset->add(new Submit());
$form->add($fieldset);
$page->add($form);
$page->addStyleSheet('/css/styles.css');
echo $page;
Installation Instructions
composer require phpfui/phpfui
Then run update.php from the vendor/phpfui/phpfui directory and supply the path to your public directory / the directory for the various JS and CSS files PHPFUI uses. This will copy all required public files into your public directory. For example:
php vendor/phpfui/phpfui/update.php public/PHPFUI
The PHPFUI library defaults to your-public-directory/PHPFUI, it can be overridden, but it is suggested to use PHPFUI to keep everything in one place. update.php should be run when ever you update PHPFUI.
Versioning
Versioning will match the Foundation versions for Major semantic versions. PHPUI will always support the most recent version of Foundation possible for the Major version. PHPFUI Minor version will include breaking changes and may incorporate changes for the latest version of Foundation. The PHPFUI Patch version will include non breaking changes or additions. So PHPFUI Version 6.0.0 would be the first version of the library, 6.0.1 would be the first patch of PHPFUI. Both should work with any Foundation 6.x version. PHPFUI 6.1.0 will track PHP 7.4 - 8.1, 6.2.0 will track 8.0 - 8.2, but both will still track Foundation 6.x. PHPFUI 7.0.0 would track Foundation 7.x series on currently supported versions of PHP.
Depreciation and Foundation changes
Since major versions of Foundation have in the past depreciated and obsoleted things, PHPFUI will track the latest version of Foundation for class names and functionality. However, when Foundation makes a breaking change or removes something, PHPFUI will continue to support the old functionality as best as possible in the new Foundation framework. Depreciated classes will be put in the \PHPFUI\Vx namespace (where x would be the prior Major Foundation version containing that feature). So if something gets depreciated in a newer version of Foundation, you simply will need to change your code from \PHPFUI\Example to \PHPFUI\V6\Example. The depreciated namespace will only be supported for one Major version of PHPFUI, so it is recommended you migrate off of it in a timely manor.
Full Class Documentation
Live Examples
Via PHPFUI/Examples
Unit Testing
Full unit testing using phpfui/html-unit-tester
License
PHPFUI is distributed under the MIT License.
PHP Versions
This library only supports modern versions of PHP which still receive security updates. While we would love to support PHP from the late Ming Dynasty, the advantages of modern PHP versions far out weigh quaint notions of backward compatibility. Time to upgrade.
PHPFUI\InstaDoc Library
A quick and easy way to add documentation to your PHP project
We all document our code with PHP DocBlocks but we never seem to actually generate the documentation and add it to our project. Why? It simply takes too much time (over a minute), so we put it off till later, and later never comes.
But with PHPFUI/InstaDoc, you can document your site in about a minute (OK, maybe 2). The steps involved:
- Install PHPFUI/InstaDoc via Composer (30 seconds)
- Run installation script (30 seconds)
- Create document page (1 minute, 6 lines of code)
Two minutes to usable documentation with the following features:
PHPFUI/InstaDoc Features
- Always up to date, even with code that is not yet checked in.
- Send constructor information including parameters and default values to clipboard.
- Child and Parent class hierarchy clearly displayed and accessable.
- Quick access to highlighted PHP source with user selectable highlighting.
- Quick access to the file's git history for the local repo.
- Full support for @inheritDoc tag so child method docs are displayed correctly.
- Documents all projects loaded via Composer automatically.
- Tabbed documentation so you are not looking at irrelevant methods.
- Alphabetized everything, no more searching unalphabetized pages!
- Support for markdown and custom markdown pages.
- Ability to generate static html files for high volume sites.
- Add any local repo directories.
- Remove any Composer project you don't care about.
- 5+ line config compatible with all PHP frameworks, or standalone.
- Uses Foundation CSS framework for a great experience on mobile.
Install PHPFUI/InstaDoc (requires PHP >= 8.0)
composer require phpfui/InstaDoc
Run Installation Script
Once installed, you need to run an installation script to copy static files to your public directory. From your project root, run the following:
php vendor/phpfui/instadoc/install.php yourPublicDirectory/subDirectory
Example: php vendor/phpfui/instadoc/install.php public/PHPFUI will add all needed files to public/PHPFUI, which will avoid any conflicts with your current files. You can specify any directory by using \PHPFUI\Page::setResourcePath, but PHPFUI is recomended to keep things simple.
Create Document Page
PHPFUI/InstaDoc does not reply on any framework and can run on a standalone page. It is recommended that you do not make your documentation public, as PHPFUI/InstaDoc will display PHP source files. How you restrict access to the page is up to you. The following does not restrict access and is simply an example:
include 'yourAutoLoader.php';
// pass the directory containing your composer.json file
$fileManager = new \PHPFUI\InstaDoc\FileManager('../');
// add your App class tree in, pass true as the last parameter if this namespace is in your local git repo.
$fileManager->addNamespace('App', '../App', true);
// load your cached files
$fileManager->load();
// load child classes if you want to display them, if you don't do this step, docs will not show classes that extend the displayed class
\PHPFUI\InstaDoc\ChildClasses::load();
// get the controller
$controller = new \PHPFUI\InstaDoc\Controller($fileManager);
// display will return a fully formed page
echo $controller->display();
That is it. You are done!
Adding New Classes
PHPFUI/InstaDoc saves the classes to display in PHP serialized files. Delete those files (.serial extension) when you want to display new classes. PHPFUI/InstaDoc will regenerate automatically if the files are missing.
Add Child Classes to the Docs
\PHPFUI\InstaDoc\ChildClasses::load('../ChildClasses.serial');
Add a Global Namespace Class
The git repo path defaults to the composer directory, but you can change the path by calling:
$fileManager->addGlobalNameSpaceClass(__DIR__ . '/global/FPDF.php');
Removing a Namespace
$fileManager->excludeNamespace('Carbon');
Add git Repository Page
The git repo path defaults to the composer directory, but you can change the path by calling:
$controller->setGitRoot(getcwd() . '/../');
Add Documents To Your Docs Home Page
$controller->addHomePageMarkdown('../PHPFUI/InstaDoc/README.md');
Set Your Home Page
You may want users to get back into your system easily. Clicking on the top left menu bar will take them here:
$controller->setHomeUrl('/');
Breakup Your Documentation Into Sections
If you have a lot of source code, you might want to break it into sections, so you will need a separate file to store the index in per section:
$fileManager->setBaseFile('SubProject');
Generate Static Files
Just the doc and file pages, no git!
$controller->generate('static/file/path', [\PHPFUI\InstaDoc\Controller::DOC_PAGE, \PHPFUI\InstaDoc\Controller::FILE_PAGE, ]));
Examples and Full Class Documentation
Package Documentation
- Composer\Semver Readme
composer/semver
Semver (Semantic Versioning) library that offers utilities, version constraint parsing and validation.
Originally written as part of composer/composer, now extracted and made available as a stand-alone library.
Installation
Install the latest version with:
composer require composer/semver
Requirements
- PHP 5.3.2 is required but using the latest version of PHP is highly recommended.
Version Comparison
For details on how versions are compared, refer to the Versions article in the documentation section of the getcomposer.org website.
Basic usage
Comparator
The
Composer\Semver\Comparator
class provides the following methods for comparing versions:- greaterThan($v1, $v2)
- greaterThanOrEqualTo($v1, $v2)
- lessThan($v1, $v2)
- lessThanOrEqualTo($v1, $v2)
- equalTo($v1, $v2)
- notEqualTo($v1, $v2)
Each function takes two version strings as arguments and returns a boolean. For example:
use Composer\Semver\Comparator; Comparator::greaterThan('1.25.0', '1.24.0'); // 1.25.0 > 1.24.0
Semver
The
Composer\Semver\Semver
class provides the following methods:- satisfies($version, $constraints)
- satisfiedBy(array $versions, $constraint)
- sort($versions)
- rsort($versions)
Intervals
The
Composer\Semver\Intervals
static class provides a few utilities to work with complex constraints or read version intervals from a constraint:use Composer\Semver\Intervals; // Checks whether $candidate is a subset of $constraint Intervals::isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint); // Checks whether $a and $b have any intersection, equivalent to $a->matches($b) Intervals::haveIntersections(ConstraintInterface $a, ConstraintInterface $b); // Optimizes a complex multi constraint by merging all intervals down to the smallest // possible multi constraint. The drawbacks are this is not very fast, and the resulting // multi constraint will have no human readable prettyConstraint configured on it Intervals::compactConstraint(ConstraintInterface $constraint); // Creates an array of numeric intervals and branch constraints representing a given constraint Intervals::get(ConstraintInterface $constraint); // Clears the memoization cache when you are done processing constraints Intervals::clear()
See the class docblocks for more details.
License
composer/semver is licensed under the MIT License, see the LICENSE file for details.
- DeepCopy Readme
DeepCopy
DeepCopy helps you create deep copies (clones) of your objects. It is designed to handle cycles in the association graph.
Table of Contents
How?
Install with Composer:
composer require myclabs/deep-copy
Use it:
use DeepCopy\DeepCopy; $copier = new DeepCopy(); $myCopy = $copier->copy($myObject);
Why?
- How do you create copies of your objects?
$myCopy = clone $myObject;
- How do you create deep copies of your objects (i.e. copying also all the objects referenced in the properties)?
You use
__clone()
and implement the behavior yourself.- But how do you handle cycles in the association graph?
Now you're in for a big mess :(
Using simply
clone
Overriding
__clone()
With
DeepCopy
How it works
DeepCopy recursively traverses all the object's properties and clones them. To avoid cloning the same object twice it keeps a hash map of all instances and thus preserves the object graph.
To use it:
use function DeepCopy\deep_copy; $copy = deep_copy($var);
Alternatively, you can create your own
DeepCopy
instance to configure it differently for example:use DeepCopy\DeepCopy; $copier = new DeepCopy(true); $copy = $copier->copy($var);
You may want to roll your own deep copy function:
namespace Acme; use DeepCopy\DeepCopy; function deep_copy($var) { static $copier = null; if (null === $copier) { $copier = new DeepCopy(true); } return $copier->copy($var); }
Going further
You can add filters to customize the copy process.
The method to add a filter is
DeepCopy\DeepCopy::addFilter($filter, $matcher)
, with$filter
implementingDeepCopy\Filter\Filter
and$matcher
implementingDeepCopy\Matcher\Matcher
.We provide some generic filters and matchers.
Matchers
-
DeepCopy\Matcher
applies on a object attribute. -
DeepCopy\TypeMatcher
applies on any element found in graph, including array elements.
Property name
The
PropertyNameMatcher
will match a property by its name:use DeepCopy\Matcher\PropertyNameMatcher; // Will apply a filter to any property of any objects named "id" $matcher = new PropertyNameMatcher('id');
Specific property
The
PropertyMatcher
will match a specific property of a specific class:use DeepCopy\Matcher\PropertyMatcher; // Will apply a filter to the property "id" of any objects of the class "MyClass" $matcher = new PropertyMatcher('MyClass', 'id');
Type
The
TypeMatcher
will match any element by its type (instance of a class or any value that could be parameter of gettype() function):use DeepCopy\TypeMatcher\TypeMatcher; // Will apply a filter to any object that is an instance of Doctrine\Common\Collections\Collection $matcher = new TypeMatcher('Doctrine\Common\Collections\Collection');
Filters
-
DeepCopy\Filter
applies a transformation to the object attribute matched byDeepCopy\Matcher
-
DeepCopy\TypeFilter
applies a transformation to any element matched byDeepCopy\TypeMatcher
By design, matching a filter will stop the chain of filters (i.e. the next ones will not be applied). Using the (
ChainableFilter
) won't stop the chain of filters.SetNullFilter
(filter)Let's say for example that you are copying a database record (or a Doctrine entity), so you want the copy not to have any ID:
use DeepCopy\DeepCopy; use DeepCopy\Filter\SetNullFilter; use DeepCopy\Matcher\PropertyNameMatcher; $object = MyClass::load(123); echo $object->id; // 123 $copier = new DeepCopy(); $copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id')); $copy = $copier->copy($object); echo $copy->id; // null
KeepFilter
(filter)If you want a property to remain untouched (for example, an association to an object):
use DeepCopy\DeepCopy; use DeepCopy\Filter\KeepFilter; use DeepCopy\Matcher\PropertyMatcher; $copier = new DeepCopy(); $copier->addFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category')); $copy = $copier->copy($object); // $copy->category has not been touched
ChainableFilter
(filter)If you use cloning on proxy classes, you might want to apply two filters for:
- loading the data
- applying a transformation
You can use the
ChainableFilter
as a decorator of the proxy loader filter, which won't stop the chain of filters (i.e. the next ones may be applied).use DeepCopy\DeepCopy; use DeepCopy\Filter\ChainableFilter; use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; use DeepCopy\Filter\SetNullFilter; use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; use DeepCopy\Matcher\PropertyNameMatcher; $copier = new DeepCopy(); $copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher()); $copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id')); $copy = $copier->copy($object); echo $copy->id; // null
DoctrineCollectionFilter
(filter)If you use Doctrine and want to copy an entity, you will need to use the
DoctrineCollectionFilter
:use DeepCopy\DeepCopy; use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter; use DeepCopy\Matcher\PropertyTypeMatcher; $copier = new DeepCopy(); $copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection')); $copy = $copier->copy($object);
DoctrineEmptyCollectionFilter
(filter)If you use Doctrine and want to copy an entity who contains a
Collection
that you want to be reset, you can use theDoctrineEmptyCollectionFilter
use DeepCopy\DeepCopy; use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter; use DeepCopy\Matcher\PropertyMatcher; $copier = new DeepCopy(); $copier->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty')); $copy = $copier->copy($object); // $copy->myProperty will return an empty collection
DoctrineProxyFilter
(filter)If you use Doctrine and use cloning on lazy loaded entities, you might encounter errors mentioning missing fields on a Doctrine proxy class (...\__CG__\Proxy). You can use the
DoctrineProxyFilter
to load the actual entity behind the Doctrine proxy class. Make sure, though, to put this as one of your very first filters in the filter chain so that the entity is loaded before other filters are applied! We recommend to decorate theDoctrineProxyFilter
with theChainableFilter
to allow applying other filters to the cloned lazy loaded entities.use DeepCopy\DeepCopy; use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; $copier = new DeepCopy(); $copier->addFilter(new ChainableFilter(new DoctrineProxyFilter()), new DoctrineProxyMatcher()); $copy = $copier->copy($object); // $copy should now contain a clone of all entities, including those that were not yet fully loaded.
ReplaceFilter
(type filter)- If you want to replace the value of a property:
use DeepCopy\DeepCopy; use DeepCopy\Filter\ReplaceFilter; use DeepCopy\Matcher\PropertyMatcher; $copier = new DeepCopy(); $callback = function ($currentValue) { return $currentValue . ' (copy)' }; $copier->addFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title')); $copy = $copier->copy($object); // $copy->title will contain the data returned by the callback, e.g. 'The title (copy)'
- If you want to replace whole element:
use DeepCopy\DeepCopy; use DeepCopy\TypeFilter\ReplaceFilter; use DeepCopy\TypeMatcher\TypeMatcher; $copier = new DeepCopy(); $callback = function (MyClass $myClass) { return get_class($myClass); }; $copier->addTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass')); $copy = $copier->copy([new MyClass, 'some string', new MyClass]); // $copy will contain ['MyClass', 'some string', 'MyClass']
The
$callback
parameter of theReplaceFilter
constructor accepts any PHP callable.ShallowCopyFilter
(type filter)Stop DeepCopy from recursively copying element, using standard
clone
instead:use DeepCopy\DeepCopy; use DeepCopy\TypeFilter\ShallowCopyFilter; use DeepCopy\TypeMatcher\TypeMatcher; use Mockery as m; $this->deepCopy = new DeepCopy(); $this->deepCopy->addTypeFilter( new ShallowCopyFilter, new TypeMatcher(m\MockInterface::class) ); $myServiceWithMocks = new MyService(m::mock(MyDependency1::class), m::mock(MyDependency2::class)); // All mocks will be just cloned, not deep copied
Edge cases
The following structures cannot be deep-copied with PHP Reflection. As a result they are shallow cloned and filters are not applied. There is two ways for you to handle them:
- Implement your own
__clone()
method - Use a filter with a type matcher
Contributing
DeepCopy is distributed under the MIT license.
Tests
Running the tests is simple:
vendor/bin/phpunit
Support
Get professional support via the Tidelift Subscription.
- Druidfi\Mysqldump Readme
mysqldump-php
This is a PHP version of
mysqldump
cli that comes with MySQL. It can be used for interacting with the data before creating the database dump. E.g. it can modify the contents of tables and is thus good for anonymize data.Out of the box,
mysqldump-php
supports backing up table structures, the data itself, views, triggers and events.mysqldump-php
supports:- output binary blobs as hex
- resolves view dependencies (using Stand-In tables)
- output compared against original mysqldump
- dumps stored routines (functions and procedures)
- dumps events
- does extended-insert and/or complete-insert
- supports virtual columns from MySQL 5.7
- does insert-ignore, like a REPLACE but ignoring errors if a duplicate key exists
- modifying data from database on-the-fly when dumping, using hooks
- can save directly to Google Cloud storage over a compressed stream wrapper (GZIPSTREAM)
Requirements
- PHP 7.4 or 8.x with PDO - see supported versions
- MySQL 5.7 or newer (and compatible MariaDB)
Installing
Install using Composer:
composer require druidfi/mysqldump-php
Getting started
try { $dump = new \Druidfi\Mysqldump\Mysqldump('mysql:host=localhost;dbname=testdb', 'username', 'password'); $dump->start('storage/work/dump.sql'); } catch (\Exception $e) { echo 'mysqldump-php error: ' . $e->getMessage(); }
Refer to the ifsnop/mysqldump-php Wiki for some examples and a comparison between mysqldump and mysqldump-php dumps.
Changing values when exporting
You can register a callable that will be used to transform values during the export. An example use-case for this is removing sensitive data from database dumps:
$dumper = new \Druidfi\Mysqldump\Mysqldump('mysql:host=localhost;dbname=testdb', 'username', 'password'); $dumper->setTransformTableRowHook(function ($tableName, array $row) { if ($tableName === 'customers') { $row['social_security_number'] = (string) rand(1000000, 9999999); } return $row; }); $dumper->start('storage/work/dump.sql');
Getting information about the dump
You can register a callable that will be used to report on the progress of the dump
$dumper->setInfoHook(function($object, $info) { if ($object === 'table') { echo $info['name'], $info['rowCount']; });
Table specific export conditions
You can register table specific 'where' clauses to limit data on a per table basis. These override the default
where
dump setting:$dumper = new \Druidfi\Mysqldump\Mysqldump('mysql:host=localhost;dbname=testdb', 'username', 'password'); $dumper->setTableWheres([ 'users' => 'date_registered > NOW() - INTERVAL 3 MONTH AND deleted=0', 'logs' => 'date_logged > NOW() - INTERVAL 1 DAY', 'posts' => 'isLive=1' ]);
Table specific export limits
You can register table specific 'limits' to limit the returned rows on a per table basis:
$dumper = new \Druidfi\Mysqldump\Mysqldump('mysql:host=localhost;dbname=testdb', 'username', 'password'); $dumper->setTableLimits([ 'users' => 300, 'logs' => 50, 'posts' => 10 ]);
You can also specify the limits as an array where the first value is the number of rows and the second is the offset
$dumper = new \Druidfi\Mysqldump\Mysqldump('mysql:host=localhost;dbname=testdb', 'username', 'password'); $dumper->setTableLimits([ 'users' => [20, 10], //MySql query equivalent "... LIMIT 20 OFFSET 10" ]);
Dump Settings
Dump settings can be changed from default values with 4th argument for Mysqldump constructor:
$dumper = new \Druidfi\Mysqldump\Mysqldump('mysql:host=localhost;dbname=testdb', 'username', 'password', $pdoOptions);
All options:
-
include-tables
- Only include these tables (array of table names), include all if empty.
-
exclude-tables
- Exclude these tables (array of table names), include all if empty, supports regexps.
-
include-views
- Only include these views (array of view names), include all if empty. By default, all views named as the include-tables array are included.
-
if-not-exists
- Only create a new table when a table of the same name does not already exist. No error message is thrown if the table already exists.
-
compress
- Possible values:
Bzip2|Gzip|Gzipstream|None
, default isNone
- Could be specified using the consts:
CompressManagerFactory::GZIP
,CompressManagerFactory::BZIP2
orCompressManagerFactory::NONE
- Possible values:
-
reset-auto-increment
- Removes the AUTO_INCREMENT option from the database definition
- Useful when used with no-data, so when db is recreated, it will start from 1 instead of using an old value
-
add-drop-database
- MySQL docs 5.7
-
add-drop-table
- MySQL docs 5.7
-
add-drop-triggers
- MySQL docs 5.7
-
add-locks
- MySQL docs 5.7
-
complete-insert
- MySQL docs 5.7
-
databases
- MySQL docs 5.7
-
default-character-set
- Possible values:
utf8|utf8mb4
, default isutf8
-
utf8
is compatible option andutf8mb4
is for full utf8 compliance - Could be specified using the consts:
DumpSettings::UTF8
orDumpSettings::UTF8MB4
- MySQL docs 5.7
- Possible values:
-
disable-keys
- MySQL docs 5.7
-
events
- MySQL docs 5.7
-
extended-insert
- MySQL docs 5.7
-
hex-blob
- MySQL docs 5.7
-
insert-ignore
- MySQL docs 5.7
-
lock-tables
- MySQL docs 5.7
-
net_buffer_length
- MySQL docs 5.7
-
no-autocommit
- Option to disable autocommit (faster inserts, no problems with index keys)
- MySQL docs 5.7
-
no-create-info
- MySQL docs 5.7
-
no-data
- Do not dump data for these tables (array of table names), support regexps,
true
to ignore all tables - MySQL docs 5.7
- Do not dump data for these tables (array of table names), support regexps,
-
routines
- MySQL docs 5.7
-
single-transaction
- MySQL docs 5.7
-
skip-comments
- MySQL docs 5.7
-
skip-dump-date
- MySQL docs 5.7
-
skip-triggers
- MySQL docs 5.7
-
skip-tz-utc
- MySQL docs 5.7
-
skip-definer
- MySQL docs 5.7
-
where
- MySQL docs 5.7
The following options are now enabled by default, and there is no way to disable them since they should always be used.
-
disable-foreign-keys-check
- MySQL docs 5.7
Privileges
To dump a database, you need the following privileges:
-
SELECT
- In order to dump table structures and data.
-
SHOW VIEW
- If any databases has views, else you will get an error.
-
TRIGGER
- If any table has one or more triggers.
-
LOCK TABLES
- If "lock tables" option was enabled.
-
PROCESS
- If you don’t use the --no-tablespaces option.
Use SHOW GRANTS FOR user@host; to know what privileges user has. See the following link for more information:
- Which are the minimum privileges required to get a backup of a MySQL database schema?
- PROCESS privilege from MySQL 5.7.31 and MySQL 8.0.21 in July 2020
Tests
The testing script creates and populates a database using all possible datatypes. Then it exports it using both mysqldump-php and mysqldump, and compares the output. Only if it is identical tests are OK.
Some tests are skipped if mysql server doesn't support them.
A couple of tests are only comparing between original sql code and mysqldump-php generated sql, because some options are not available in mysqldump.
Local setup for tests:
docker compose up -d --build docker compose exec php81 /app/tests/scripts/create_users.sh docker compose exec php81 /app/tests/scripts/create_users.sh db2 docker compose exec php81 /app/tests/scripts/create_users.sh db3 docker compose exec -w /app/tests/scripts php74 ./test.sh docker compose exec -w /app/tests/scripts php80 ./test.sh docker compose exec -w /app/tests/scripts php81 ./test.sh docker compose exec -w /app/tests/scripts php82 ./test.sh docker compose exec -w /app/tests/scripts php74 ./test.sh db2 docker compose exec -w /app/tests/scripts php80 ./test.sh db2 docker compose exec -w /app/tests/scripts php81 ./test.sh db2 docker compose exec -w /app/tests/scripts php82 ./test.sh db2 docker compose exec -w /app/tests/scripts php74 ./test.sh db3 docker compose exec -w /app/tests/scripts php80 ./test.sh db3 docker compose exec -w /app/tests/scripts php81 ./test.sh db3 docker compose exec -w /app/tests/scripts php82 ./test.sh db3
Credits
Forked from Diego Torres's version which have latest updates from 2020. Use it for PHP 7.3 and older. https://github.com/ifsnop/mysqldump-php
Originally based on James Elliott's script from 2009. https://code.google.com/archive/p/db-mysqldump/
Adapted and extended by Michael J. Calkins. https://github.com/clouddueling
License
This project is open-sourced software licensed under the GPL license
- Gitonomy\Git Readme
Gitlib for Gitonomy
This library provides methods to access Git repository from PHP 5.6+.
It makes shell calls, which makes it less performant than any solution.
Anyway, it's convenient and don't need to build anything to use it. That's how we love it.
Quick Start
You can install gitlib using Composer. Simply require the version you need:
$ composer require gitonomy/gitlib
or edit your
composer.json
file by hand:{ "require": { "gitonomy/gitlib": "^1.3" } }
Example Usage
use Gitonomy\Git\Repository; $repository = new Repository('/path/to/repository'); foreach ($repository->getReferences()->getBranches() as $branch) { echo '- '.$branch->getName().PHP_EOL; } $repository->run('fetch', ['--all']);
API Documentation
For Enterprise
Available as part of the Tidelift Subscription
The maintainers of
gitonomy/gitlib
and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more. - Gitonomy\Git\doc Admin
Create and access git repositories
gitlib provides methods to initialize new repositories.
Create a repository
To initialize a new repository, use method
Admin::init
.// Initialize a bare repository $repository = Gitonomy\Git\Admin::init('/path/to/repository'); // Initialize a non-bare repository $repository = Gitonomy\Git\Admin::init('/path/to/repository', false);
Default behavior is to create a bare repository. If you want to initialize a repository with a working copy,pass
false
as third argument of Repository constructor.Cloning repositories
You can clone a repository from an URL by doing:
// Clone to a bare repository $repository = Gitonomy\Git\Admin::cloneTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git'); // Clone to a non-bare repository $repository = Gitonomy\Git\Admin::cloneTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git', false);
Default behavior is to clone in a bare repository.
You can also clone a repository and point it to a specific branch. In a non-bare repository, this branch will be checked out:
// Clone to a bare repository $repository = Gitonomy\Git\Admin::cloneBranchTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git', 'a-branch'); // Clone to a non-bare repository $repository = Gitonomy\Git\Admin::cloneBranchTo('/tmp/gitlib', 'https://github.com/gitonomy/gitlib.git', 'a-branch', false);
Clone a Repository object
If you already have a Repository instance and want to clone it, you can use this shortcut:
$new = $repository->cloneTo('/tmp/clone');
Mirror a repository
If you want to mirror fully a repository and all references, use the
mirrorTo
method. This method takes only two arguments, where to mirror and what to mirror:// Mirror to a bare repository $mirror = Gitonomy\Git\Admin::mirrorTo('/tmp/mirror', 'https://github.com/gitonomy/gitlib.git'); // Mirror to a non-bare repository $mirror = Gitonomy\Git\Admin::mirrorTo('/tmp/mirror', 'https://github.com/gitonomy/gitlib.git', false);
References
- Gitonomy\Git\doc Blame
Blaming files
Line-per-line iteration
To iterate on lines of a blame:
$blame = $repository->getBlame('master', 'README.md'); foreach ($blame->getLines() as $lineNumber => $line) { $commit = $line->getCommit(); echo $lineNumber.': '.$line->getContent().' - '.$commit->getAuthorName().PHP_EOL; }
The getLines method returns an array indexed starting from 1.
As you can see, you can access the commit object related to the line you are iterating on.
If you want to access directly a line:
$line = $blame->getLine(32);
The Line object
LineObject represents an item of the blame file. It is composed of those informations:
$line->getCommit(); // returns a Commit $line->getContent(); // returns text // you can access author from commmit: $author = $line->getCommit()->getAuthorName();
Group reading by commit
If you plan to display it, you'll probably need a version where lines from same commit are grouped.
To do so, use the getGroupedLines method that will return an array like this:
$blame = array( array(Commit, array(1 => Line, 2 => Line, 3 => Line)), array(Commit, array(4 => Line)), array(Commit, array(5 => Line, 6 => Line)) )
- Gitonomy\Git\doc Blob
Blob
In git, a blob represents a file content. You can't access the file name directly from the Blob object; the filename information is stored within the tree, not in the blob.
It means that for git, two files with different names but same content will have the same hash.
To access a repository Blob, you need the hash identifier:
$repository = new Gitonomy\Git\Repository('/path/to/repository'); $blob = $repository->getBlob('a7c8d2b4');
Get content
To get content from a Blob object:
echo $blob->getContent();
File informations
To get mimetype of a Blob object using finfo extension:
echo $blob->getMimetype();
You can also test if Blob is a text of a binary file:
if ($blob->isText()) { echo $blob->getContent(), PHP_EOL; } elseif ($blob->isBinary()) { echo 'File is binary', PHP_EOL; }
- Gitonomy\Git\doc Branch
Branch
To access a Branch, starting from a repository object:
$repository = new Gitonomy\Git\Repository('/path/to/repository'); $branch = $repository->getReferences()->getBranch('master');
You can check is the branch is a local or remote one:
$branch->isLocal(); $branch->isRemote();
- Gitonomy\Git\doc Commit
Commit
To access a Commit, starting from a repository object:
$repository = new Gitonomy\Git\Repository('/path/to/repository'); $commit = $repository->getCommit('a7c8d2b4');
Browsing parents
A Commit can have a natural number of parents:
- no parent: it's an initial commit, the root of a tree
- one parent: it means it's not a merge, just a regular commit
- many parents: it's a merge-commit
You have 2 methods available for accessing parents:
// Access parent hashes $hashes = $commit->getParentHashes(); // Access parent commit objects $commits = $commit->getParents();
For example, if you want to display all parents, starting from a commit:
function displayLog(Gitonomy\Git\Commit $commit) { echo '- '.$commit->getShortMessage().PHP_EOL; foreach ($commit->getParents() as $parent) { displayLog($parent); } }
Notice that this function will first display all commits from first merged branch and then display all commits from next branch, and so on.
Accessing tree
The tree object contains the reference to the files associated to a given commit. Every commit has one and only one tree, referencing all files and folders of a given state for a project. For more informations about the tree, see the chapter dedicated to it.
To access a tree starting from a commit:
// Returns the tree hash $tree = $commit->getTreeHash(); // Returns the tree object $tree = $commit->getTree();
Author & Committer informations
Each commit has two authoring informations: an author and a committer. The author is the creator of the modification, authoring a modification in the repository. The committer is responsible of introducing this modification to the repository.
You can access informations from author and committer using those methods:
// Author $commit->getAuthorName(); $commit->getAuthorEmail(); $commit->getAuthorDate(); // returns a DateTime object // Committer $commit->getCommitterName(); $commit->getCommitterEmail(); $commit->getCommitterDate(); // returns a DateTime object
Commit message and short message
Each commit also has a message, associated to the modification. This message can be multilined.
To access the message, you can use the getMessage method:
$commit->getMessage();
For your convenience, this library provides a shortcut method to keep only the first line or first 50 characters if the first line is too long:
$commit->getShortMessage();
You can customize it like this:
$commit->getShortMessage(45, true, '.');
- The first parameter is the max length of the message.
- The second parameter determine if the last word should be cut or preserved
- The third parameter is the separator
There are also two other methods for your convenience:
// The first line $commit->getSubjectMessage(); // The body (rest of the message) $commit->getBodyMessage();
Diff of a commit
You can check the modifications introduced by a commit using the getDiff method. When you request a diff for a commit, depending of the number of parents, the strategy will be different:
- If you have no parent, the diff will be the content of the tree
- If you only have one parent, the diff will be between the commit and his parent
- If you have multiple parents, the diff will be the difference between the commit and the first common ancestor of all parents
For more informations about the diff API, read the related chapter.
To access the Diff object of a commit, use the method getDiff:
$diff = $commit->getDiff();
Last modification of a file
To know the last modification of a file, you can use the getLastModification method on a commit.
Here is a very straightforward example:
$last = $commit->getLastModification('README'); echo 'Last README modification'.PHP_EOL; echo ' Author: '.$last->getAuthorName().PHP_EOL; echo ' Date: '.$last->getAuthorDate()->format('d/m/Y').PHP_EOL; echo ' Message: '.$last->getMessage();
Find every branches containing a commit
$branches = $commit->getIncludingBranches($includeLocalBranches, $includeRemoteBranches); $localBranches = $commit->getIncludingBranches(true, false); $remoteBranches = $commit->getIncludingBranches(false, true);
- Gitonomy\Git\doc Diff
Computing diff
Even if git is a diff-less storage engine, it's possible to compute them.
To compute a diff in git, you need to specify a revision. This revision can be a commit (2bc7a8) or a range (2bc7a8..ff4c21b).
For more informations about git revisions: man gitrevisions.
When you have decided the revision you want and have your Repository object, you can call the getDiff method on the repository:
$diff = $repository->getDiff('master@{2 days ago}..master');
You can also access it from a Log object:
$log = $repository->getLog('master@{2 days ago}..master'); $diff = $log->getDiff();
Iterating a diff
When you have a Diff object, you can iterate over files using method getFiles(). This method returns a list of File objects, who represents the modifications for a single file.
$files = $diff->getFiles(); echo sprintf('%s files modified%s', count($files), PHP_EOL); foreach ($files as $fileDiff) { echo sprintf('Old name: (%s) %s%s', $fileDiff->getOldMode(), $fileDiff->getOldName(), PHP_EOL); echo sprintf('New name: (%s) %s%s', $fileDiff->getNewMode(), $fileDiff->getNewName(), PHP_EOL); }
The File object
Here is an exhaustive list of the File class methods:
$file->getOldName(); $file->getNewName(); $file->getOldDiff(); $file->getNewDiff(); $file->isCreation(); $file->isDeletion(); $file->isModification(); $file->isRename(); $file->isChangeMode(); $file->getAdditions(); // Number of added lines $file->getDeletions(); // Number of deleted lines $file->isBinary(); // Binary files have no "lines" $file->getChanges(); // See next chapter
The FileChange object
note
This part of API is not very clean, very consistent. If you have any idea or suggestion on how to enhance this, your comment would be appreciated.
A File object is composed of many changes. For each of those changes, a FileChange object is associated.
To access changes from a file, use the getChanges method:
$changes = $file->getChanges(); foreach ($changes as $change) { foreach ($lines as $data) { list ($type, $line) = $data; if ($type === FileChange::LINE_CONTEXT) { echo ' '.$line.PHP_EOL; } elseif ($type === FileChange::LINE_ADD) { echo '+'.$line.PHP_EOL; } else { echo '-'.$line.PHP_EOL; } } }
To get line numbers, use the range methods:
echo sprintf('Previously from line %s to %s%s', $change->getOldRangeStart(), $change->getOldRangeEnd(), PHP_EOL); echo sprintf('Now from line %s to %s%s', $change->getNewRangeStart(), $change->getNewRangeEnd(), PHP_EOL);
- Gitonomy\Git\doc Hooks
Hooks
It's possible to define custom hooks on any repository with git. Those hooks are located in the .git/hooks folder.
Those files need to be executable. For convenience, gitlib will set them to 777.
With gitlib, you can manage hooks over a repository using the Hooks object.
To access it from a repository, use the getHooks method on a Repository object:
$hooks = $repository->getHooks();
Reading hooks
To read the content of a hook, use the get method like this:
$content = $hooks->get('pre-receive'); // returns a string
If the hook does not exist, an exception will be thrown (InvalidArgumentException).
You can test if a hook is present using the method has:
$hooks->has('pre-receive'); // a boolean indicating presence
Inserting hooks
You can modify a hook in two different ways: creating a new file or using a symlink.
To create the hook using a symlink:
$hooks->setSymlink('pre-receive', '/path/to/file-to-link');
If the hook already exist, a LogicException will be thrown. If an error occured during symlink creation, a RuntimeException will be thrown.
If you want to directly create a new file in hooks directory, use the method set. This method will create a new file, put content in it and make it executable:
$content = <<<HOOK #!/bin/bash echo "Push is disabled" exit 1 HOOK; // this hook will reject every push $hooks->set('pre-receive', $content);
If the hook already exists, a LogicException will be thrown.
Removing hooks
To remove a hook from a repository, use the function remove:
$hooks->remove('pre-receive');
- Gitonomy\Git\doc Log
Getting log history
Crawling manually commits and parents to browse history is surely a good solution. But when it comes to ordering them or aggregate them from multiple branches, we tend to use
git log
.To get a Log object from a repository:
$log = $repository->getLog();
You can pass four arguments to getLog method:
// Global log for repository $log = $repository->getLog(); // Log for master branch $log = $repository->getLog('master'); // Returns last 10 commits on README file $log = $repository->getLog('master', 'README', 0, 10); // Returns last 10 commits on README or UPGRADE files $log = $repository->getLog('master', ['README', 'UPGRADE'], 0, 10);
Counting
If you want to count overall commits, without offset or limit, use the countCommits method:
echo sprintf('This log contains %s commits%s', $log->countCommits(), PHP_EOL); // Countable interface echo sprintf('This log contains %s commits%s', count($log), PHP_EOL);
Offset and limit
Use those methods:
$log->setOffset(32); $log->setLimit(40); // or read it: $log->getOffset(); $log->getLimit();
- Gitonomy\Git\doc References
Tags and branches
Accessing tags and branches
With gitlib, you can access them via the ReferenceBag object. To get this object from a Repository, use the getReferences method:
$references = $repository->getReferences();
First, you can test existence of tags and branches like this:
if ($references->hasBranch('master') && $references->hasTag('0.1')) { echo 'Good start!'.PHP_EOL; }
If you want to access all branches or all tags:
$branches = $references->getBranches(); $localBranches = $references->getLocalBranches(); $remoteBranches = $references->getRemoteBranches(); $tags = $references->getTags(); $all = $references->getAll();
To get a given branch or tag, call getBranch or getTag on the ReferenceBag. Those methods return Branch and Tag objects:
$master = $references->getBranch('master'); $feat123 = $references->getLocalBranch('feat123'); $feat456 = $references->getRemoteBranch('origin/feat456'); $v0_1 = $references->getTag('0.1');
If the reference cannot be resolved, a ReferenceNotFoundException will be thrown.
On each of those objects, you can access those informations:
// Get the associated commit $commit = $master->getCommit(); // Get the commit hash $hash = $master->getCommitHash(); // Get the last modification $lastModification = $master->getLastModification();
Create and delete reference
You can create new tags and branches on repository, using helper methods on ReferenceBag object:
// create a branch $references = $repository->getReferences(); $branch = $references->createBranch('foobar', 'a8b7e4...'); // commit to reference // create a tag $references = $repository->getReferences(); $tag = $references->createTag('0.3', 'a8b7e4...'); // commit to reference // delete a branch or a tag $branch->delete();
Resolution from a commit
To resolve a branch or a commit from a commit, you can use the resolveTags and resolveBranches methods on it:
$branches = $references->resolveBranches($commit); $tags = $references->resolveTags($commit); // Resolve branches and tags $all = $references->resolve($commit);
You can pass a Commit object or a hash to the method, gitlib will handle it.
- Gitonomy\Git\doc Repository
Repository methods
Creating a Repository object is possible, providing a path argument to the constructor:
$repository = new Repository('/path/to/repo');
Repository options
The constructor of Repository takes an additional parameter:
$options
. This parameter can be used used to tune behavior of library.Available options are:
- debug (default: true): Enables exception when edge cases are met
- environment_variables: (default: none) An array of environment variables to be set in sub-process
-
logger: (default: none) Logger to use for reporting of execution
(a
Psr\Log\LoggerInterface
) -
command: (default:
git
) Specify command to execute to run git - working_dir: If you are using multiple working directories, this option is for you
An example:
$repository = new Repository('/path/to/repo', [ 'debug' => true, 'logger' => new Monolog\Logger(), ]);
Test if a repository is bare
On a Repository object, you can call method isBare to test if your repository is bare or not:
$repository->isBare();
Compute size of a repository
To know how much size a repository is using on your drive, you can use
getSize
method on a Repository object.warning
This command was only tested with linux.
The returned size is in kilobytes:
$size = $repository->getSize(); echo 'Your repository size is '.$size.'KB';
Access HEAD
HEAD
represents in git the version you are working on (in working tree). YourHEAD
can be attached (using a reference) or detached (using a commit).$head = $repository->getHead(); // Commit or Reference $head = $repository->getHeadCommit(); // Commit if ($repository->isHeadDetached()) { echo 'Sorry man'.PHP_EOL; }
Options for repository
Logger
If you are developing, you may appreciate to have a logger inside repository, telling you every executed command.
You call method
setLogger
as an option on repository creation:$repository->setLogger(new Monolog\Logger('repository')); $repository->run('fetch', ['--all']);
You can also specify as an option on repository creation:
$logger = new MonologLogger('repository'); $repository = new Repository('/path/foo', ['logger' => $logger]); $repository->run('fetch', ['--all']);
This will output:
info run command: fetch "--all" debug last command (fetch) duration: 23.24ms debug last command (fetch) return code: 0 debug last command (fetch) output: Fetching origin
Disable debug-mode
Gitlib throws an exception when something seems wrong. If a
git
command exits with a non-zero code, then execution will be stopped, and aRuntimeException
will be thrown. If you want to prevent this, set thedebug
option tofalse
. This will makeRepository
log errors and return empty data instead of throwing exceptions.$repository = new Repository('/tmp/foo', ['debug' => false, 'logger' => $logger]);
note
If you plan to disable debug, you should rely on the logger to keep a trace of the failing cases.
Specify git command to use
You can pass the option
command
to specify which command to use to run git calls. If you have a git binary located somewhere else, use this option to specify to gitlib path to your git binary:$repository = new Gitonomy\Git\Repository('/tmp/foo', ['command' => '/home/alice/bin/git']);
Environment variables
It is possible to send environment variables to the
git
commands.$repository = new Gitonomy\Git\Repository('/tmp/foo', ['environment_variables' => ['GIT_']])
- Gitonomy\Git\doc Revision
Revision
To get a revision from a Repository object:
$revision = $repository->getRevision('master@{2 days ago}');
Getting the log
You can access a Log object starting from a revision using the getLog method. This method takes two parameters: offset and limit:
// Returns 100 lasts commits $log = $revision->getLog(null, 100);
Resolve a revision
To resolve a revision to a commit:
$commit = $revision->getCommit();
- Gitonomy\Git\doc Tree
Tree and files
To organize folders, git uses trees. In gitlib, those trees are represented via Tree object.
To get the root tree associated to a commit, use the getTree method on the commit object:
$tree = $commit->getTree();
This tree is the entry point of all of your files.
The main method for a tree is the getEntries method. This method will return an array, indexed by name. Each of those elements will be the entry mode and the entry object.
Let's understand how it works with a concrete example:
function displayTree(Tree $tree, $indent = 0) { $indent = str_repeat(' ', $indent); foreach ($tree->getEntries() as $name => $data) { list($mode, $entry) = $data; if ($entry instanceof Tree) { echo $indent.$name.'/'.PHP_EOL; displayTree($tree, $indent + 1); } else { echo $indent.$name.PHP_EOL; } } } displayTree($commit->getTree());
This method will recursively display all entries of a tree.
Resolve a path
To access directly a sub-file, the easier is probably to use the resolvePath method.
An example:
$source = $tree->resolvePath('src/Gitonomy/Git'); $source instanceof Tree;
- Gitonomy\Git\doc Workingcopy
Working copy
Working copy is the folder associated to a git repository. In gitlib, you can access this object using the getWorkingCopy on a Repository object:
$repo = new Repository('/path/to/working-dir'); $wc = $repo->getWorkingCopy();
Checkout a revision
You can checkout any revision using checkout method. You can also pass a second argument, which will be passed as argument with
-b
:// git checkout master $wc->checkout('master'); // git checkout origin/master -b master $wc->checkout('origin/master', 'master');
You can also pass a Reference or a Commit.
Staged modifications
You can get a diff of modifications pending in staging area. To get the
Diff
object, call methodgetDiffStaged()
:$diff = $wc->getDiffStaged();
Pending modifications
You can get pending modifications on tracked files by calling method
getDiffPending()
:$diff = $wc->getDiffPending();
- GuzzleHttp Readme
Guzzle, PHP HTTP client
Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services.
- Simple interface for building query strings, POST requests, streaming large uploads, streaming large downloads, using HTTP cookies, uploading JSON data, etc...
- Can send both synchronous and asynchronous requests using the same interface.
- Uses PSR-7 interfaces for requests, responses, and streams. This allows you to utilize other PSR-7 compatible libraries with Guzzle.
- Supports PSR-18 allowing interoperability between other PSR-18 HTTP Clients.
- Abstracts away the underlying HTTP transport, allowing you to write environment and transport agnostic code; i.e., no hard dependency on cURL, PHP streams, sockets, or non-blocking event loops.
- Middleware system allows you to augment and compose client behavior.
$client = new \GuzzleHttp\Client(); $response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle'); echo $response->getStatusCode(); // 200 echo $response->getHeaderLine('content-type'); // 'application/json; charset=utf8' echo $response->getBody(); // '{"id": 1420053, "name": "guzzle", ...}' // Send an asynchronous request. $request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); $promise = $client->sendAsync($request)->then(function ($response) { echo 'I completed! ' . $response->getBody(); }); $promise->wait();
Help and docs
We use GitHub issues only to discuss bugs and new features. For support please refer to:
- Documentation
- Stack Overflow
- #guzzle channel on PHP-HTTP Slack
- Gitter
Installing Guzzle
The recommended way to install Guzzle is through Composer.
composer require guzzlehttp/guzzle
Version Guidance
Version Status Packagist Namespace Repo Docs PSR-7 PHP Version 3.x EOL guzzle/guzzle
Guzzle
v3 v3 No >=5.3.3,<7.0 4.x EOL guzzlehttp/guzzle
GuzzleHttp
v4 N/A No >=5.4,<7.0 5.x EOL guzzlehttp/guzzle
GuzzleHttp
v5 v5 No >=5.4,<7.4 6.x Security fixes only guzzlehttp/guzzle
GuzzleHttp
v6 v6 Yes >=5.5,<8.0 7.x Latest guzzlehttp/guzzle
GuzzleHttp
v7 v7 Yes >=7.2.5,<8.4 Security
If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see Security Policy for more information.
License
Guzzle is made available under the MIT License (MIT). Please see License File for more information.
For Enterprise
Available as part of the Tidelift Subscription
The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more.
- GuzzleHttp\Promise Readme
Guzzle Promises
Promises/A+ implementation that handles promise chaining and resolution iteratively, allowing for "infinite" promise chaining while keeping the stack size constant. Read this blog post for a general introduction to promises.
Features
- Promises/A+ implementation.
- Promise resolution and chaining is handled iteratively, allowing for "infinite" promise chaining.
- Promises have a synchronous
wait
method. - Promises can be cancelled.
- Works with any object that has a
then
function. - C# style async/await coroutine promises using
GuzzleHttp\Promise\Coroutine::of()
.
Installation
composer require guzzlehttp/promises
Version Guidance
Version Status PHP Version 1.x Security fixes only >=5.5,<8.3 2.x Latest >=7.2.5,<8.5 Quick Start
A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its
then
method, which registers callbacks to receive either a promise's eventual value or the reason why the promise cannot be fulfilled.Callbacks
Callbacks are registered with the
then
method by providing an optional$onFulfilled
followed by an optional$onRejected
function.use GuzzleHttp\Promise\Promise; $promise = new Promise(); $promise->then( // $onFulfilled function ($value) { echo 'The promise was fulfilled.'; }, // $onRejected function ($reason) { echo 'The promise was rejected.'; } );
Resolving a promise means that you either fulfill a promise with a value or reject a promise with a reason. Resolving a promise triggers callbacks registered with the promise's
then
method. These callbacks are triggered only once and in the order in which they were added.Resolving a Promise
Promises are fulfilled using the
resolve($value)
method. Resolving a promise with any value other than aGuzzleHttp\Promise\RejectedPromise
will trigger all of the onFulfilled callbacks (resolving a promise with a rejected promise will reject the promise and trigger the$onRejected
callbacks).use GuzzleHttp\Promise\Promise; $promise = new Promise(); $promise ->then(function ($value) { // Return a value and don't break the chain return "Hello, " . $value; }) // This then is executed after the first then and receives the value // returned from the first then. ->then(function ($value) { echo $value; }); // Resolving the promise triggers the $onFulfilled callbacks and outputs // "Hello, reader." $promise->resolve('reader.');
Promise Forwarding
Promises can be chained one after the other. Each then in the chain is a new promise. The return value of a promise is what's forwarded to the next promise in the chain. Returning a promise in a
then
callback will cause the subsequent promises in the chain to only be fulfilled when the returned promise has been fulfilled. The next promise in the chain will be invoked with the resolved value of the promise.use GuzzleHttp\Promise\Promise; $promise = new Promise(); $nextPromise = new Promise(); $promise ->then(function ($value) use ($nextPromise) { echo $value; return $nextPromise; }) ->then(function ($value) { echo $value; }); // Triggers the first callback and outputs "A" $promise->resolve('A'); // Triggers the second callback and outputs "B" $nextPromise->resolve('B');
Promise Rejection
When a promise is rejected, the
$onRejected
callbacks are invoked with the rejection reason.use GuzzleHttp\Promise\Promise; $promise = new Promise(); $promise->then(null, function ($reason) { echo $reason; }); $promise->reject('Error!'); // Outputs "Error!"
Rejection Forwarding
If an exception is thrown in an
$onRejected
callback, subsequent$onRejected
callbacks are invoked with the thrown exception as the reason.use GuzzleHttp\Promise\Promise; $promise = new Promise(); $promise->then(null, function ($reason) { throw new Exception($reason); })->then(null, function ($reason) { assert($reason->getMessage() === 'Error!'); }); $promise->reject('Error!');
You can also forward a rejection down the promise chain by returning a
GuzzleHttp\Promise\RejectedPromise
in either an$onFulfilled
or$onRejected
callback.use GuzzleHttp\Promise\Promise; use GuzzleHttp\Promise\RejectedPromise; $promise = new Promise(); $promise->then(null, function ($reason) { return new RejectedPromise($reason); })->then(null, function ($reason) { assert($reason === 'Error!'); }); $promise->reject('Error!');
If an exception is not thrown in a
$onRejected
callback and the callback does not return a rejected promise, downstream$onFulfilled
callbacks are invoked using the value returned from the$onRejected
callback.use GuzzleHttp\Promise\Promise; $promise = new Promise(); $promise ->then(null, function ($reason) { return "It's ok"; }) ->then(function ($value) { assert($value === "It's ok"); }); $promise->reject('Error!');
Synchronous Wait
You can synchronously force promises to complete using a promise's
wait
method. When creating a promise, you can provide a wait function that is used to synchronously force a promise to complete. When a wait function is invoked it is expected to deliver a value to the promise or reject the promise. If the wait function does not deliver a value, then an exception is thrown. The wait function provided to a promise constructor is invoked when thewait
function of the promise is called.$promise = new Promise(function () use (&$promise) { $promise->resolve('foo'); }); // Calling wait will return the value of the promise. echo $promise->wait(); // outputs "foo"
If an exception is encountered while invoking the wait function of a promise, the promise is rejected with the exception and the exception is thrown.
$promise = new Promise(function () use (&$promise) { throw new Exception('foo'); }); $promise->wait(); // throws the exception.
Calling
wait
on a promise that has been fulfilled will not trigger the wait function. It will simply return the previously resolved value.$promise = new Promise(function () { die('this is not called!'); }); $promise->resolve('foo'); echo $promise->wait(); // outputs "foo"
Calling
wait
on a promise that has been rejected will throw an exception. If the rejection reason is an instance of\Exception
the reason is thrown. Otherwise, aGuzzleHttp\Promise\RejectionException
is thrown and the reason can be obtained by calling thegetReason
method of the exception.$promise = new Promise(); $promise->reject('foo'); $promise->wait();
PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'
Unwrapping a Promise
When synchronously waiting on a promise, you are joining the state of the promise into the current state of execution (i.e., return the value of the promise if it was fulfilled or throw an exception if it was rejected). This is called "unwrapping" the promise. Waiting on a promise will by default unwrap the promise state.
You can force a promise to resolve and not unwrap the state of the promise by passing
false
to the first argument of thewait
function:$promise = new Promise(); $promise->reject('foo'); // This will not throw an exception. It simply ensures the promise has // been resolved. $promise->wait(false);
When unwrapping a promise, the resolved value of the promise will be waited upon until the unwrapped value is not a promise. This means that if you resolve promise A with a promise B and unwrap promise A, the value returned by the wait function will be the value delivered to promise B.
Note: when you do not unwrap the promise, no value is returned.
Cancellation
You can cancel a promise that has not yet been fulfilled using the
cancel()
method of a promise. When creating a promise you can provide an optional cancel function that when invoked cancels the action of computing a resolution of the promise.API
Promise
When creating a promise object, you can provide an optional
$waitFn
and$cancelFn
.$waitFn
is a function that is invoked with no arguments and is expected to resolve the promise.$cancelFn
is a function with no arguments that is expected to cancel the computation of a promise. It is invoked when thecancel()
method of a promise is called.use GuzzleHttp\Promise\Promise; $promise = new Promise( function () use (&$promise) { $promise->resolve('waited'); }, function () { // do something that will cancel the promise computation (e.g., close // a socket, cancel a database query, etc...) } ); assert('waited' === $promise->wait());
A promise has the following methods:
-
then(callable $onFulfilled, callable $onRejected) : PromiseInterface
Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.
-
otherwise(callable $onRejected) : PromiseInterface
Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
-
wait($unwrap = true) : mixed
Synchronously waits on the promise to complete.
$unwrap
controls whether or not the value of the promise is returned for a fulfilled promise or if an exception is thrown if the promise is rejected. This is set totrue
by default. -
cancel()
Attempts to cancel the promise if possible. The promise being cancelled and the parent most ancestor that has not yet been resolved will also be cancelled. Any promises waiting on the cancelled promise to resolve will also be cancelled.
-
getState() : string
Returns the state of the promise. One of
pending
,fulfilled
, orrejected
. -
resolve($value)
Fulfills the promise with the given
$value
. -
reject($reason)
Rejects the promise with the given
$reason
.
FulfilledPromise
A fulfilled promise can be created to represent a promise that has been fulfilled.
use GuzzleHttp\Promise\FulfilledPromise; $promise = new FulfilledPromise('value'); // Fulfilled callbacks are immediately invoked. $promise->then(function ($value) { echo $value; });
RejectedPromise
A rejected promise can be created to represent a promise that has been rejected.
use GuzzleHttp\Promise\RejectedPromise; $promise = new RejectedPromise('Error'); // Rejected callbacks are immediately invoked. $promise->then(null, function ($reason) { echo $reason; });
Promise Interoperability
This library works with foreign promises that have a
then
method. This means you can use Guzzle promises with React promises for example. When a foreign promise is returned inside of a then method callback, promise resolution will occur recursively.// Create a React promise $deferred = new React\Promise\Deferred(); $reactPromise = $deferred->promise(); // Create a Guzzle promise that is fulfilled with a React promise. $guzzlePromise = new GuzzleHttp\Promise\Promise(); $guzzlePromise->then(function ($value) use ($reactPromise) { // Do something something with the value... // Return the React promise return $reactPromise; });
Please note that wait and cancel chaining is no longer possible when forwarding a foreign promise. You will need to wrap a third-party promise with a Guzzle promise in order to utilize wait and cancel functions with foreign promises.
Event Loop Integration
In order to keep the stack size constant, Guzzle promises are resolved asynchronously using a task queue. When waiting on promises synchronously, the task queue will be automatically run to ensure that the blocking promise and any forwarded promises are resolved. When using promises asynchronously in an event loop, you will need to run the task queue on each tick of the loop. If you do not run the task queue, then promises will not be resolved.
You can run the task queue using the
run()
method of the global task queue instance.// Get the global task queue $queue = GuzzleHttp\Promise\Utils::queue(); $queue->run();
For example, you could use Guzzle promises with React using a periodic timer:
$loop = React\EventLoop\Factory::create(); $loop->addPeriodicTimer(0, [$queue, 'run']);
Implementation Notes
Promise Resolution and Chaining is Handled Iteratively
By shuffling pending handlers from one owner to another, promises are resolved iteratively, allowing for "infinite" then chaining.
require 'vendor/autoload.php'; use GuzzleHttp\Promise\Promise; $parent = new Promise(); $p = $parent; for ($i = 0; $i < 1000; $i++) { $p = $p->then(function ($v) { // The stack size remains constant (a good thing) echo xdebug_get_stack_depth() . ', '; return $v + 1; }); } $parent->resolve(0); var_dump($p->wait()); // int(1000)
When a promise is fulfilled or rejected with a non-promise value, the promise then takes ownership of the handlers of each child promise and delivers values down the chain without using recursion.
When a promise is resolved with another promise, the original promise transfers all of its pending handlers to the new promise. When the new promise is eventually resolved, all of the pending handlers are delivered the forwarded value.
A Promise is the Deferred
Some promise libraries implement promises using a deferred object to represent a computation and a promise object to represent the delivery of the result of the computation. This is a nice separation of computation and delivery because consumers of the promise cannot modify the value that will be eventually delivered.
One side effect of being able to implement promise resolution and chaining iteratively is that you need to be able for one promise to reach into the state of another promise to shuffle around ownership of handlers. In order to achieve this without making the handlers of a promise publicly mutable, a promise is also the deferred value, allowing promises of the same parent class to reach into and modify the private properties of promises of the same type. While this does allow consumers of the value to modify the resolution or rejection of the deferred, it is a small price to pay for keeping the stack size constant.
$promise = new Promise(); $promise->then(function ($value) { echo $value; }); // The promise is the deferred value, so you can deliver a value to it. $promise->resolve('foo'); // prints "foo"
Upgrading from Function API
A static API was first introduced in 1.4.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience:
Original Function Replacement Method queue
Utils::queue
task
Utils::task
promise_for
Create::promiseFor
rejection_for
Create::rejectionFor
exception_for
Create::exceptionFor
iter_for
Create::iterFor
inspect
Utils::inspect
inspect_all
Utils::inspectAll
unwrap
Utils::unwrap
all
Utils::all
some
Utils::some
any
Utils::any
settle
Utils::settle
each
Each::of
each_limit
Each::ofLimit
each_limit_all
Each::ofLimitAll
!is_fulfilled
Is::pending
is_fulfilled
Is::fulfilled
is_rejected
Is::rejected
is_settled
Is::settled
coroutine
Coroutine::of
Security
If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see Security Policy for more information.
License
Guzzle is made available under the MIT License (MIT). Please see License File for more information.
For Enterprise
Available as part of the Tidelift Subscription
The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more.
- GuzzleHttp\Psr7 Readme
PSR-7 Message Implementation
This repository contains a full PSR-7 message implementation, several stream decorators, and some helpful functionality like query string parsing.
Features
This package comes with a number of stream implementations and stream decorators.
Installation
composer require guzzlehttp/psr7
Version Guidance
Version Status PHP Version 1.x EOL (2024-06-30) >=5.4,<8.2 2.x Latest >=7.2.5,<8.5 AppendStream
GuzzleHttp\Psr7\AppendStream
Reads from multiple streams, one after the other.
use GuzzleHttp\Psr7; $a = Psr7\Utils::streamFor('abc, '); $b = Psr7\Utils::streamFor('123.'); $composed = new Psr7\AppendStream([$a, $b]); $composed->addStream(Psr7\Utils::streamFor(' Above all listen to me')); echo $composed; // abc, 123. Above all listen to me.
BufferStream
GuzzleHttp\Psr7\BufferStream
Provides a buffer stream that can be written to fill a buffer, and read from to remove bytes from the buffer.
This stream returns a "hwm" metadata value that tells upstream consumers what the configured high water mark of the stream is, or the maximum preferred size of the buffer.
use GuzzleHttp\Psr7; // When more than 1024 bytes are in the buffer, it will begin returning // false to writes. This is an indication that writers should slow down. $buffer = new Psr7\BufferStream(1024);
CachingStream
The CachingStream is used to allow seeking over previously read bytes on non-seekable streams. This can be useful when transferring a non-seekable entity body fails due to needing to rewind the stream (for example, resulting from a redirect). Data that is read from the remote stream will be buffered in a PHP temp stream so that previously read bytes are cached first in memory, then on disk.
use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r')); $stream = new Psr7\CachingStream($original); $stream->read(1024); echo $stream->tell(); // 1024 $stream->seek(0); echo $stream->tell(); // 0
DroppingStream
GuzzleHttp\Psr7\DroppingStream
Stream decorator that begins dropping data once the size of the underlying stream becomes too full.
use GuzzleHttp\Psr7; // Create an empty stream $stream = Psr7\Utils::streamFor(); // Start dropping data when the stream has more than 10 bytes $dropping = new Psr7\DroppingStream($stream, 10); $dropping->write('01234567890123456789'); echo $stream; // 0123456789
FnStream
GuzzleHttp\Psr7\FnStream
Compose stream implementations based on a hash of functions.
Allows for easy testing and extension of a provided stream without needing to create a concrete class for a simple extension point.
use GuzzleHttp\Psr7; $stream = Psr7\Utils::streamFor('hi'); $fnStream = Psr7\FnStream::decorate($stream, [ 'rewind' => function () use ($stream) { echo 'About to rewind - '; $stream->rewind(); echo 'rewound!'; } ]); $fnStream->rewind(); // Outputs: About to rewind - rewound!
InflateStream
GuzzleHttp\Psr7\InflateStream
Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content.
This stream decorator converts the provided stream to a PHP stream resource, then appends the zlib.inflate filter. The stream is then converted back to a Guzzle stream resource to be used as a Guzzle stream.
LazyOpenStream
GuzzleHttp\Psr7\LazyOpenStream
Lazily reads or writes to a file that is opened only after an IO operation take place on the stream.
use GuzzleHttp\Psr7; $stream = new Psr7\LazyOpenStream('/path/to/file', 'r'); // The file has not yet been opened... echo $stream->read(10); // The file is opened and read from only when needed.
LimitStream
GuzzleHttp\Psr7\LimitStream
LimitStream can be used to read a subset or slice of an existing stream object. This can be useful for breaking a large file into smaller pieces to be sent in chunks (e.g. Amazon S3's multipart upload API).
use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+')); echo $original->getSize(); // >>> 1048576 // Limit the size of the body to 1024 bytes and start reading from byte 2048 $stream = new Psr7\LimitStream($original, 1024, 2048); echo $stream->getSize(); // >>> 1024 echo $stream->tell(); // >>> 0
MultipartStream
GuzzleHttp\Psr7\MultipartStream
Stream that when read returns bytes for a streaming multipart or multipart/form-data stream.
NoSeekStream
GuzzleHttp\Psr7\NoSeekStream
NoSeekStream wraps a stream and does not allow seeking.
use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor('foo'); $noSeek = new Psr7\NoSeekStream($original); echo $noSeek->read(3); // foo var_export($noSeek->isSeekable()); // false $noSeek->seek(0); var_export($noSeek->read(3)); // NULL
PumpStream
GuzzleHttp\Psr7\PumpStream
Provides a read only stream that pumps data from a PHP callable.
When invoking the provided callable, the PumpStream will pass the amount of data requested to read to the callable. The callable can choose to ignore this value and return fewer or more bytes than requested. Any extra data returned by the provided callable is buffered internally until drained using the read() function of the PumpStream. The provided callable MUST return false when there is no more data to read.
Implementing stream decorators
Creating a stream decorator is very easy thanks to the
GuzzleHttp\Psr7\StreamDecoratorTrait
. This trait provides methods that implementPsr\Http\Message\StreamInterface
by proxying to an underlying stream. Justuse
theStreamDecoratorTrait
and implement your custom methods.For example, let's say we wanted to call a specific function each time the last byte is read from a stream. This could be implemented by overriding the
read()
method.use Psr\Http\Message\StreamInterface; use GuzzleHttp\Psr7\StreamDecoratorTrait; class EofCallbackStream implements StreamInterface { use StreamDecoratorTrait; private $callback; private $stream; public function __construct(StreamInterface $stream, callable $cb) { $this->stream = $stream; $this->callback = $cb; } public function read($length) { $result = $this->stream->read($length); // Invoke the callback when EOF is hit. if ($this->eof()) { ($this->callback)(); } return $result; } }
This decorator could be added to any existing stream and used like so:
use GuzzleHttp\Psr7; $original = Psr7\Utils::streamFor('foo'); $eofStream = new EofCallbackStream($original, function () { echo 'EOF!'; }); $eofStream->read(2); $eofStream->read(1); // echoes "EOF!" $eofStream->seek(0); $eofStream->read(3); // echoes "EOF!"
PHP StreamWrapper
You can use the
GuzzleHttp\Psr7\StreamWrapper
class if you need to use a PSR-7 stream as a PHP stream resource.Use the
GuzzleHttp\Psr7\StreamWrapper::getResource()
method to create a PHP stream from a PSR-7 stream.use GuzzleHttp\Psr7\StreamWrapper; $stream = GuzzleHttp\Psr7\Utils::streamFor('hello!'); $resource = StreamWrapper::getResource($stream); echo fread($resource, 6); // outputs hello!
Static API
There are various static methods available under the
GuzzleHttp\Psr7
namespace.GuzzleHttp\Psr7\Message::toString
public static function toString(MessageInterface $message): string
Returns the string representation of an HTTP message.
$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com'); echo GuzzleHttp\Psr7\Message::toString($request);
GuzzleHttp\Psr7\Message::bodySummary
public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null
Get a short summary of the message body.
Will return
null
if the response is not printable.GuzzleHttp\Psr7\Message::rewindBody
public static function rewindBody(MessageInterface $message): void
Attempts to rewind a message body and throws an exception on failure.
The body of the message will only be rewound if a call to
tell()
returns a value other than0
.GuzzleHttp\Psr7\Message::parseMessage
public static function parseMessage(string $message): array
Parses an HTTP message into an associative array.
The array contains the "start-line" key containing the start line of the message, "headers" key containing an associative array of header array values, and a "body" key containing the body of the message.
GuzzleHttp\Psr7\Message::parseRequestUri
public static function parseRequestUri(string $path, array $headers): string
Constructs a URI for an HTTP request message.
GuzzleHttp\Psr7\Message::parseRequest
public static function parseRequest(string $message): Request
Parses a request message string into a request object.
GuzzleHttp\Psr7\Message::parseResponse
public static function parseResponse(string $message): Response
Parses a response message string into a response object.
GuzzleHttp\Psr7\Header::parse
public static function parse(string|array $header): array
Parse an array of header values containing ";" separated data into an array of associative arrays representing the header key value pair data of the header. When a parameter does not contain a value, but just contains a key, this function will inject a key with a '' string value.
GuzzleHttp\Psr7\Header::splitList
public static function splitList(string|string[] $header): string[]
Splits a HTTP header defined to contain a comma-separated list into each individual value:
$knownEtags = Header::splitList($request->getHeader('if-none-match'));
Example headers include
accept
,cache-control
andif-none-match
.GuzzleHttp\Psr7\Header::normalize
(deprecated)public static function normalize(string|array $header): array
Header::normalize()
is deprecated in favor ofHeader::splitList()
which performs the same operation with a cleaned up API and improved documentation.Converts an array of header values that may contain comma separated headers into an array of headers with no comma separated values.
GuzzleHttp\Psr7\Query::parse
public static function parse(string $str, int|bool $urlEncoding = true): array
Parse a query string into an associative array.
If multiple values are found for the same key, the value of that key value pair will become an array. This function does not parse nested PHP style arrays into an associative array (e.g.,
foo[a]=1&foo[b]=2
will be parsed into['foo[a]' => '1', 'foo[b]' => '2'])
.GuzzleHttp\Psr7\Query::build
public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string
Build a query string from an array of key value pairs.
This function can use the return value of
parse()
to build a query string. This function does not modify the provided keys when an array is encountered (likehttp_build_query()
would).GuzzleHttp\Psr7\Utils::caselessRemove
public static function caselessRemove(iterable<string> $keys, $keys, array $data): array
Remove the items given by the keys, case insensitively from the data.
GuzzleHttp\Psr7\Utils::copyToStream
public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void
Copy the contents of a stream into another stream until the given number of bytes have been read.
GuzzleHttp\Psr7\Utils::copyToString
public static function copyToString(StreamInterface $stream, int $maxLen = -1): string
Copy the contents of a stream into a string until the given number of bytes have been read.
GuzzleHttp\Psr7\Utils::hash
public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string
Calculate a hash of a stream.
This method reads the entire stream to calculate a rolling hash, based on PHP's
hash_init
functions.GuzzleHttp\Psr7\Utils::modifyRequest
public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface
Clone and modify a request with the given changes.
This method is useful for reducing the number of clones needed to mutate a message.
- method: (string) Changes the HTTP method.
- set_headers: (array) Sets the given headers.
- remove_headers: (array) Remove the given headers.
- body: (mixed) Sets the given body.
- uri: (UriInterface) Set the URI.
- query: (string) Set the query string value of the URI.
- version: (string) Set the protocol version.
GuzzleHttp\Psr7\Utils::readLine
public static function readLine(StreamInterface $stream, ?int $maxLength = null): string
Read a line from the stream up to the maximum allowed buffer length.
GuzzleHttp\Psr7\Utils::redactUserInfo
public static function redactUserInfo(UriInterface $uri): UriInterface
Redact the password in the user info part of a URI.
GuzzleHttp\Psr7\Utils::streamFor
public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface
Create a new stream based on the input type.
Options is an associative array that can contain the following keys:
- metadata: Array of custom metadata.
- size: Size of the stream.
This method accepts the following
$resource
types:-
Psr\Http\Message\StreamInterface
: Returns the value as-is. -
string
: Creates a stream object that uses the given string as the contents. -
resource
: Creates a stream object that wraps the given PHP stream resource. -
Iterator
: If the provided value implementsIterator
, then a read-only stream object will be created that wraps the given iterable. Each time the stream is read from, data from the iterator will fill a buffer and will be continuously called until the buffer is equal to the requested read size. Subsequent read calls will first read from the buffer and then callnext
on the underlying iterator until it is exhausted. -
object
with__toString()
: If the object has the__toString()
method, the object will be cast to a string and then a stream will be returned that uses the string value. -
NULL
: Whennull
is passed, an empty stream object is returned. -
callable
When a callable is passed, a read-only stream object will be created that invokes the given callable. The callable is invoked with the number of suggested bytes to read. The callable can return any number of bytes, but MUST returnfalse
when there is no more data to return. The stream object that wraps the callable will invoke the callable until the number of requested bytes are available. Any additional bytes will be buffered and used in subsequent reads.
$stream = GuzzleHttp\Psr7\Utils::streamFor('foo'); $stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r')); $generator = function ($bytes) { for ($i = 0; $i < $bytes; $i++) { yield ' '; } } $stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100));
GuzzleHttp\Psr7\Utils::tryFopen
public static function tryFopen(string $filename, string $mode): resource
Safely opens a PHP stream resource using a filename.
When fopen fails, PHP normally raises a warning. This function adds an error handler that checks for errors and throws an exception instead.
GuzzleHttp\Psr7\Utils::tryGetContents
public static function tryGetContents(resource $stream): string
Safely gets the contents of a given stream.
When stream_get_contents fails, PHP normally raises a warning. This function adds an error handler that checks for errors and throws an exception instead.
GuzzleHttp\Psr7\Utils::uriFor
public static function uriFor(string|UriInterface $uri): UriInterface
Returns a UriInterface for the given value.
This function accepts a string or UriInterface and returns a UriInterface for the given value. If the value is already a UriInterface, it is returned as-is.
GuzzleHttp\Psr7\MimeType::fromFilename
public static function fromFilename(string $filename): string|null
Determines the mimetype of a file by looking at its extension.
GuzzleHttp\Psr7\MimeType::fromExtension
public static function fromExtension(string $extension): string|null
Maps a file extensions to a mimetype.
Upgrading from Function API
The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience:
Original Function Replacement Method str
Message::toString
uri_for
Utils::uriFor
stream_for
Utils::streamFor
parse_header
Header::parse
normalize_header
Header::normalize
modify_request
Utils::modifyRequest
rewind_body
Message::rewindBody
try_fopen
Utils::tryFopen
copy_to_string
Utils::copyToString
copy_to_stream
Utils::copyToStream
hash
Utils::hash
readline
Utils::readLine
parse_request
Message::parseRequest
parse_response
Message::parseResponse
parse_query
Query::parse
build_query
Query::build
mimetype_from_filename
MimeType::fromFilename
mimetype_from_extension
MimeType::fromExtension
_parse_message
Message::parseMessage
_parse_request_uri
Message::parseRequestUri
get_message_body_summary
Message::bodySummary
_caseless_remove
Utils::caselessRemove
Additional URI Methods
Aside from the standard
Psr\Http\Message\UriInterface
implementation in form of theGuzzleHttp\Psr7\Uri
class, this library also provides additional functionality when working with URIs as static methods.URI Types
An instance of
Psr\Http\Message\UriInterface
can either be an absolute URI or a relative reference. An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI, the base URI. Relative references can be divided into several forms according to RFC 3986 Section 4.2:- network-path references, e.g.
//example.com/path
- absolute-path references, e.g.
/path
- relative-path references, e.g.
subpath
The following methods can be used to identify the type of the URI.
GuzzleHttp\Psr7\Uri::isAbsolute
public static function isAbsolute(UriInterface $uri): bool
Whether the URI is absolute, i.e. it has a scheme.
GuzzleHttp\Psr7\Uri::isNetworkPathReference
public static function isNetworkPathReference(UriInterface $uri): bool
Whether the URI is a network-path reference. A relative reference that begins with two slash characters is termed an network-path reference.
GuzzleHttp\Psr7\Uri::isAbsolutePathReference
public static function isAbsolutePathReference(UriInterface $uri): bool
Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is termed an absolute-path reference.
GuzzleHttp\Psr7\Uri::isRelativePathReference
public static function isRelativePathReference(UriInterface $uri): bool
Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is termed a relative-path reference.
GuzzleHttp\Psr7\Uri::isSameDocumentReference
public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool
Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its fragment component, identical to the base URI. When no base URI is given, only an empty URI reference (apart from its fragment) is considered a same-document reference.
URI Components
Additional methods to work with URI components.
GuzzleHttp\Psr7\Uri::isDefaultPort
public static function isDefaultPort(UriInterface $uri): bool
Whether the URI has the default port of the current scheme.
Psr\Http\Message\UriInterface::getPort
may return null or the standard port. This method can be used independently of the implementation.GuzzleHttp\Psr7\Uri::composeComponents
public static function composeComponents($scheme, $authority, $path, $query, $fragment): string
Composes a URI reference string from its various components according to RFC 3986 Section 5.3. Usually this method does not need to be called manually but instead is used indirectly via
Psr\Http\Message\UriInterface::__toString
.GuzzleHttp\Psr7\Uri::fromParts
public static function fromParts(array $parts): UriInterface
Creates a URI from a hash of
parse_url
components.GuzzleHttp\Psr7\Uri::withQueryValue
public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface
Creates a new URI with a specific query string value. Any existing query string values that exactly match the provided key are removed and replaced with the given key value pair. A value of null will set the query string key without a value, e.g. "key" instead of "key=value".
GuzzleHttp\Psr7\Uri::withQueryValues
public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface
Creates a new URI with multiple query string values. It has the same behavior as
withQueryValue()
but for an associative array of key => value.GuzzleHttp\Psr7\Uri::withoutQueryValue
public static function withoutQueryValue(UriInterface $uri, $key): UriInterface
Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the provided key are removed.
Cross-Origin Detection
GuzzleHttp\Psr7\UriComparator
provides methods to determine if a modified URL should be considered cross-origin.GuzzleHttp\Psr7\UriComparator::isCrossOrigin
public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool
Determines if a modified URL should be considered cross-origin with respect to an original URL.
Reference Resolution
GuzzleHttp\Psr7\UriResolver
provides methods to resolve a URI reference in the context of a base URI according to RFC 3986 Section 5. This is for example also what web browsers do when resolving a link in a website based on the current request URI.GuzzleHttp\Psr7\UriResolver::resolve
public static function resolve(UriInterface $base, UriInterface $rel): UriInterface
Converts the relative URI into a new URI that is resolved against the base URI.
GuzzleHttp\Psr7\UriResolver::removeDotSegments
public static function removeDotSegments(string $path): string
Removes dot segments from a path and returns the new path according to RFC 3986 Section 5.2.4.
GuzzleHttp\Psr7\UriResolver::relativize
public static function relativize(UriInterface $base, UriInterface $target): UriInterface
Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
One use-case is to use the current request URI as base URI and then generate relative links in your documents to reduce the document size or offer self-contained downloadable document archives.
$base = new Uri('http://example.com/a/b/'); echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
Normalization and Comparison
GuzzleHttp\Psr7\UriNormalizer
provides methods to normalize and compare URIs according to RFC 3986 Section 6.GuzzleHttp\Psr7\UriNormalizer::normalize
public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface
Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface. This methods adds additional normalizations that can be configured with the
$flags
parameter which is a bitmask of normalizations to apply. The following normalizations are available:-
UriNormalizer::PRESERVING_NORMALIZATIONS
Default normalizations which only include the ones that preserve semantics.
-
UriNormalizer::CAPITALIZE_PERCENT_ENCODING
All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
Example:
http://example.org/a%c2%b1b
→http://example.org/a%C2%B1b
-
UriNormalizer::DECODE_UNRESERVED_CHARACTERS
Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved characters by URI normalizers.
Example:
http://example.org/%7Eusern%61me/
→http://example.org/~username/
-
UriNormalizer::CONVERT_EMPTY_PATH
Converts the empty path to "/" for http and https URIs.
Example:
http://example.org
→http://example.org/
-
UriNormalizer::REMOVE_DEFAULT_HOST
Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host "localhost". All of
file:/myfile
,file:///myfile
, andfile://localhost/myfile
are equivalent according to RFC 3986.Example:
file://localhost/myfile
→file:///myfile
-
UriNormalizer::REMOVE_DEFAULT_PORT
Removes the default port of the given URI scheme from the URI.
Example:
http://example.org:80/
→http://example.org/
-
UriNormalizer::REMOVE_DOT_SEGMENTS
Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would change the semantics of the URI reference.
Example:
http://example.org/../a/b/../c/./d.html
→http://example.org/a/c/d.html
-
UriNormalizer::REMOVE_DUPLICATE_SLASHES
Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization may change the semantics. Encoded slashes (%2F) are not removed.
Example:
http://example.org//foo///bar.html
→http://example.org/foo/bar.html
-
UriNormalizer::SORT_QUERY_PARAMETERS
Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be significant (this is not defined by the standard). So this normalization is not safe and may change the semantics of the URI.
Example:
?lang=en&article=fred
→?article=fred&lang=en
GuzzleHttp\Psr7\UriNormalizer::isEquivalent
public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool
Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
$normalizations
bitmask. The method also accepts relative URI references and returns true when they are equivalent. This of course assumes they will be resolved against the same base URI. If this is not the case, determination of equivalence or difference of relative references does not mean anything.Security
If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see Security Policy for more information.
License
Guzzle is made available under the MIT License (MIT). Please see License File for more information.
For Enterprise
Available as part of the Tidelift Subscription
The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more.
- Highlight Readme
highlight.php
highlight.php is a server-side syntax highlighter written in PHP that currently supports 185 languages. It's a port of highlight.js by Ivan Sagalaev that makes full use of the language and style definitions of the original JavaScript project.
This is the README for highlight.php v10, which is currently under development. The latest stable release is the 9.18.x series.
Table of Contents
Installation + Setup
The recommended approach is to install the project through Composer.
composer require scrivo/highlight.php
If you're not using Composer, ensure that the classes defined in the
Highlight
namespace can be found either by inclusion or by an autoloader. A trivial autoloader for this purpose is included in this project asHighlight\Autoloader.php
Composer Version Constraints
When requiring this project in your
composer.json
, it is recommended you use the caret version range and use only the major and minor values; i.e.^9.14
.It's come to our attention that a lot of tutorials and projects out there are locking themselves into highly specific versions of this project; e.g.
"scrivo/highlight.php": "v9.12.0.1"
. Please do not do this or encourage it. We promise a reliable backward compatibility policy so there's no reason to lock yourself to such a specific version. By doing this, you are preventing yourself or your users from receiving updates to language definitions and bug fixes.Usage
The
\Highlight\Highlighter
class contains the syntax highlighting functionality. You can choose between two highlighting modes:- explicit mode
- automatic language detection mode
Explicit Mode
In explicit mode, you must define which language you will be highlighting as.
// Instantiate the Highlighter. $hl = new \Highlight\Highlighter(); $code = file_get_contents('some_ruby_script.rb'); try { // Highlight some code. $highlighted = $hl->highlight('ruby', $code); echo "<pre><code class=\"hljs {$highlighted->language}\">"; echo $highlighted->value; echo "</code></pre>"; } catch (DomainException $e) { // This is thrown if the specified language does not exist echo "<pre><code>"; echo htmlentities($code); echo "</code></pre>"; }
Automatic Language Detection Mode
Alternatively you can use the automatic detection mode, which highlights your code with the language the library thinks is best. It is highly recommended you explicitly choose the language or limit the number of languages to automatically detect to reduce the number of inaccuracies.
Warning: Auto-detection occurs in a brute force fashion and the language with the most accurate result will be selected. This is extremely inefficient as you supply more languages and may not always be 100% accurate if similar languages are configured.
$hl = new \Highlight\Highlighter(); $hl->setAutodetectLanguages(array('ruby', 'python', 'perl')); $highlighted = $hl->highlightAuto(file_get_contents('some_ruby_script.rb')); echo "<pre><code class=\"hljs {$highlighted->language}\">"; echo $highlighted->value; echo "</code></pre>";
Default Languages
If no autodetect languages are set in the highlighter, then every language will be used and cause significant performance issues.
Stylesheets
The same stylesheets available in the highlight.js project are available in the
styles
directory of this project and may be included in your own CSS or made accessible to your web server.Highlighter Utilities
The core of the project is loyal port of highlight.js and is available under the main
Highlight
namespace. A series of convenience functions are provided under theHighlightUtilities
namespace to introduce additional functionality without the need for another dependency.Available functions:
-
getAvailableStyleSheets(bool $filePaths = false): string[]
-
getStyleSheet(string $name): false|string
-
getStyleSheetFolder(): string
-
getStyleSheetPath(string $name): string
-
getLanguagesFolder(): string
-
getLanguageDefinitionPath(string $name): string
-
getThemeBackgroundColor(string $name): float[]
-
splitCodeIntoArray(string $html): false|string[]
Versioning
This project will follow the same version numbers as the highlight.js project with regards to languages, meaning that a language definition available in highlight.js 9.12.0 will be available in highlight.php 9.12.0. However, there are times where bugs may arise in this project or its translated definition files, so there'll be one more number appended to the version number. For example, version 9.12.0.1 will contain all of the same languages as highlight.js 9.12.0 but also contain fixes solely to this project. This is done so this project can have version bumps without conflicts should highlight.js release version 9.12.1.
Backward Compatibility Promise
Despite the fact that the semantic versioning used in this project mirrors that of highlight.js, this project will adhere to Symfony's Backward Compatibility Promise. You can rest assured that there will be no breaking changes during
9.x
and any deprecations will be marked with@deprecated
and won't be removed until the next major release.Some History
Geert Bergman Sep 30, 2013
JavaScript code highlighting is very convenient and in many cases just what you want to use. Especially for programming blogs I would not advice you to use otherwise. But there are occasions where you're better off with a more 'static' approach, for instance if you want to send highlighted code in an email or for API documents. For this I needed a code highlighting program preferably written in PHP.
I couldn't found any satisfactory PHP solution so I decided to port one from JavaScript. After some comparison of different highlighting programs based on license, technology, language support highlight.js came out most favorable in my opinion.
It was my decision not to make a PHP highlighter but to do a port of highlight.js, these are different things. The goal was to make it work exactly as highlight.js to make as much use as possible of the language definitions and CSS files of the original program.
Happy coding!
License
- ICalendarOrg Readme
Zap Calendar iCalendar Library
A modern 7.4 namespaced fork of Zap Calendar iCalendar Library
The Zap Calendar iCalendar Library is a PHP library for supporting the iCalendar (RFC 5545) standard.
This PHP library is for reading and writing iCalendar formatted feeds and files. Features of the library include:
- Read AND write support for iCalendar files
- Object based creation and manipulation of iCalendar files
- Supports expansion of RRULE to a list of repeating dates
- Supports adding timezone info to iCalendar file
All iCalendar data is stored in a PHP object tree. This allows any property to be added to the iCalendar feed without requiring specialized library function calls. With power comes responsibility. Missing or invalid properties can cause the resulting iCalendar file to be invalid. Visit iCalendar.org to view valid properties and test your feed using the site's iCalendar validator tool.
Library API documentation can be found at http://icalendar.org/zapcallibdocs and PHPFUI/ICalendarOrg
See the examples folder for programs that read and write iCalendar files. Best to include the sample files into a file with an active autoloader or include all the classes to run the examples directly.
Create an ical object using the ZCiCal object:
$icalobj = new \ICalendarOrg\ZCiCal();
Add an event object:
$eventobj = new \ICalendarOrg\ZCiCalNode("VEVENT", $icalobj->curnode);
Add a start and end date to the event:
// add start date $eventobj->addNode(new \ICalendarOrg\ZCiCalDataNode("DTSTART:" . \ICalendarOrg\ZDateHelper::fromSqlDateTime("2020-01-01 12:00:00"))); // add end date $eventobj->addNode(new \ICalendarOrg\ZCiCalDataNode("DTEND:" . \ICalendarOrg\ZDateHelper::fromSqlDateTime("2020-01-01 13:00:00")));
Write the object in iCalendar format using the export() function call:
echo $icalobj->export();
This example will not validate since it is missing some required elements. Look at the simpleevent.php example for the minimum # of elements needed for a validated iCalendar file.
To create a multi-event iCalendar file, simply create multiple event objects. For example:
$icalobj = new \ICalendarOrg\ZCiCal(); $eventobj1 = new \ICalendarOrg\ZCiCalNode("VEVENT", $icalobj->curnode); $eventobj1->addNode(new \ICalendarOrg\ZCiCalDataNode("SUMMARY:Event 1")); ... $eventobj2 = new \ICalendarOrg\ZCiCalNode("VEVENT", $icalobj->curnode); $eventobj2->addNode(new \ICalendarOrg\ZCiCalDataNode("SUMMARY:Event 2")); ...
To read an existing iCalendar file/feed, create the ZCiCal object with a string representing the contents of the iCalendar file:
$icalobj = new \ICalendarOrg\ZCiCal($icalstring);
Large iCalendar files can be read in chunks to reduce the amount of memory needed to hold the iCalendar feed in memory. This example reads 500 events at a time:
$icalobj = null; $eventcount = 0; $maxevents = 500; do { $icalobj = new \ICalendarOrg\ZCiCal($icalstring, $maxevents, $eventcount); ... $eventcount += $maxevents; } while($icalobj->countEvents() >= $eventcount);
You can read the events from an imported (or created) iCalendar object in this manner:
foreach($icalobj->tree->child as $node) { if($node->getName() == "VEVENT") { foreach($node->data as $key => $value) { if($key == "SUMMARY") { echo "event title: " . $value->getValues() . "\n"; } } } }
Known Limitations
- Since the library utilizes objects to read and write iCalendar data, the size of the iCalendar data is limited to the amount of available memory on the machine. The ZCiCal() object supports reading a range of events to minimize memory space.
- The library ignores timezone info when importing files, instead utilizing PHP's timezone library for calculations (timezones are supported when exporting files). Imported timezones need to be aliased to a PHP supported timezone.
- At this time, the library does not support the "BYSETPOS" option in RRULE items.
- League\CommonMark Readme
league/commonmark
league/commonmark is a highly-extensible PHP Markdown parser created by Colin O'Dell which supports the full CommonMark spec and GitHub-Flavored Markdown. It is based on the CommonMark JS reference implementation by John MacFarlane (@jgm).
📦 Installation & Basic Usage
This project requires PHP 7.4 or higher with the
mbstring
extension. To install it via Composer simply run:$ composer require league/commonmark
The
CommonMarkConverter
class provides a simple wrapper for converting CommonMark to HTML:use League\CommonMark\CommonMarkConverter; $converter = new CommonMarkConverter([ 'html_input' => 'strip', 'allow_unsafe_links' => false, ]); echo $converter->convert('# Hello World!'); // <h1>Hello World!</h1>
Or if you want GitHub-Flavored Markdown, use the
GithubFlavoredMarkdownConverter
class instead:use League\CommonMark\GithubFlavoredMarkdownConverter; $converter = new GithubFlavoredMarkdownConverter([ 'html_input' => 'strip', 'allow_unsafe_links' => false, ]); echo $converter->convert('# Hello World!'); // <h1>Hello World!</h1>
Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.
[!CAUTION] If you will be parsing untrusted input from users, please consider setting the
html_input
andallow_unsafe_links
options per the example above. See https://commonmark.thephpleague.com/security/ for more details. If you also do choose to allow raw HTML input from untrusted users, consider using a library (like HTML Purifier) to provide additional HTML filtering.📓 Documentation
Full documentation on advanced usage, configuration, and customization can be found at commonmark.thephpleague.com.
⏫ Upgrading
Information on how to upgrade to newer versions of this library can be found at https://commonmark.thephpleague.com/releases.
💻 GitHub-Flavored Markdown
The
GithubFlavoredMarkdownConverter
shown earlier is a drop-in replacement for theCommonMarkConverter
which adds additional features found in the GFM spec:- Autolinks
- Disallowed raw HTML
- Strikethrough
- Tables
- Task Lists
See the Extensions documentation for more details on how to include only certain GFM features if you don't want them all.
🗃️ Related Packages
Integrations
- CakePHP 3
- Drupal
- Laravel 4+
- Sculpin
- Symfony 2 & 3
- Symfony 4
- Twig Markdown extension
- Twig filter and tag
- Laravel CommonMark Blog
Included Extensions
See our extension documentation for a full list of extensions bundled with this library.
Community Extensions
Custom parsers/renderers can be bundled into extensions which extend CommonMark. Here are some that you may find interesting:
- Emoji extension - UTF-8 emoji extension with Github tag.
-
Sup Sub extensions - Adds support of superscript and subscript (
<sup>
and<sub>
HTML tags) - YouTube iframe extension - Replaces youtube link with iframe.
- Lazy Image extension - Adds various options for lazy loading of images.
-
Marker Extension - Adds support of highlighted text (
<mark>
HTML tag)
Others can be found on Packagist under the
commonmark-extension
package type.If you build your own, feel free to submit a PR to add it to this list!
Others
Check out the other cool things people are doing with
league/commonmark
: https://packagist.org/packages/league/commonmark/dependents🏷️ Versioning
SemVer is followed closely. Minor and patch releases should not introduce breaking changes to the codebase; however, they might change the resulting AST or HTML output of parsed Markdown (due to bug fixes, spec changes, etc.) As a result, you might get slightly different HTML, but any custom code built onto this library should still function correctly.
Any classes or methods marked
@internal
are not intended for use outside of this library and are subject to breaking changes at any time, so please avoid using them.🛠️ Maintenance & Support
When a new minor version (e.g.
2.0
->2.1
) is released, the previous one (2.0
) will continue to receive security and critical bug fixes for at least 3 months.When a new major version is released (e.g.
1.6
->2.0
), the previous one (1.6
) will receive critical bug fixes for at least 3 months and security updates for 6 months after that new release comes out.(This policy may change in the future and exceptions may be made on a case-by-case basis.)
Professional support, including notification of new releases and security updates, is available through a Tidelift Subscription.
👷♀️ Contributing
To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure with us.
If you encounter a bug in the spec, please report it to the CommonMark project. Any resulting fix will eventually be implemented in this project as well.
Contributions to this library are welcome, especially ones that:
- Improve usability or flexibility without compromising our ability to adhere to the CommonMark spec
- Mirror fixes made to the reference implementation
- Optimize performance
- Fix issues with adhering to the CommonMark spec
Major refactoring to core parsing logic should be avoided if possible so that we can easily follow updates made to the reference implementation. That being said, we will absolutely consider changes which don't deviate too far from the reference spec or which are favored by other popular CommonMark implementations.
Please see CONTRIBUTING for additional details.
🧪 Testing
$ composer test
This will also test league/commonmark against the latest supported spec.
🚀 Performance Benchmarks
You can compare the performance of league/commonmark to other popular parsers by running the included benchmark tool:
$ ./tests/benchmark/benchmark.php
👥 Credits & Acknowledgements
This code was originally based on the CommonMark JS reference implementation which is written, maintained, and copyrighted by John MacFarlane. This project simply wouldn't exist without his work.
And a huge thanks to all of our amazing contributors:
Sponsors
We'd also like to extend our sincere thanks the following sponsors who support ongoing development of this project:
- Tidelift for offering support to both the maintainers and end-users through their professional support program
- Blackfire for providing an Open-Source Profiler subscription
- JetBrains for supporting this project with complimentary PhpStorm licenses
Are you interested in sponsoring development of this project? See https://www.colinodell.com/sponsor for a list of ways to contribute.
📄 License
league/commonmark is licensed under the BSD-3 license. See the
LICENSE
file for more details.🏛️ Governance
This project is primarily maintained by Colin O'Dell. Members of the PHP League Leadership Team may occasionally assist with some of these duties.
🗺️ Who Uses It?
This project is used by Drupal, Laravel Framework, Cachet, Firefly III, Neos, Daux.io, and more!
- League\Config Readme
league/config
league/config helps you define nested configuration arrays with strict schemas and access configuration values with dot notation. It was created by Colin O'Dell.
📦 Installation
This project requires PHP 7.4 or higher. To install it via Composer simply run:
composer require league/config
🧰️ Basic Usage
The
Configuration
class provides everything you need to define the configuration structure and fetch values:use League\Config\Configuration; use Nette\Schema\Expect; // Define your configuration schema $config = new Configuration([ 'database' => Expect::structure([ 'driver' => Expect::anyOf('mysql', 'postgresql', 'sqlite')->required(), 'host' => Expect::string()->default('localhost'), 'port' => Expect::int()->min(1)->max(65535), 'ssl' => Expect::bool(), 'database' => Expect::string()->required(), 'username' => Expect::string()->required(), 'password' => Expect::string()->nullable(), ]), 'logging' => Expect::structure([ 'enabled' => Expect::bool()->default($_ENV['DEBUG'] == true), 'file' => Expect::string()->deprecated("use logging.path instead"), 'path' => Expect::string()->assert(function ($path) { return \is_writeable($path); })->required(), ]), ]); // Set the values, either all at once with `merge()`: $config->merge([ 'database' => [ 'driver' => 'mysql', 'port' => 3306, 'database' => 'mydb', 'username' => 'user', 'password' => 'secret', ], ]); // Or one-at-a-time with `set()`: $config->set('logging.path', '/var/log/myapp.log'); // You can now retrieve those values with `get()`. // Validation and defaults will be applied for you automatically $config->get('database'); // Fetches the entire "database" section as an array $config->get('database.driver'); // Fetch a specific nested value with dot notation $config->get('database/driver'); // Fetch a specific nested value with slash notation $config->get('database.host'); // Returns the default value "localhost" $config->get('logging.path'); // Guaranteed to be writeable thanks to the assertion in the schema // If validation fails an `InvalidConfigurationException` will be thrown: $config->set('database.driver', 'mongodb'); $config->get('database.driver'); // InvalidConfigurationException // Attempting to fetch a non-existent key will result in an `InvalidConfigurationException` $config->get('foo.bar'); // You could avoid this by checking whether that item exists: $config->exists('foo.bar'); // Returns `false`
📓 Documentation
Full documentation can be found at config.thephpleague.com.
💭 Philosophy
This library aims to provide a simple yet opinionated approach to configuration with the following goals:
- The configuration should operate on arrays with nested values which are easily accessible
- The configuration structure should be defined with strict schemas defining the overall structure, allowed types, and allowed values
- Schemas should be defined using a simple, fluent interface
- You should be able to add and combine schemas but never modify existing ones
- Both the configuration values and the schema should be defined and managed with PHP code
- Schemas should be immutable; they should never change once they are set
- Configuration values should never define or influence the schemas
As a result, this library will likely never support features like:
- Loading and/or exporting configuration values or schemas using YAML, XML, or other files
- Parsing configuration values from a command line or other user interface
- Dynamically changing the schema, allowed values, or default values based on other configuration values
If you need that functionality you should check out other libraries like:
- symfony/config
- symfony/options-resolver
- hassankhan/config
- consolidation/config
- laminas/laminas-config
🏷️ Versioning
SemVer is followed closely. Minor and patch releases should not introduce breaking changes to the codebase.
Any classes or methods marked
@internal
are not intended for use outside this library and are subject to breaking changes at any time, so please avoid using them.🛠️ Maintenance & Support
When a new minor version (e.g.
1.0
->1.1
) is released, the previous one (1.0
) will continue to receive security and critical bug fixes for at least 3 months.When a new major version is released (e.g.
1.1
->2.0
), the previous one (1.1
) will receive critical bug fixes for at least 3 months and security updates for 6 months after that new release comes out.(This policy may change in the future and exceptions may be made on a case-by-case basis.)
👷️ Contributing
Contributions to this library are welcome! We only ask that you adhere to our contributor guidelines and avoid making changes that conflict with our Philosophy above.
🧪 Testing
composer test
📄 License
league/config is licensed under the BSD-3 license. See the
LICENSE.md
file for more details.🗺️ Who Uses It?
This project is used by league/commonmark.
- League\Geotools Readme
Geotools
Geotools is a PHP geo-related library, built atop Geocoder and React libraries.
Features
- Batch geocode & reverse geocoding request(s) in series / in parallel against one or a set of providers. »
- Cache geocode & reverse geocoding result(s) with PSR-6 to improve performances. »
- Compute geocode & reverse geocoding in the command-line interface (CLI) + dumpers and formatters. »
- Accept almost all kind of WGS84 geographic coordinates as coordinates. »
- Support 23 different ellipsoids and it's easy to provide a new one if needed. »
- Convert and format decimal degrees coordinates to decimal minutes or degrees minutes seconds coordinates. »
- Convert decimal degrees coordinates in the Universal Transverse Mercator (UTM) projection. »
- Compute the distance in meter (by default), km, mi or ft between two coordinates using flat, great circle, haversine or vincenty algorithms. »
- Compute the initial and final bearing from the origin coordinate to the destination coordinate in degrees. »
- Compute the initial and final cardinal point (direction) from the origin coordinate to the destination coordinate, read more in wikipedia. »
- Compute the half-way point (coordinate) between the origin and the destination coordinates. »
- Compute the destination point (coordinate) with given bearing in degrees and a distance in meters. »
- Encode a coordinate to a geo hash string and decode it to a coordinate, read more in wikipedia and on geohash.org. »
- Encode a coordinate via the 10:10 algorithm. »
- Polygon class provides methods to check either a poing (coordinate) is in, or on the polygon's boundaries. »
- A command-line interface (CLI) for Distance, Point, Geohash and Convert classes. »
- Integration with Frameworks: Laravel 4, Silex ... »
- ... more to come ...
Installation
Geotools can be found on Packagist. The recommended way to install Geotools is through composer.
Run the following on the command line:
composer require league/geotools
Important: you should use the
0.4
version if you use Geocoder2.x
or/and PHP5.3
.And install dependencies:
composer install
Now you can add the autoloader, and you will have access to the library:
require 'vendor/autoload.php';
Usage & API
Coordinate & Ellipsoid
The default geodetic datum is WGS84 and coordinates are in decimal degrees.
Here are the available ellipsoids:
AIRY
,AUSTRALIAN_NATIONAL
,BESSEL_1841
,BESSEL_1841_NAMBIA
,CLARKE_1866
,CLARKE_1880
,EVEREST
,FISCHER_1960_MERCURY
,FISCHER_1968
,GRS_1967
,GRS_1980
,HELMERT_1906
,HOUGH
,INTERNATIONAL
,KRASSOVSKY
,MODIFIED_AIRY
,MODIFIED_EVEREST
,MODIFIED_FISCHER_1960
,SOUTH_AMERICAN_1969
,WGS60
,WGS66
,WGS72
, andWGS84
.If you need to use an other ellipsoid, just create an array like this:
$myEllipsoid = \League\Geotools\Coordinate\Ellipsoid::createFromArray([ 'name' => 'My Ellipsoid', // The name of the Ellipsoid 'a' => 123.0, // The semi-major axis (equatorial radius) in meters 'invF' => 456.0 // The inverse flattening ]);
Geotools is built atop Geocoder. It means it's possible to use the
\Geocoder\Model\Address
directly but it's also possible to use a string or a simple array with its latitude and longitude.It supports valid and acceptable geographic coordinates like:
- 40:26:46N,079:56:55W
- 40:26:46.302N 079:56:55.903W
- 40°26′47″N 079°58′36″W
- 40d 26′ 47″ N 079d 58′ 36″ W
- 40.446195N 79.948862W
- 40.446195, -79.948862
- 40° 26.7717, -79° 56.93172
Latitudes below -90.0 or above 90.0 degrees are capped through
\League\Geotools\Coordinate\Coordinate::normalizeLatitude()
. Longitudes below -180.0 or above 180.0 degrees are wrapped through\League\Geotools\Coordinate\Coordinate::normalizeLongitude()
.use League\Geotools\Coordinate\Coordinate; use League\Geotools\Coordinate\Ellipsoid; // from an \Geocoder\Model\Address instance within Airy ellipsoid $coordinate = new Coordinate($geocoderResult, Ellipsoid::createFromName(Ellipsoid::AIRY)); // or in an array of latitude/longitude coordinate within GRS 1980 ellipsoid $coordinate = new Coordinate([48.8234055, 2.3072664], Ellipsoid::createFromName(Ellipsoid::GRS_1980)); // or in latitude/longitude coordinate within WGS84 ellipsoid $coordinate = new Coordinate('48.8234055, 2.3072664'); // or in degrees minutes seconds coordinate within WGS84 ellipsoid $coordinate = new Coordinate('48°49′24″N, 2°18′26″E'); // or in decimal minutes coordinate within WGS84 ellipsoid $coordinate = new Coordinate('48 49.4N, 2 18.43333E'); // the result will be: printf("Latitude: %F\n", $coordinate->getLatitude()); // 48.8234055 printf("Longitude: %F\n", $coordinate->getLongitude()); // 2.3072664 printf("Ellipsoid name: %s\n", $coordinate->getEllipsoid()->getName()); // WGS 84 printf("Equatorial radius: %F\n", $coordinate->getEllipsoid()->getA()); // 6378136.0 printf("Polar distance: %F\n", $coordinate->getEllipsoid()->getB()); // 6356751.317598 printf("Inverse flattening: %F\n", $coordinate->getEllipsoid()->getInvF()); // 298.257224 printf("Mean radius: %F\n", $coordinate->getEllipsoid()->getArithmeticMeanRadius()); // 6371007.772533 // it's also possible to modify the coordinate without creating an other coodinate $coordinate->setFromString('40°26′47″N 079°58′36″W'); printf("Latitude: %F\n", $coordinate->getLatitude()); // 40.446388888889 printf("Longitude: %F\n", $coordinate->getLongitude()); // -79.976666666667
Convert
It provides methods (and aliases) to convert decimal degrees WGS84 coordinates to degrees minutes seconds or decimal minutes WGS84 coordinates. You can format the output string easily.
You can also convert them in the Universal Transverse Mercator (UTM) projection (Southwest coast of Norway and the region of Svalbard are covered).
$geotools = new \League\Geotools\Geotools(); $coordinate = new \League\Geotools\Coordinate\Coordinate('40.446195, -79.948862'); $converted = $geotools->convert($coordinate); // convert to decimal degrees without and with format string printf("%s\n", $converted->toDecimalMinutes()); // 40 26.7717N, -79 56.93172W // convert to degrees minutes seconds without and with format string printf("%s\n", $converted->toDegreesMinutesSeconds('<p>%P%D:%M:%S, %p%d:%m:%s</p>')); // <p>40:26:46, -79:56:56</p> // convert in the UTM projection (standard format) printf("%s\n", $converted->toUniversalTransverseMercator()); // 17T 589138 4477813
Here is the mapping:
Decimal minutes Latitude Longitude Positive or negative sign %P
%p
Direction %L
%l
Degrees %D
%d
Decimal minutes %N
%n
Degrees minutes seconds Latitude Longitude Positive or negative sign %P
%p
Direction %L
%l
Degrees %D
%d
Minutes %M
%m
Seconds %S
%s
Batch
It provides a very handy way to batch geocode and reverse geocoding requests in serie or in parallel against a set of providers. Thanks to Geocoder and React libraries.
It's possible to batch one request (a string) or a set of request (an array) against one provider or set of providers.
You can use a provided cache engine or use your own by setting a cache object which should implement
League\Geotools\Cache\CacheInterface
and extendLeague\Geotools\Cache\AbstractCache
if needed.At the moment Geotools supports any PSR-6 cache.
NB: Before you implement caching in your app please be sure that doing so does not violate the Terms of Service for your(s) geocoding provider(s).
$geocoder = new \Geocoder\ProviderAggregator(); // or \Geocoder\TimedGeocoder $httpClient = HttpClientDiscovery::find(); $geocoder->registerProviders([ new \Geocoder\Provider\GoogleMaps\GoogleMaps($httpClient), new \Geocoder\Provider\OpenStreetMap\OpenStreetMap($httpClient), new \Geocoder\Provider\BingMaps\BingMaps($httpClient, '<FAKE_API_KEY>'), // throws InvalidCredentialsException new \Geocoder\Provider\Yandex\Yandex($httpClient), new \Geocoder\Provider\FreeGeoIp\FreeGeoIp($httpClient), new \Geocoder\Provider\Geoip\Geoip(), ]); try { $geotools = new \League\Geotools\Geotools(); $cache = new \Cache\Adapter\PHPArray\ArrayCachePool(); $results = $geotools->batch($geocoder)->setCache($cache)->geocode([ 'Paris, France', 'Copenhagen, Denmark', '74.200.247.59', '::ffff:66.147.244.214' ])->parallel(); } catch (\Exception $e) { die($e->getMessage()); } $dumper = new \Geocoder\Dumper\WktDumper(); foreach ($results as $result) { // if a provider throws an exception (UnsupportedException, InvalidCredentialsException ...) // an custom /Geocoder/Result/Geocoded instance is returned which embedded the name of the provider, // the query string and the exception string. It's possible to use dumpers // and/or formatters from the Geocoder library. printf("%s|%s|%s\n", $result->getProviderName(), $result->getQuery(), '' == $result->getExceptionMessage() ? $dumper->dump($result) : $result->getExceptionMessage() ); }
You should get 24 results (4 values to geocode against 6 providers) something like:
google_maps|Paris, France|POINT(2.352222 48.856614) google_maps|Copenhagen, Denmark|POINT(12.568337 55.676097) google_maps|74.200.247.59|The GoogleMapsProvider does not support IP addresses. google_maps|::ffff:66.147.244.214|The GoogleMapsProvider does not support IP addresses. openstreetmap|Paris, France|POINT(2.352133 48.856506) openstreetmap|Copenhagen, Denmark|POINT(12.570072 55.686724) openstreetmap|74.200.247.59|Could not execute query http://nominatim.openstreetmap.org/search?q=74.200.247.59&format=xml&addressdetails=1&limit=1 openstreetmap|::ffff:66.147.244.214|The OpenStreetMapProvider does not support IPv6 addresses. bing_maps|Paris, France|Could not execute query http://dev.virtualearth.net/REST/v1/Locations/?q=Paris%2C+France&key=<FAKE_API_KEY> bing_maps|Copenhagen, Denmark|Could not execute query http://dev.virtualearth.net/REST/v1/Locations/?q=Copenhagen%2C+Denmark&key=<FAKE_API_KEY> bing_maps|74.200.247.59|The BingMapsProvider does not support IP addresses. bing_maps|::ffff:66.147.244.214|The BingMapsProvider does not support IP addresses. yandex|Paris, France|POINT(2.341198 48.856929) yandex|Copenhagen, Denmark|POINT(12.567602 55.675682) yandex|74.200.247.59|The YandexProvider does not support IP addresses. yandex|::ffff:66.147.244.214|The YandexProvider does not support IP addresses. free_geo_ip|Paris, France|The FreeGeoIpProvider does not support Street addresses. free_geo_ip|Copenhagen, Denmark|The FreeGeoIpProvider does not support Street addresses. free_geo_ip|74.200.247.59|POINT(-122.415600 37.748400) free_geo_ip|::ffff:66.147.244.214|POINT(-111.613300 40.218100) geoip|Paris, France|The GeoipProvider does not support Street addresses. geoip|Copenhagen, Denmark|The GeoipProvider does not support Street addresses. geoip|74.200.247.59|POINT(-122.415604 37.748402) geoip|::ffff:66.147.244.214|The GeoipProvider does not support IPv6 addresses.
Batch reverse geocoding is something like:
// ... $geocoder like the previous example ... // If you want to reverse one coordinate try { $results = $geotools->batch($geocoder)->reverse( new \League\Geotools\Coordinate\Coordinate([2.307266, 48.823405]) )->parallel(); } catch (\Exception $e) { die($e->getMessage()); } // Or if you want to reverse geocoding 3 coordinates $coordinates = [ new \League\Geotools\Coordinate\Coordinate([2.307266, 48.823405]), new \League\Geotools\Coordinate\Coordinate([12.568337, 55.676097]), new \League\Geotools\Coordinate\Coordinate('-74.005973 40.714353')), ]; $results = $geotools->batch($geocoder)->reverse($coordinates)->parallel(); // ...
If you want to batch it in serie, replace the method
parallel()
byserie()
.To optimize batch requests you need to register providers according to their capabilities and what you're looking for (geocode street addresses, geocode IPv4, geocode IPv6 or reverse geocoding), please read more at the Geocoder library doc.
Distance
It provides methods to compute the distance in meter (by default), km, mi or ft between two coordinates using flat (most performant), great circle, haversine or vincenty (most accurate) algorithms.
Those coordinates should be in the same ellipsoid.
$geotools = new \League\Geotools\Geotools(); $coordA = new \League\Geotools\Coordinate\Coordinate([48.8234055, 2.3072664]); $coordB = new \League\Geotools\Coordinate\Coordinate([43.296482, 5.36978]); $distance = $geotools->distance()->setFrom($coordA)->setTo($coordB); printf("%s\n",$distance->flat()); // 659166.50038742 (meters) printf("%s\n",$distance->greatCircle()); // 659021.90812846 printf("%s\n",$distance->in('km')->haversine()); // 659.02190812846 printf("%s\n",$distance->in('mi')->vincenty()); // 409.05330679648 printf("%s\n",$distance->in('ft')->flat()); // 2162619.7519272
Point
It provides methods to compute the initial and final bearing in degrees, the initial and final cardinal direction, the middle point and the destination point. The middle and the destination points returns a
\League\Geotools\Coordinate\Coordinate
object with the same ellipsoid.$geotools = new \League\Geotools\Geotools(); $coordA = new \League\Geotools\Coordinate\Coordinate([48.8234055, 2.3072664]); $coordB = new \League\Geotools\Coordinate\Coordinate([43.296482, 5.36978]); $vertex = $geotools->vertex()->setFrom($coordA)->setTo($coordB); printf("%d\n", $vertex->initialBearing()); // 157 (degrees) printf("%s\n", $vertex->initialCardinal()); // SSE (SouthSouthEast) printf("%d\n", $vertex->finalBearing()); // 160 (degrees) printf("%s\n", $vertex->finalCardinal()); // SSE (SouthSouthEast) $middlePoint = $vertex->middle(); // \League\Geotools\Coordinate\Coordinate printf("%s\n", $middlePoint->getLatitude()); // 46.070143125815 printf("%s\n", $middlePoint->getLongitude()); // 3.9152401085931 $destinationPoint = $geotools->vertex()->setFrom($coordA)->destination(180, 200000); // \League\Geotools\Coordinate\Coordinate printf("%s\n", $destinationPoint->getLatitude()); // 47.026774650075 printf("%s\n", $destinationPoint->getLongitude()); // 2.3072664
Geohash
It provides methods to get the geo hash and its bounding box's coordinates (SouthWest & NorthEast) of a coordinate and the coordinate and its bounding box's coordinates (SouthWest & NorthEast) of a geo hash.
$geotools = new \League\Geotools\Geotools(); $coordToGeohash = new \League\Geotools\Coordinate\Coordinate('43.296482, 5.36978'); // encoding $encoded = $geotools->geohash()->encode($coordToGeohash, 4); // 12 is the default length / precision // encoded printf("%s\n", $encoded->getGeohash()); // spey // encoded bounding box $boundingBox = $encoded->getBoundingBox(); // array of \League\Geotools\Coordinate\CoordinateInterface $southWest = $boundingBox[0]; $northEast = $boundingBox[1]; printf("http://www.openstreetmap.org/?minlon=%s&minlat=%s&maxlon=%s&maxlat=%s&box=yes\n", $southWest->getLongitude(), $southWest->getLatitude(), $northEast->getLongitude(), $northEast->getLatitude() ); // http://www.openstreetmap.org/?minlon=5.2734375&minlat=43.2421875&maxlon=5.625&maxlat=43.41796875&box=yes // decoding $decoded = $geotools->geohash()->decode('spey61y'); // decoded coordinate printf("%s\n", $decoded->getCoordinate()->getLatitude()); // 43.296432495117 printf("%s\n", $decoded->getCoordinate()->getLongitude()); // 5.3702545166016 // decoded bounding box $boundingBox = $decoded->getBoundingBox(); //array of \League\Geotools\Coordinate\CoordinateInterface $southWest = $boundingBox[0]; $northEast = $boundingBox[1]; printf("http://www.openstreetmap.org/?minlon=%s&minlat=%s&maxlon=%s&maxlat=%s&box=yes\n", $southWest->getLongitude(), $southWest->getLatitude(), $northEast->getLongitude(), $northEast->getLatitude() ); // http://www.openstreetmap.org/?minlon=5.3695678710938&minlat=43.295745849609&maxlon=5.3709411621094&maxlat=43.297119140625&box=yes
You can also get information about neighbor points (image).
$geotools = new \League\Geotools\Geotools(); // decoding $decoded = $geotools->geohash()->decode('spey61y'); // get neighbor geohash printf("%s\n", $decoded->getNeighbor(\League\Geotools\Geohash\Geohash::DIRECTION_NORTH)); // spey64n printf("%s\n", $decoded->getNeighbor(\League\Geotools\Geohash\Geohash::DIRECTION_SOUTH_EAST)); // spey61x // get all neighbor geohashes print_r($decoded->getNeighbors(true)); /** * Array * ( * [north] => spey64n * [south] => spey61w * [west] => spey61v * [east] => spey61z * [north_west] => spey64j * [north_east] => spey64p * [south_west] => spey61t * [south_east] => spey61x * ) */
10:10
Represent a location with 10m accuracy using a 10 character code that includes features to prevent errors in entering the code. Read more about the algorithm here.
$tenten = new \League\Geotools\Tests\Geohash\TenTen; $tenten->encode(new Coordinate([51.09559, 1.12207])); // MEQ N6G 7NY5
Vertex
Represents a segment with a direction. You can find if two vertexes are on the same line.
$vertexA->setFrom(48.8234055); $vertexA->setTo(2.3072664); $vertexB->setFrom(48.8234055); $vertexB->setTo(2.3072664); $vertexA->isOnSameLine($vertexB);
Polygon
It helps you to know if a point (coordinate) is in a Polygon or on the Polygon's boundaries and if this in on a Polygon's vertex.
First you need to create the polygon, you can provide:
- an array of arrays
- an array of
Coordinate
- a
CoordinateCollection
$polygon = new \League\Geotools\Polygon\Polygon([ [48.9675969, 1.7440796], [48.4711003, 2.5268555], [48.9279131, 3.1448364], [49.3895245, 2.6119995], ]); $polygon->setPrecision(5); // set the comparision precision $polygon->pointInPolygon(new \League\Geotools\Coordinate\Coordinate([49.1785607, 2.4444580])); // true $polygon->pointInPolygon(new \League\Geotools\Coordinate\Coordinate([49.1785607, 5])); // false $polygon->pointOnBoundary(new \League\Geotools\Coordinate\Coordinate([48.7193486, 2.13546755])); // true $polygon->pointOnBoundary(new \League\Geotools\Coordinate\Coordinate([47.1587188, 2.87841795])); // false $polygon->pointOnVertex(new \League\Geotools\Coordinate\Coordinate([48.4711003, 2.5268555])); // true $polygon->pointOnVertex(new \League\Geotools\Coordinate\Coordinate([49.1785607, 2.4444580])); // false $polygon->getBoundingBox(); // return the BoundingBox object
CLI
It provides command lines to compute methods provided by Distance, Point, Geohash and Convert classes. Thanks to the Symfony Console Component.
$ php geotools list // list of available commands $ php geotools help distance:flat // get the help $ php geotools distance:flat "40° 26.7717, -79° 56.93172" "30°16′57″N 029°48′32″W" // 4690203.1048522 $ php geotools distance:haversine "35,45" "45,35" --ft // 4593030.9787593 $ php geotools distance:vincenty "35,45" "45,35" --km // 1398.4080717661 $ php geotools d:v "35,45" "45,35" --km --ellipsoid=WGS60 // 1398.4145201642 $ php geotools point:initial-cardinal "40:26:46.302N 079:56:55.903W" "43.296482, 5.36978" // NE (NordEast) $ php geotools point:final-cardinal "40:26:46.302N 079:56:55.903W" "43.296482, 5.36978" // ESE (EastSouthEast) $ php geotools point:destination "40° 26.7717, -79° 56.93172" 25 10000 // 40.527599285543, -79.898914904538 $ php geotools p:d "40° 26.7717, -79° 56.93172" 25 10000 --ellipsoid=GRS_1980 // 40.527599272782, -79.898914912379 $ php geotools geohash:encode "40° 26.7717, -79° 56.93172" --length=3 // dpp $ php geotools convert:dm "40.446195, -79.948862" --format="%P%D°%N %p%d°%n" // 40°26.7717 -79°56.93172 $ php geotools convert:dms "40.446195, -79.948862" --format="%P%D:%M:%S, %p%d:%m:%s" // 40:26:46, -79:56:56 $ php geotools convert:utm "60.3912628, 5.3220544" // 32V 297351 6700644 $ php geotools c:u "60.3912628, 5.3220544" --ellipsoid=AIRY // 32V 297371 6700131 ...
Compute street addresses, IPv4s or IPv6s geocoding and reverse geocoding right in your console.
It's possible to define and precise your request through these options:
-
--provider
:bing_maps
,yahoo
,maxmind
...google_maps
is the default one. See the full list here. -
--raw
: the result output in RAW format, shows Adapter, Provider and Arguments if any. -
--json
: the result output in JSON string format. -
--args
: this option accepts multiple values (e.g. --args="API_KEY" --args="LOCALE") if your provider needs or can have arguments. -
--dumper
: this option is available for geocoding,gpx
,geojson
,kml
,wkb
andwkt
by default. Read more here. -
--format
: this option is available for reverse geocoding, see the mapping here.
$ php geotools help geocoder:geocode // get the help $ php geotools geocoder:geocode "Copenhagen, Denmark" // 55.6760968, 12.5683371 $ php geotools geocoder:geocode "74.200.247.59" --provider="free_geo_ip" // 37.7484, -122.4156 $ php geotools geocoder:geocode Paris --args="fr_FR" --args="France" --args="true" // 48.856614, 2.3522219 $ php geotools geocoder:geocode Paris --dumper=wkt // POINT(2.352222 48.856614) ... $ php geotools geocoder:reverse "48.8631507, 2.388911" // Avenue Gambetta 10, 75020 Paris $ php geotools geocoder:reverse "48.8631507, 2.388911" --format="%L, %A1, %C" // Paris, Île-De-France, France $ php geotools geocoder:reverse "48.8631507, 2.388911" --format="%L, %A1, %C" --provider="openstreetmap" // Paris, Île-De-France, France Métropolitaine ... $ php geotools geocoder:geocode "Tagensvej 47, Copenhagen" --raw --args=da_DK --args=Denmark
The last command will show an output like this:
HttpClient: \Http\Client\Curl\Client Provider: \Geocoder\Provider\GoogleMaps Cache: \League\Geotools\Cache\Redis Arguments: da_DK,Denmark --- Latitude: 55.699953 Longitude: 12.552736 Bounds - South: 55.699953 - West: 12.552736 - North: 55.699953 - East: 12.552736 Street Number: 47 Street Name: Tagensvej Zipcode: 2200 City: Copenhagen City District: København N County: København County Code: KØBENHAVN Region: Capital Region Of Denmark Region Code: CAPITAL REGION OF DENMARK Country: Denmark Country Code: DK Timezone:
Integration with Frameworks
- Laravel 4 & 5
- Silex
- ...
Unit Tests
To run unit tests, you'll need the
cURL
extension and a set of dependencies, you can install them using Composer:$ php composer.phar install --dev
Once installed, just launch the following command:
$ phpunit --coverage-text
Credits
Acknowledgments
- Geocoder - MIT
- ReactPHP - MIT
- Symfony Console Component - MIT
- Symfony Serializer Component - MIT
- PHP client library for Redis - MIT
- Geokit, Geotools-for-CodeIgniter, geotools-php ...
Changelog
Contributing
Please see CONTRIBUTING for details.
Support
Bugs and feature request are tracked on GitHub
Contributor Code of Conduct
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the Contributor Covenant, version 1.0.0, available at https://contributor-covenant.org/version/1/0/0/
License
Geotools is released under the MIT License. See the bundled LICENSE file for details.
- League\Geotools\Tests Readme
Geotools
Geotools is a PHP geo-related library, built atop Geocoder and React libraries.
Features
- Batch geocode & reverse geocoding request(s) in series / in parallel against one or a set of providers. »
- Cache geocode & reverse geocoding result(s) with PSR-6 to improve performances. »
- Compute geocode & reverse geocoding in the command-line interface (CLI) + dumpers and formatters. »
- Accept almost all kind of WGS84 geographic coordinates as coordinates. »
- Support 23 different ellipsoids and it's easy to provide a new one if needed. »
- Convert and format decimal degrees coordinates to decimal minutes or degrees minutes seconds coordinates. »
- Convert decimal degrees coordinates in the Universal Transverse Mercator (UTM) projection. »
- Compute the distance in meter (by default), km, mi or ft between two coordinates using flat, great circle, haversine or vincenty algorithms. »
- Compute the initial and final bearing from the origin coordinate to the destination coordinate in degrees. »
- Compute the initial and final cardinal point (direction) from the origin coordinate to the destination coordinate, read more in wikipedia. »
- Compute the half-way point (coordinate) between the origin and the destination coordinates. »
- Compute the destination point (coordinate) with given bearing in degrees and a distance in meters. »
- Encode a coordinate to a geo hash string and decode it to a coordinate, read more in wikipedia and on geohash.org. »
- Encode a coordinate via the 10:10 algorithm. »
- Polygon class provides methods to check either a poing (coordinate) is in, or on the polygon's boundaries. »
- A command-line interface (CLI) for Distance, Point, Geohash and Convert classes. »
- Integration with Frameworks: Laravel 4, Silex ... »
- ... more to come ...
Installation
Geotools can be found on Packagist. The recommended way to install Geotools is through composer.
Run the following on the command line:
php composer require league/geotools=@stable
Protip: you should browse the
league/geotools
page to choose a stable version to use, avoid the@stable
meta constraint.Important: you should use the
0.4
version if you use Geocoder2.x
or/and PHP5.3
.And install dependencies:
$ curl -sS https://getcomposer.org/installer | php $ php composer.phar install
Now you can add the autoloader, and you will have access to the library:
require 'vendor/autoload.php';
Usage & API
Coordinate & Ellipsoid
The default geodetic datum is WGS84 and coordinates are in decimal degrees.
Here are the available ellipsoids:
AIRY
,AUSTRALIAN_NATIONAL
,BESSEL_1841
,BESSEL_1841_NAMBIA
,CLARKE_1866
,CLARKE_1880
,EVEREST
,FISCHER_1960_MERCURY
,FISCHER_1968
,GRS_1967
,GRS_1980
,HELMERT_1906
,HOUGH
,INTERNATIONAL
,KRASSOVSKY
,MODIFIED_AIRY
,MODIFIED_EVEREST
,MODIFIED_FISCHER_1960
,SOUTH_AMERICAN_1969
,WGS60
,WGS66
,WGS72
, andWGS84
.If you need to use an other ellipsoid, just create an array like this:
$myEllipsoid = \League\Geotools\Coordinate\Ellipsoid::createFromArray([ 'name' => 'My Ellipsoid', // The name of the Ellipsoid 'a' => 123.0, // The semi-major axis (equatorial radius) in meters 'invF' => 456.0 // The inverse flattening ]);
Geotools is built atop Geocoder. It means it's possible to use the
\Geocoder\Model\Address
directly but it's also possible to use a string or a simple array with its latitude and longitude.It supports valid and acceptable geographic coordinates like:
- 40:26:46N,079:56:55W
- 40:26:46.302N 079:56:55.903W
- 40°26′47″N 079°58′36″W
- 40d 26′ 47″ N 079d 58′ 36″ W
- 40.446195N 79.948862W
- 40.446195, -79.948862
- 40° 26.7717, -79° 56.93172
Latitudes below -90.0 or above 90.0 degrees are capped through
\League\Geotools\Coordinate\Coordinate::normalizeLatitude()
. Longitudes below -180.0 or above 180.0 degrees are wrapped through\League\Geotools\Coordinate\Coordinate::normalizeLongitude()
.use League\Geotools\Coordinate\Coordinate; use League\Geotools\Coordinate\Ellipsoid; // from an \Geocoder\Model\Address instance within Airy ellipsoid $coordinate = new Coordinate($geocoderResult, Ellipsoid::createFromName(Ellipsoid::AIRY)); // or in an array of latitude/longitude coordinate within GRS 1980 ellipsoid $coordinate = new Coordinate([48.8234055, 2.3072664], Ellipsoid::createFromName(Ellipsoid::GRS_1980)); // or in latitude/longitude coordinate within WGS84 ellipsoid $coordinate = new Coordinate('48.8234055, 2.3072664'); // or in degrees minutes seconds coordinate within WGS84 ellipsoid $coordinate = new Coordinate('48°49′24″N, 2°18′26″E'); // or in decimal minutes coordinate within WGS84 ellipsoid $coordinate = new Coordinate('48 49.4N, 2 18.43333E'); // the result will be: printf("Latitude: %F\n", $coordinate->getLatitude()); // 48.8234055 printf("Longitude: %F\n", $coordinate->getLongitude()); // 2.3072664 printf("Ellipsoid name: %s\n", $coordinate->getEllipsoid()->getName()); // WGS 84 printf("Equatorial radius: %F\n", $coordinate->getEllipsoid()->getA()); // 6378136.0 printf("Polar distance: %F\n", $coordinate->getEllipsoid()->getB()); // 6356751.317598 printf("Inverse flattening: %F\n", $coordinate->getEllipsoid()->getInvF()); // 298.257224 printf("Mean radius: %F\n", $coordinate->getEllipsoid()->getArithmeticMeanRadius()); // 6371007.772533 // it's also possible to modify the coordinate without creating an other coodinate $coordinate->setFromString('40°26′47″N 079°58′36″W'); printf("Latitude: %F\n", $coordinate->getLatitude()); // 40.446388888889 printf("Longitude: %F\n", $coordinate->getLongitude()); // -79.976666666667
Convert
It provides methods (and aliases) to convert decimal degrees WGS84 coordinates to degrees minutes seconds or decimal minutes WGS84 coordinates. You can format the output string easily.
You can also convert them in the Universal Transverse Mercator (UTM) projection (Southwest coast of Norway and the region of Svalbard are covered).
$geotools = new \League\Geotools\Geotools(); $coordinate = new \League\Geotools\Coordinate\Coordinate('40.446195, -79.948862'); $converted = $geotools->convert($coordinate); // convert to decimal degrees without and with format string printf("%s\n", $converted->toDecimalMinutes()); // 40 26.7717N, -79 56.93172W // convert to degrees minutes seconds without and with format string printf("%s\n", $converted->toDegreesMinutesSeconds('<p>%P%D:%M:%S, %p%d:%m:%s</p>')); // <p>40:26:46, -79:56:56</p> // convert in the UTM projection (standard format) printf("%s\n", $converted->toUniversalTransverseMercator()); // 17T 589138 4477813
Here is the mapping:
Decimal minutes Latitude Longitude Positive or negative sign %P
%p
Direction %L
%l
Degrees %D
%d
Decimal minutes %N
%n
Degrees minutes seconds Latitude Longitude Positive or negative sign %P
%p
Direction %L
%l
Degrees %D
%d
Minutes %M
%m
Seconds %S
%s
Batch
It provides a very handy way to batch geocode and reverse geocoding requests in serie or in parallel against a set of providers. Thanks to Geocoder and React libraries.
It's possible to batch one request (a string) or a set of request (an array) against one provider or set of providers.
You can use a provided cache engine or use your own by setting a cache object which should implement
League\Geotools\Cache\CacheInterface
and extendLeague\Geotools\Cache\AbstractCache
if needed.At the moment Geotools supports any PSR-6 cache.
NB: Before you implement caching in your app please be sure that doing so does not violate the Terms of Service for your(s) geocoding provider(s).
$geocoder = new \Geocoder\ProviderAggregator(); // or \Geocoder\TimedGeocoder $httpClient = HttpClientDiscovery::find(); $geocoder->registerProviders([ new \Geocoder\Provider\GoogleMaps\GoogleMaps($httpClient), new \Geocoder\Provider\OpenStreetMap\OpenStreetMap($httpClient), new \Geocoder\Provider\BingMaps\BingMaps($httpClient, '<FAKE_API_KEY>'), // throws InvalidCredentialsException new \Geocoder\Provider\Yandex\Yandex($httpClient), new \Geocoder\Provider\FreeGeoIp\FreeGeoIp($httpClient), new \Geocoder\Provider\Geoip\Geoip(), ]); try { $geotools = new \League\Geotools\Geotools(); $cache = new \Cache\Adapter\PHPArray\ArrayCachePool(); $results = $geotools->batch($geocoder)->setCache($cache)->geocode([ 'Paris, France', 'Copenhagen, Denmark', '74.200.247.59', '::ffff:66.147.244.214' ])->parallel(); } catch (\Exception $e) { die($e->getMessage()); } $dumper = new \Geocoder\Dumper\WktDumper(); foreach ($results as $result) { // if a provider throws an exception (UnsupportedException, InvalidCredentialsException ...) // an custom /Geocoder/Result/Geocoded instance is returned which embedded the name of the provider, // the query string and the exception string. It's possible to use dumpers // and/or formatters from the Geocoder library. printf("%s|%s|%s\n", $result->getProviderName(), $result->getQuery(), '' == $result->getExceptionMessage() ? $dumper->dump($result) : $result->getExceptionMessage() ); }
You should get 24 results (4 values to geocode against 6 providers) something like:
google_maps|Paris, France|POINT(2.352222 48.856614) google_maps|Copenhagen, Denmark|POINT(12.568337 55.676097) google_maps|74.200.247.59|The GoogleMapsProvider does not support IP addresses. google_maps|::ffff:66.147.244.214|The GoogleMapsProvider does not support IP addresses. openstreetmaps|Paris, France|POINT(2.352133 48.856506) openstreetmaps|Copenhagen, Denmark|POINT(12.570072 55.686724) openstreetmaps|74.200.247.59|Could not execute query http://nominatim.openstreetmap.org/search?q=74.200.247.59&format=xml&addressdetails=1&limit=1 openstreetmaps|::ffff:66.147.244.214|The OpenStreetMapProvider does not support IPv6 addresses. bing_maps|Paris, France|Could not execute query http://dev.virtualearth.net/REST/v1/Locations/?q=Paris%2C+France&key=<FAKE_API_KEY> bing_maps|Copenhagen, Denmark|Could not execute query http://dev.virtualearth.net/REST/v1/Locations/?q=Copenhagen%2C+Denmark&key=<FAKE_API_KEY> bing_maps|74.200.247.59|The BingMapsProvider does not support IP addresses. bing_maps|::ffff:66.147.244.214|The BingMapsProvider does not support IP addresses. yandex|Paris, France|POINT(2.341198 48.856929) yandex|Copenhagen, Denmark|POINT(12.567602 55.675682) yandex|74.200.247.59|The YandexProvider does not support IP addresses. yandex|::ffff:66.147.244.214|The YandexProvider does not support IP addresses. free_geo_ip|Paris, France|The FreeGeoIpProvider does not support Street addresses. free_geo_ip|Copenhagen, Denmark|The FreeGeoIpProvider does not support Street addresses. free_geo_ip|74.200.247.59|POINT(-122.415600 37.748400) free_geo_ip|::ffff:66.147.244.214|POINT(-111.613300 40.218100) geoip|Paris, France|The GeoipProvider does not support Street addresses. geoip|Copenhagen, Denmark|The GeoipProvider does not support Street addresses. geoip|74.200.247.59|POINT(-122.415604 37.748402) geoip|::ffff:66.147.244.214|The GeoipProvider does not support IPv6 addresses.
Batch reverse geocoding is something like:
// ... $geocoder like the previous example ... // If you want to reverse one coordinate try { $results = $geotools->batch($geocoder)->reverse( new \League\Geotools\Coordinate\Coordinate([2.307266, 48.823405]) )->parallel(); } catch (\Exception $e) { die($e->getMessage()); } // Or if you want to reverse geocoding 3 coordinates $coordinates = [ new \League\Geotools\Coordinate\Coordinate([2.307266, 48.823405]), new \League\Geotools\Coordinate\Coordinate([12.568337, 55.676097]), new \League\Geotools\Coordinate\Coordinate('-74.005973 40.714353')), ]; $results = $geotools->batch($geocoder)->reverse($coordinates)->parallel(); // ...
If you want to batch it in serie, replace the method
parallel()
byserie()
.To optimize batch requests you need to register providers according to their capabilities and what you're looking for (geocode street addresses, geocode IPv4, geocode IPv6 or reverse geocoding), please read more at the Geocoder library doc.
Distance
It provides methods to compute the distance in meter (by default), km, mi or ft between two coordinates using flat (most performant), great circle, haversine or vincenty (most accurate) algorithms.
Those coordinates should be in the same ellipsoid.
$geotools = new \League\Geotools\Geotools(); $coordA = new \League\Geotools\Coordinate\Coordinate([48.8234055, 2.3072664]); $coordB = new \League\Geotools\Coordinate\Coordinate([43.296482, 5.36978]); $distance = $geotools->distance()->setFrom($coordA)->setTo($coordB); printf("%s\n",$distance->flat()); // 659166.50038742 (meters) printf("%s\n",$distance->greatCircle()); // 659021.90812846 printf("%s\n",$distance->in('km')->haversine()); // 659.02190812846 printf("%s\n",$distance->in('mi')->vincenty()); // 409.05330679648 printf("%s\n",$distance->in('ft')->flat()); // 2162619.7519272
Point
It provides methods to compute the initial and final bearing in degrees, the initial and final cardinal direction, the middle point and the destination point. The middle and the destination points returns a
\League\Geotools\Coordinate\Coordinate
object with the same ellipsoid.$geotools = new \League\Geotools\Geotools(); $coordA = new \League\Geotools\Coordinate\Coordinate([48.8234055, 2.3072664]); $coordB = new \League\Geotools\Coordinate\Coordinate([43.296482, 5.36978]); $vertex = $geotools->vertex()->setFrom($coordA)->setTo($coordB); printf("%d\n", $vertex->initialBearing()); // 157 (degrees) printf("%s\n", $vertex->initialCardinal()); // SSE (SouthSouthEast) printf("%d\n", $vertex->finalBearing()); // 160 (degrees) printf("%s\n", $vertex->finalCardinal()); // SSE (SouthSouthEast) $middlePoint = $vertex->middle(); // \League\Geotools\Coordinate\Coordinate printf("%s\n", $middlePoint->getLatitude()); // 46.070143125815 printf("%s\n", $middlePoint->getLongitude()); // 3.9152401085931 $destinationPoint = $geotools->vertex()->setFrom($coordA)->destination(180, 200000); // \League\Geotools\Coordinate\Coordinate printf("%s\n", $destinationPoint->getLatitude()); // 47.026774650075 printf("%s\n", $destinationPoint->getLongitude()); // 2.3072664
Geohash
It provides methods to get the geo hash and its bounding box's coordinates (SouthWest & NorthEast) of a coordinate and the coordinate and its bounding box's coordinates (SouthWest & NorthEast) of a geo hash.
$geotools = new \League\Geotools\Geotools(); $coordToGeohash = new \League\Geotools\Coordinate\Coordinate('43.296482, 5.36978'); // encoding $encoded = $geotools->geohash()->encode($coordToGeohash, 4); // 12 is the default length / precision // encoded printf("%s\n", $encoded->getGeohash()); // spey // encoded bounding box $boundingBox = $encoded->getBoundingBox(); // array of \League\Geotools\Coordinate\CoordinateInterface $southWest = $boundingBox[0]; $northEast = $boundingBox[1]; printf("http://www.openstreetmap.org/?minlon=%s&minlat=%s&maxlon=%s&maxlat=%s&box=yes\n", $southWest->getLongitude(), $southWest->getLatitude(), $northEast->getLongitude(), $northEast->getLatitude() ); // http://www.openstreetmap.org/?minlon=5.2734375&minlat=43.2421875&maxlon=5.625&maxlat=43.41796875&box=yes // decoding $decoded = $geotools->geohash()->decode('spey61y'); // decoded coordinate printf("%s\n", $decoded->getCoordinate()->getLatitude()); // 43.296432495117 printf("%s\n", $decoded->getCoordinate()->getLongitude()); // 5.3702545166016 // decoded bounding box $boundingBox = $decoded->getBoundingBox(); //array of \League\Geotools\Coordinate\CoordinateInterface $southWest = $boundingBox[0]; $northEast = $boundingBox[1]; printf("http://www.openstreetmap.org/?minlon=%s&minlat=%s&maxlon=%s&maxlat=%s&box=yes\n", $southWest->getLongitude(), $southWest->getLatitude(), $northEast->getLongitude(), $northEast->getLatitude() ); // http://www.openstreetmap.org/?minlon=5.3695678710938&minlat=43.295745849609&maxlon=5.3709411621094&maxlat=43.297119140625&box=yes
10:10
Represent a location with 10m accuracy using a 10 character code that includes features to prevent errors in entering the code. Read more about the algorithm here.
$tenten = new \League\Geotools\Tests\Geohash\TenTen; $tenten->encode(new Coordinate([51.09559, 1.12207])); // MEQ N6G 7NY5
Vertex
Represents a segment with a direction. You can find if two vertexes are on the same line.
$vertexA->setFrom(48.8234055); $vertexA->setTo(2.3072664); $vertexB->setFrom(48.8234055); $vertexB->setTo(2.3072664); $vertexA->isOnSameLine($vertexB);
Polygon
It helps you to know if a point (coordinate) is in a Polygon or on the Polygon's boundaries and if this in on a Polygon's vertex.
First you need to create the polygon, you can provide:
- an array of arrays
- an array of
Coordinate
- a
CoordinateCollection
$polygon = new \League\Geotools\Polygon\Polygon([ [48.9675969, 1.7440796], [48.4711003, 2.5268555], [48.9279131, 3.1448364], [49.3895245, 2.6119995], ]); $polygon->setPrecision(5); // set the comparision precision $polygon->pointInPolygon(new \League\Geotools\Coordinate\Coordinate([49.1785607, 2.4444580])); // true $polygon->pointInPolygon(new \League\Geotools\Coordinate\Coordinate([49.1785607, 5])); // false $polygon->pointOnBoundary(new \League\Geotools\Coordinate\Coordinate([48.7193486, 2.13546755])); // true $polygon->pointOnBoundary(new \League\Geotools\Coordinate\Coordinate([47.1587188, 2.87841795])); // false $polygon->pointOnVertex(new \League\Geotools\Coordinate\Coordinate([48.4711003, 2.5268555])); // true $polygon->pointOnVertex(new \League\Geotools\Coordinate\Coordinate([49.1785607, 2.4444580])); // false $polygon->getBoundingBox(); // return the BoundingBox object
CLI
It provides command lines to compute methods provided by Distance, Point, Geohash and Convert classes. Thanks to the Symfony Console Component.
$ php geotools list // list of available commands $ php geotools help distance:flat // get the help $ php geotools distance:flat "40° 26.7717, -79° 56.93172" "30°16′57″N 029°48′32″W" // 4690203.1048522 $ php geotools distance:haversine "35,45" "45,35" --ft // 4593030.9787593 $ php geotools distance:vincenty "35,45" "45,35" --km // 1398.4080717661 $ php geotools d:v "35,45" "45,35" --km --ellipsoid=WGS60 // 1398.4145201642 $ php geotools point:initial-cardinal "40:26:46.302N 079:56:55.903W" "43.296482, 5.36978" // NE (NordEast) $ php geotools point:final-cardinal "40:26:46.302N 079:56:55.903W" "43.296482, 5.36978" // ESE (EastSouthEast) $ php geotools point:destination "40° 26.7717, -79° 56.93172" 25 10000 // 40.527599285543, -79.898914904538 $ php geotools p:d "40° 26.7717, -79° 56.93172" 25 10000 --ellipsoid=GRS_1980 // 40.527599272782, -79.898914912379 $ php geotools geohash:encode "40° 26.7717, -79° 56.93172" --length=3 // dpp $ php geotools convert:dm "40.446195, -79.948862" --format="%P%D°%N %p%d°%n" // 40°26.7717 -79°56.93172 $ php geotools convert:dms "40.446195, -79.948862" --format="%P%D:%M:%S, %p%d:%m:%s" // 40:26:46, -79:56:56 $ php geotools convert:utm "60.3912628, 5.3220544" // 32V 297351 6700644 $ php geotools c:u "60.3912628, 5.3220544" --ellipsoid=AIRY // 32V 297371 6700131 ...
Compute street addresses, IPv4s or IPv6s geocoding and reverse geocoding right in your console.
It's possible to define and precise your request through these options:
-
--provider
:bing_maps
,yahoo
,maxmind
...google_maps
is the default one. See the full list here. -
--raw
: the result output in RAW format, shows Adapter, Provider and Arguments if any. -
--json
: the result output in JSON string format. -
--args
: this option accepts multiple values (e.g. --args="API_KEY" --args="LOCALE") if your provider needs or can have arguments. -
--dumper
: this option is available for geocoding,gpx
,geojson
,kml
,wkb
andwkt
by default. Read more here. -
--format
: this option is available for reverse geocoding, see the mapping here.
$ php geotools help geocoder:geocode // get the help $ php geotools geocoder:geocode "Copenhagen, Denmark" // 55.6760968, 12.5683371 $ php geotools geocoder:geocode "74.200.247.59" --provider="free_geo_ip" // 37.7484, -122.4156 $ php geotools geocoder:geocode Paris --args="fr_FR" --args="France" --args="true" // 48.856614, 2.3522219 $ php geotools geocoder:geocode Paris --dumper=wkt // POINT(2.352222 48.856614) ... $ php geotools geocoder:reverse "48.8631507, 2.388911" // Avenue Gambetta 10, 75020 Paris $ php geotools geocoder:reverse "48.8631507, 2.388911" --format="%L, %A1, %C" // Paris, Île-De-France, France $ php geotools geocoder:reverse "48.8631507, 2.388911" --format="%L, %A1, %C" --provider="openstreetmaps" // Paris, Île-De-France, France Métropolitaine ... $ php geotools geocoder:geocode "Tagensvej 47, Copenhagen" --raw --args=da_DK --args=Denmark
The last command will show an output like this:
HttpClient: \Http\Client\Curl\Client Provider: \Geocoder\Provider\GoogleMaps Cache: \League\Geotools\Cache\Redis Arguments: da_DK,Denmark --- Latitude: 55.699953 Longitude: 12.552736 Bounds - South: 55.699953 - West: 12.552736 - North: 55.699953 - East: 12.552736 Street Number: 47 Street Name: Tagensvej Zipcode: 2200 City: Copenhagen City District: København N County: København County Code: KØBENHAVN Region: Capital Region Of Denmark Region Code: CAPITAL REGION OF DENMARK Country: Denmark Country Code: DK Timezone:
Integration with Frameworks
- Laravel 4 & 5
- Silex
- ...
Unit Tests
To run unit tests, you'll need the
cURL
extension and a set of dependencies, you can install them using Composer:$ php composer.phar install --dev
Once installed, just launch the following command:
$ phpunit --coverage-text
Credits
Acknowledgments
- Geocoder - MIT
- ReactPHP - MIT
- Symfony Console Component - MIT
- Symfony Serializer Component - MIT
- PHP client library for Redis - MIT
- Geokit, Geotools-for-CodeIgniter, geotools-php ...
Changelog
Contributing
Please see CONTRIBUTING for details.
Support
Bugs and feature request are tracked on GitHub
Contributor Code of Conduct
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the Contributor Covenant, version 1.0.0, available at https://contributor-covenant.org/version/1/0/0/
License
Geotools is released under the MIT License. See the bundled LICENSE file for details.
- Maknz\Slack Readme
Slack for PHP
A simple PHP package for sending messages to Slack with incoming webhooks, focused on ease-of-use and elegant syntax.
supports: PHP
7.1
,7.2
,7.3
,7.4
or8.0
require:guzzlehttp/guzzle
any of versions~7.0|~6.0|~5.0|~4.0
This is the fork of popular, great, but abandoned package
maknz/slack
Quick Tour
-
create an incoming webhook & copy
hook_url
-
composer require alek13/slack
-
add the following code
use Maknz\Slack\Client; require(__DIR__ .'/vendor/autoload.php'); $client = new Client('https://hook_url'); $client->to('#general')->send('Good morning');
Done!
Installation
You can install the package using the Composer package manager by running in your project root:
composer require alek13/slack
Incoming WebHook
Then create an incoming webhook on your Slack account for the package to use. You'll need the webhook URL to instantiate the client (or for the configuration file if using Laravel).
Basic Usage
Instantiate the client
// Instantiate without defaults $client = new Maknz\Slack\Client('https://hooks.slack.com/...'); // Instantiate with defaults, so all messages created // will be sent from 'Cyril' and to the #accounting channel // by default. Any names like @regan or #channel will also be linked. // use response_type (in_channel | ephemeral) to denote whether the message will be visible // to others in the channel. $settings = [ 'username' => 'Cyril', 'channel' => '#accounting', 'reponse_type' => 'in_channel', 'link_names' => true ]; $client = new Maknz\Slack\Client('https://hooks.slack.com/...', $settings);
Settings
The default settings are pretty good, but you may wish to set up default behaviour for your client to be used for all messages sent. All settings are optional and you don't need to provide any. Where not provided, we'll fallback to what is configured on the webhook integration, which are managed at Slack, or our sensible defaults.
Field Type Description channel
string The default channel that messages will be sent to username
string The default username for your bot icon
string The default icon that messages will be sent with, either :emoji:
or a URL to an imageresponse_type
string Whether to show the response in the channel to all members or privately ('ephemeral' link_names
bool Whether names like @regan
or#accounting
should be linked in the message (defaults to false)unfurl_links
bool Whether Slack should unfurl text-based URLs (defaults to false) unfurl_media
bool Whether Slack should unfurl media-based URLs, like tweets or Youtube videos (defaults to true) allow_markdown
bool Whether markdown should be parsed in messages, or left as plain text (defaults to true) markdown_in_attachments
array Which attachment fields should have markdown parsed (defaults to none) Sending messages
Sending a basic message (preview)
$client->send('Hello world!');
Sending a message to a non-default channel
$client->to('#accounting')->send('Are we rich yet?');
Sending a message to a user
$client->to('@regan')->send('Yo!');
Sending a message to a channel as a different bot name (preview)
$client->from('Jake the Dog')->to('@FinnTheHuman')->send('Adventure time!');
Sending a message with a different icon (preview)
// Either with a Slack emoji $client->to('@regan')->withIcon(':ghost:')->send('Boo!'); // or a URL $client->to('#accounting')->withIcon('http://example.com/accounting.png')->send('Some accounting notification');
Send an attachment (preview)
$client->to('#operations')->attach([ 'fallback' => 'Server health: good', 'text' => 'Server health: good', 'color' => 'danger', ])->send('New alert from the monitoring system'); // no message, but can be provided if you'd like
Send an attachment with fields (preview)
$client->to('#operations')->attach([ 'fallback' => 'Current server stats', 'text' => 'Current server stats', 'color' => 'danger', 'fields' => [ [ 'title' => 'CPU usage', 'value' => '90%', 'short' => true // whether the field is short enough to sit side-by-side other fields, defaults to false ], [ 'title' => 'RAM usage', 'value' => '2.5GB of 4GB', 'short' => true ] ] ])->send('New alert from the monitoring system'); // no message, but can be provided if you'd like
Send an attachment with an author (preview)
$client->to('@regan')->attach([ 'fallback' => 'Keep up the great work! I really love how the app works.', 'text' => 'Keep up the great work! I really love how the app works.', 'author_name' => 'Jane Appleseed', 'author_link' => 'https://yourapp.com/feedback/5874601', 'author_icon' => 'https://static.pexels.com/photos/61120/pexels-photo-61120-large.jpeg' ])->send('New user feedback');
Using blocks (Block Kit)
$client->to('@regan') ->withBlock([ 'type' => 'section', 'text' => 'Do you love the app?' ]) ->withBlock([ 'type' => 'actions', 'elements' => [[ 'type' => 'button', 'text' => 'Love it', 'style' => 'primary', 'action_id' => 'love', ], [ 'type' => 'button', 'text' => 'Hate it', 'style' => 'danger', 'action_id' => 'hate', ],] ]) ->send('Notification fallback message');
Advanced usage
Markdown
By default, Markdown is enabled for message text, but disabled for attachment fields. This behaviour can be configured in settings, or on the fly:
Send a message enabling or disabling Markdown
$client->to('#weird')->disableMarkdown()->send('Disable *markdown* just for this message'); $client->to('#general')->enableMarkdown()->send('Enable _markdown_ just for this message');
Send an attachment specifying which fields should have Markdown enabled
$client->to('#operations')->attach([ 'fallback' => 'It is all broken, man', 'text' => 'It is _all_ broken, man', 'pretext' => 'From user: *JimBob*', 'color' => 'danger', 'mrkdwn_in' => ['pretext', 'text'] ])->send('New alert from the monitoring system');
Explicit message creation
For convenience, message objects are created implicitly by calling message methods on the client. We can however do this explicitly to avoid hitting the magic method.
// Implicitly $client->to('@regan')->send('I am sending this implicitly'); // Explicitly $message = $client->createMessage(); $message ->to('@regan') ->setText('I am sending this explicitly') ; $client->send($message);
Attachments
When using attachments, the easiest way is to provide an array of data as shown in the examples, which is actually converted to an Attachment object under the hood. You can also attach an Attachment object to the message:
$attachment = new Attachment([ 'fallback' => 'Some fallback text', 'text' => 'The attachment text' ]); // Explicitly create a message from the client // rather than using the magic passthrough methods $message = $client->createMessage(); $message->attach($attachment); // Explicitly set the message text rather than // implicitly through the send method $message->setText('Hello world'); $client->send($message);
Each attachment field is also an object, an AttachmentField. They can be used as well instead of their data in array form:
$attachment = new Attachment([ 'fallback' => 'Some fallback text', 'text' => 'The attachment text', 'fields' => [ new AttachmentField([ 'title' => 'A title', 'value' => 'A value', 'short' => true ]) ] ]);
You can also set the attachments and fields directly if you have a whole lot of them:
// implicitly create a message and set the attachments $client->setAttachments($bigArrayOfAttachments); // or explicitly $client->createMessage()->setAttachments($bigArrayOfAttachments);
$attachment = new Attachment([]); $attachment->setFields($bigArrayOfFields);
Playground
There is the
php-slack/playground
simple console script to test messaging and to see how messages looks really.Questions
If you have any questions how to use or contribute,
you are welcome in our Slack Workspace.Contributing
If you're having problems, spot a bug, or have a feature suggestion, please log and issue on Github. If you'd like to have a crack yourself, fork the package and make a pull request. Please include tests for any added or changed functionality. If it's a bug, include a regression test.
-
- NXP Readme
MathExecutor
A simple and extensible math expressions calculator
Features:
- Built in support for +, -, *, /, % and power (^) operators
- Parentheses () and arrays [] are fully supported
- Logical operators (==, !=, <, <, >=, <=, &&, ||, !)
- Built in support for most PHP math functions
- Support for BCMath Arbitrary Precision Math
- Support for variable number of function parameters and optional function parameters
- Conditional If logic
- Support for user defined operators
- Support for user defined functions
- Support for math on user defined objects
- Dynamic variable resolution (delayed computation)
- Unlimited variable name lengths
- String support, as function parameters or as evaluated as a number by PHP
- Exceptions on divide by zero, or treat as zero
- Unary Plus and Minus (e.g. +3 or -sin(12))
- Pi ($pi) and Euler's number ($e) support to 11 decimal places
- Easily extensible
Install via Composer:
composer require nxp/math-executor
Sample usage:
use NXP\MathExecutor; $executor = new MathExecutor(); echo $executor->execute('1 + 2 * (2 - (4+10))^2 + sin(10)');
Functions:
Default functions:
- abs
- acos (arccos)
- acosh
- arccos
- arccosec
- arccot
- arccotan
- arccsc (arccosec)
- arcctg (arccot, arccotan)
- arcsec
- arcsin
- arctan
- arctg
- array
- asin (arcsin)
- atan (atn, arctan, arctg)
- atan2
- atanh
- atn
- avg
- bindec
- ceil
- cos
- cosec
- cosec (csc)
- cosh
- cot
- cotan
- cotg
- csc
- ctg (cot, cotan, cotg, ctn)
- ctn
- decbin
- dechex
- decoct
- deg2rad
- exp
- expm1
- floor
- fmod
- hexdec
- hypot
- if
- intdiv
- lg
- ln
- log (ln)
- log10 (lg)
- log1p
- max
- median
- min
- octdec
- pi
- pow
- rad2deg
- round
- sec
- sin
- sinh
- sqrt
- tan (tn, tg)
- tanh
- tg
- tn
Add custom function to executor:
$executor->addFunction('concat', function($arg1, $arg2) {return $arg1 . $arg2;});
Optional parameters:
$executor->addFunction('round', function($num, int $precision = 0) {return round($num, $precision);}); $executor->execute('round(17.119)'); // 17 $executor->execute('round(17.119, 2)'); // 17.12
Variable number of parameters:
$executor->addFunction('average', function(...$args) {return array_sum($args) / count($args);}); $executor->execute('average(1,3)'); // 2 $executor->execute('average(1, 3, 4, 8)'); // 4
Operators:
Default operators:
+ - * / % ^
Add custom operator to executor:
use NXP\Classes\Operator; $executor->addOperator(new Operator( '%', // Operator sign false, // Is right associated operator 180, // Operator priority function (&$stack) { $op2 = array_pop($stack); $op1 = array_pop($stack); $result = $op1->getValue() % $op2->getValue(); return $result; } ));
Logical operators:
Logical operators (==, !=, <, <, >=, <=, &&, ||, !) are supported, but logically they can only return true (1) or false (0). In order to leverage them, use the built in if function:
if($a > $b, $a - $b, $b - $a)
You can think of the if function as prototyped like:
function if($condition, $returnIfTrue, $returnIfFalse)
Variables:
Variables can be prefixed with the dollar sign ($) for PHP compatibility, but is not required.
Default variables:
$pi = 3.14159265359 $e = 2.71828182846
You can add your own variables to executor:
$executor->setVar('var1', 0.15)->setVar('var2', 0.22); echo $executor->execute("$var1 + var2");
Arrays are also supported (as variables, as func params or can be returned in user defined funcs):
$executor->setVar('monthly_salaries', [1800, 1900, 1200, 1600]); echo $executor->execute("avg(monthly_salaries) * min([1.1, 1.3])");
By default, variables must be scalar values (int, float, bool or string) or array. If you would like to support another type, use setVarValidationHandler
$executor->setVarValidationHandler(function (string $name, $variable) { // allow all scalars, array and null if (is_scalar($variable) || is_array($variable) || $variable === null) { return; } // Allow variables of type DateTime, but not others if (! $variable instanceof \DateTime) { throw new MathExecutorException("Invalid variable type"); } });
You can dynamically define variables at run time. If a variable has a high computation cost, but might not be used, then you can define an undefined variable handler. It will only get called when the variable is used, rather than having to always set it initially.
$calculator = new MathExecutor(); $calculator->setVarNotFoundHandler( function ($varName) { if ($varName == 'trans') { return transmogrify(); } return null; } );
Floating Point BCMath Support
By default,
MathExecutor
uses PHP floating point math, but if you need a fixed precision, call useBCMath(). Precision defaults to 2 decimal points, or pass the required number.WARNING
: Functions may return a PHP floating point number. By doing the basic math functions on the results, you will get back a fixed number of decimal points. Use a plus sign in front of any stand alone function to return the proper number of decimal places.Division By Zero Support:
Division by zero throws a
\NXP\Exception\DivisionByZeroException
by defaulttry { echo $executor->execute('1/0'); } catch (DivisionByZeroException $e) { echo $e->getMessage(); }
Or call setDivisionByZeroIsZero
echo $executor->setDivisionByZeroIsZero()->execute('1/0');
If you want another behavior, you can override division operator:
$executor->addOperator("/", false, 180, function($a, $b) { if ($b == 0) { return null; } return $a / $b; }); echo $executor->execute('1/0');
String Support:
Expressions can contain double or single quoted strings that are evaluated the same way as PHP evaluates strings as numbers. You can also pass strings to functions.
echo $executor->execute("1 + '2.5' * '.5' + myFunction('category')");
To use reverse solidus character (\) in strings, or to use single quote character (') in a single quoted string, or to use double quote character (") in a double quoted string, you must prepend reverse solidus character (\).
echo $executor->execute("countArticleSentences('My Best Article\'s Title')");
Extending MathExecutor
You can add operators, functions and variables with the public methods in MathExecutor, but if you need to do more serious modifications to base behaviors, the easiest way to extend MathExecutor is to redefine the following methods in your derived class:
- defaultOperators
- defaultFunctions
- defaultVars
This will allow you to remove functions and operators if needed, or implement different types more simply.
Also note that you can replace an existing default operator by adding a new operator with the same regular expression string. For example if you just need to redefine TokenPlus, you can just add a new operator with the same regex string, in this case '\+'.
Documentation
Full class documentation via PHPFUI/InstaDoc
Future Enhancements
This package will continue to track currently supported versions of PHP.
- phpDocumentor\Reflection Readme
ReflectionDocBlock
Introduction
The ReflectionDocBlock component of phpDocumentor provides a DocBlock parser that is 100% compatible with the PHPDoc standard.
With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.
Installation
composer require phpdocumentor/reflection-docblock
Usage
In order to parse the DocBlock one needs a DocBlockFactory that can be instantiated using its
createInstance
factory method like this:$factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
Then we can use the
create
method of the factory to interpret the DocBlock. Please note that it is also possible to provide a class that has thegetDocComment()
method, such as an object of typeReflectionClass
, the create method will read that if it exists.$docComment = <<<DOCCOMMENT /** * This is an example of a summary. * * This is a Description. A Summary and Description are separated by either * two subsequent newlines (thus a whiteline in between as can be seen in this * example), or when the Summary ends with a dot (`.`) and some form of * whitespace. */ DOCCOMMENT; $docblock = $factory->create($docComment);
The
create
method will yield an object of type\phpDocumentor\Reflection\DocBlock
whose methods can be queried:// Contains the summary for this DocBlock $summary = $docblock->getSummary(); // Contains \phpDocumentor\Reflection\DocBlock\Description object $description = $docblock->getDescription(); // You can either cast it to string $description = (string) $docblock->getDescription(); // Or use the render method to get a string representation of the Description. $description = $docblock->getDescription()->render();
For more examples it would be best to review the scripts in the
/examples
folder. - PHPFUI Readme
PHPFUI
PHP Wrapper for the Foundation CSS Framework
PHPFUI, PHP Foundation User Interface, is a modern PHP library that produces HTML formated for Foundation. It does everything you need for a fully functional Foundation page, with the power of an OO language. It currently uses Foundation 6.6.
"I was surprised that people were prepared to write HTML. In my initial requirements for this thing, I had assumed, as an absolute pre-condition, that nobody would have to do HTML or deal with URLs. If you use the original World Wide Web program, you never see a URL or have to deal with HTML. You're presented with the raw information. You then input more information. So you are linking information to information--like using a word processor. That was a surprise to me--that people were prepared to painstakingly write HTML."
Sir Tim Berners-Lee, inventor of the World Wide Web
Using PHPFUI for view output will produce 100% valid HTML and insulate you from future changes to Foundation, your custom HMTL layouts, CSS and JS library changes. You write to an abstract concept (I want a checkbox here), and the library will output a checkbox formatted for Foundation. You can inherit from CheckBox and add your own take on a checkbox, and when the graphic designer decides they have the most awesome checkbox ever, you simply change your CheckBox class, and it is changed on every page system wide.
Don't write HTML by hand!
Usage
namespace PHPFUI; $page = new Page(); $form = new Form($page); $fieldset = new FieldSet('A basic input form'); $time = new Input\Time($page, 'time', 'Enter A Time in 15 minute increments'); $time->setRequired(); $date = new Input\Date($page, 'date', 'Pick A Date'); $fieldset->add(new MultiColumn($time, $date)); $fieldset->add(new Input\TextArea('text', 'Enter some text')); $fieldset->add(new Submit()); $form->add($fieldset); $page->add($form); $page->addStyleSheet('/css/styles.css'); echo $page;
Installation Instructions
composer require phpfui/phpfui
Then run update.php from the vendor/phpfui/phpfui directory and supply the path to your public directory / the directory for the various JS and CSS files PHPFUI uses. This will copy all required public files into your public directory. For example:
php vendor/phpfui/phpfui/update.php public/PHPFUI
The PHPFUI library defaults to your-public-directory/PHPFUI, it can be overridden, but it is suggested to use PHPFUI to keep everything in one place. update.php should be run when ever you update PHPFUI.
Versioning
Versioning will match the Foundation versions for Major semantic versions. PHPUI will always support the most recent version of Foundation possible for the Major version. PHPFUI Minor version will include breaking changes and may incorporate changes for the latest version of Foundation. The PHPFUI Patch version will include non breaking changes or additions. So PHPFUI Version 6.0.0 would be the first version of the library, 6.0.1 would be the first patch of PHPFUI. Both should work with any Foundation 6.x version. PHPFUI 6.1.0 will track PHP 7.4 - 8.1, 6.2.0 will track 8.0 - 8.2, but both will still track Foundation 6.x. PHPFUI 7.0.0 would track Foundation 7.x series on currently supported versions of PHP.
Depreciation and Foundation changes
Since major versions of Foundation have in the past depreciated and obsoleted things, PHPFUI will track the latest version of Foundation for class names and functionality. However, when Foundation makes a breaking change or removes something, PHPFUI will continue to support the old functionality as best as possible in the new Foundation framework. Depreciated classes will be put in the \PHPFUI\Vx namespace (where x would be the prior Major Foundation version containing that feature). So if something gets depreciated in a newer version of Foundation, you simply will need to change your code from \PHPFUI\Example to \PHPFUI\V6\Example. The depreciated namespace will only be supported for one Major version of PHPFUI, so it is recommended you migrate off of it in a timely manor.
Full Class Documentation
Live Examples
Via PHPFUI/Examples
Unit Testing
Full unit testing using phpfui/html-unit-tester
License
PHPFUI is distributed under the MIT License.
PHP Versions
This library only supports modern versions of PHP which still receive security updates. While we would love to support PHP from the late Ming Dynasty, the advantages of modern PHP versions far out weigh quaint notions of backward compatibility. Time to upgrade.
- PHPFUI Paypal
PHPFUI PayPal Options
There are two ways to interface easily with PayPal. Checkout is recommended as the PayPal PHP wrapper for Express is abandoned.
PayPal Express (not recommended or supported but still works)
$page = new \PHPFUI\Page(); $express = new \PHPFUI\PayPal\Express($page, $yourClientId); $express->setType('sandbox'); $express->setPaymentUrl($root . '/Paypal/CreatePayment'); $express->setExecuteUrl($root . '/Paypal/AuthorizePayment'); $express->setErrorUrl($root . '/Paypal/ErrorPayment'); $page->add($express); echo $page;
You will then need to implement the above endpoints using the abandoned paypal/rest-api-sdk-php package.
PayPal Checkout (recommended)
$page = new \PHPFUI\Page(); $container = new \PHPFUI\HTML5Element('div'); // this element's html will be replaced by JavaScript below $container->add(new \PHPFUI\Header('Pay For Your Order')); $checkout = new \PHPFUI\PayPal\Checkout($page, $yourClientId); $head = 'https://www.YourDomain.com/PayPal/'; $executeUrl = $head . 'CompletedPayment'; $createOrderUrl = $head . 'CreateOrder'; $completedUrl = $head . 'Completed'; $cancelledUrl = $head . 'Cancelled'; $errorUrl = $head . 'Error'; $dollar = '$'; $id = $container->getId(); // Example JavaScript, change as needed for your pages $checkout->setFunctionJavaScript('onCancel', "{$dollar}.post('{$cancelledUrl}',JSON.stringify({orderID:data.orderID}),function(data){{$dollar}('#{$id}').html(data.html)})"); $checkout->setFunctionJavaScript('onError', "{$dollar}.post('{$errorUrl}',JSON.stringify({data:data,actions:actions}),function(data){{$dollar}('#{$id}').html(data.html)})"); $checkout->setFunctionJavaScript('createOrder', "return fetch('{$createOrderUrl}',{method:'post',headers:{'content-type':'application/json'}}). then(function(res){return res.json();}).then(function(data){return data.id;})"); $checkout->setFunctionJavaScript('onApprove', "return fetch('{$executeUrl}',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({orderID:data.orderID})}). then(function(res){return res.json();}). then(function(details){if(details.error==='INSTRUMENT_DECLINED'){return actions.restart();} $.post('{$completedUrl}',JSON.stringify({orderID:data.orderID}),function(data){{$dollar}('#{$id}').html(data.html)})})"); $container->add($checkout); $page->add($container); echo $page;
You will then need to implement the above endpoints using paypal/paypal-checkout-sdk package.
You can use the PHPFUI\PayPal classes to format the JSON response. They are fully type checked and bounded to avoid stupid errors.
Example createOrder
namespace \PHPFUI\PayPal; $order = new Order('CAPTURE'); $applicationContent = new ApplicationContent(); $applicationContent->brand_name = 'EXAMPLE INC'; $applicationContent->locale = 'en-US'; $applicationContent->landing_page = 'BILLING'; $applicationContent->shipping_preferences = 'SET_PROVIDED_ADDRESS'; $applicationContent->user_action = 'PAY_NOW'; $order->setApplicationContent($applicationContent); $purchase_unit = new PurchaseUnit(); $purchase_unit->reference_id = 'PUHF'; $purchase_unit->description = 'Sporting Goods'; $purchase_unit->custom_id = 'CUST-HighFashions'; $purchase_unit->soft_descriptor = 'HighFashions'; $amount = new Amount(); $amount->setCurrency(new Currency(220.00)); $breakdown = new Breakdown(); $breakdown->item_total = new Currency(180.00); $breakdown->shipping = new Currency(20.00); $breakdown->handling = new Currency(10.00); $breakdown->tax_total = new Currency(20.00); $breakdown->shipping_discount = new Currency(10.00); $amount->breakdown = $breakdown; $purchase_unit->amount = $amount; $shipping = new Shipping(); $shipping->method = 'United States Postal Service'; $address = new Address(); $address->address_line_1 = '123 Townsend St'; $address->address_line_2 = 'Floor 6'; $address->admin_area_2 = 'San Francisco'; $address->admin_area_1 = 'CA'; $address->postal_code = '94107'; $address->country_code = 'US'; $shipping->address = $address; $purchase_unit->shipping = $shipping; $item = new Item('T-Shirt', 1, new Currency(90.00)); $item->description = 'Green XL'; $item->sku = 'sku01'; $item->tax = new Currency(10.00); $item->category = 'PHYSICAL_GOODS'; $purchase_unit->addItem($item); $item = new Item('Shoes', 2, new Currency(45.00)); $item->description = 'Running, Size 10.5'; $item->sku = 'sku02'; $item->tax = new Currency(5.00); $item->category = 'PHYSICAL_GOODS'; $purchase_unit->addItem($item); $order->addPurchaseUnit($purchase_unit); $request = new \PayPalCheckoutSdk\Orders\OrdersCreateRequest(); $request->prefer('return=representation'); $request->body = $order->getData(); $env = new \PayPalCheckoutSdk\Core\SandboxEnvironment($yourClientId, $yourSecret); $client = new \PayPalCheckoutSdk\Core\PayPalHttpClient($env); $response = $client->execute($request); $page = new \PHPFUI\Page(); $page->setRawResponse(json_encode($response->result, JSON_PRETTY_PRINT)); echo $page;
Example onApprove
You will probably want to save the generated orderId from the above and check it here for additional validation. You will also want to do what ever processing you need to save and process the payment.
$json = json_decode(file_get_contents('php://input'), true); $request = new \PayPalCheckoutSdk\Orders\OrdersCaptureRequest($json['orderID']); $request->prefer('return=representation'); $env = new \PayPalCheckoutSdk\Core\SandboxEnvironment($yourClientId, $yourSecret); $client = new \PayPalCheckoutSdk\Core\PayPalHttpClient($env); $response = $client->execute($request); $result = $response->result; $txn = $result->purchase_units[0]->payments->captures[0]->id; $status = $result->purchase_units[0]->payments->captures[0]->status; $payment_amount = $result->purchase_units[0]->payments->captures[0]->amount->value; $page = new \PHPFUI\Page(); $page->setRawResponse(json_encode($response->result, JSON_PRETTY_PRINT)); echo $page;
Example Completed, onError or onCancel
Return the HTML that will be use to replace the original PayPal buttons. You can also pass other things back as needed.
$json = json_decode(file_get_contents('php://input'), true); $container = new \PHPFUI\Container(); $container->add(new \PHPFUI\Header('Thanks for your PayPal Payment')); $pre = new \PHPFUI\HTML5Element('pre'); $pre->add(print_r($json, 1)); $container->add($pre); $response = ['html' => "{$container}"]; $page = new \PHPFUI\Page(); $page->setRawResponse(json_encode($response, JSON_PRETTY_PRINT)); echo $page;
- PHPFUI\ConstantContact Readme
ConstantContact
PHP Object Oriented wrapper for the Constant Contact V3 API.
PHPFUI/ConstantContact is a modern PHP library that tracks the latest changes to the Constant Contact API.
PHPFUI/ConstantContact reads the YAML file from the Constant Contact documentation and generates PHP classes directly from the YAML file. The library is auto updated nightly. This means the library is always up to date with the latest changes. See the versioning section for further details.
Installation
Since this library is constantly updated when Constant Contact updates their API, it is best to modify the Composer version constraint from '^' to '>=', for example:
"phpfui/constantcontact": "^22.3",
should be changed to:
"phpfui/constantcontact": ">=22.3",
Requirements
This library requires PHP Session support for authentication. See PHP Manual and a good best practices article. You can provide your own session management by specifying a callback with setSessionCallback.
Namespaces
This library normalizes the Constant Contact API to modern PHP class standards. All endpoints are first character capitialized. Underscores are removed and followed by a capital letter. Each end point is a class with methods matching the standard REST methods (ie. put, post, delete, put, etc.). The methods take required and optional parameters matching the name specified in the Constant Contact YAML API. In addition, this library supports all definitions of types in the API. See below.
Due to a mistake in naming conventions by Constant Contact API designers, several end points are duplicated between the end point that returns all objects, and the end point that just works with one object. Normally this is dealt with by using the singular form of the noun for CRUD type operations on a single object, and the plural noun form returns a list of objects. This library follows the correct naming convention (single nouns for CRUD and plural nouns for collections) and not the Constant Contact naming convention.
Definitions
This Constant Contact API defines all types of objects to interact with the API. They are defined in the PHPFUI\ConstantContact\Definition namespace. Only valid fields are allowed to be accessed. Types are fully validated as to the best ability of PHP. Min and max lengths are enforced for strings.
Usage Once Authorized (see below)
// Create a client $client = new \PHPFUI\ConstantContact\Client($apiKey, $secret, $redirectURI); // Set access and refresh tokens on client $client->accessToken = $accessTokenFromDatabase; $client->refreshToken = $refreshTokenFromDatabase; // Refresh the tokens. This should be done on a regular (daily) basis so the token does not expire. $client->refreshToken(); // save $client->accessToken and $client->refreshToken to database. // $client is now ready to use $listEndPoint = new \PHPFUI\ConstantContact\V3\ContactLists($client); $lists = $listEndPoint->get(); do { print_r($lists); $lists = $listEndPoint->next(); } while ($lists);
Constant Contact Setup
In order to use this library, you will need to set up Constant Contact correctly. Go to Constant Contact API and create an application. Get an API Key and Secret and save it off for the \PHPFUI\ConstantContact\Client constructor. You will also need to set up and add a Redirect URI. You can use https://localhost if you want to authorize manually, but it is recommended to allow your users to authorize directly. The Redirect URI will receive a code that you will need to authorize the app, and then store the generated access token and refresh token. You should also provide the URL to your site's App logo. This will be shown to your user when they authenticate the app. They will need to sign into Constant Contact if they have not already done so. Fill out the other information as appropriate, but it is for informational purposes only.
Authorization Control Flow
You will need to set up a web page where your user can enter the API Key and Secret. You should also provide an "Authorize" button if the API returns an error, meaning it was not authorized. This allows the user to control the authorization process. Authorization needs to be done interactively. The user will have to log in to Constant Contact and then authorize the application. Once this is done, the app can run in the background and refresh the token daily and it should not expire or need to be reauthorized as long as the Secret has not been changed. Change the secret to require reauthorization.
The Authorization control flow is as follows:
1. Create A Client and Redirect to Authorization URL
$redirectURI = 'http://yourdomain/php_script_in_step2.php'; $client = new \PHPFUI\ConstantContact\Client($apiKey, $secret, $redirectURI); // set any scopes here, defaults to all scopes. Your user will need to accept what ever scopes you specify. // $client->setScopes(['contact_data', 'campaign_data']); \header('location: ' . $client->getAuthorizationURL());
The above will ask the user to authorize the app for the scopes you specified. The default is all scopes, but you can specify different scopes after constructing the client and before you authorize.
2. Retrieve the Code sent to the $redirectURI
$client = new \PHPFUI\ConstantContact\Client($apiKey, $secret, $redirectURI); $client->acquireAccessToken($_GET); // Save $client->accessToken and $client->refreshToken to the database // redirect back to your businesss logic (Step 3)
You have now received authorization to access the API according to the scopes you requested.
3. Use in your code
$client = new \PHPFUI\ConstantContact\Client($apiKey, $secret, $redirectURI); $client->accessToken = $accessTokenFromDatabase; $client->refreshToken = $refreshTokenFromDatabase; $listEndPoint = new \PHPFUI\ConstantContact\V3\ContactLists($client); $lists = $listEndPoint->get(); do { print_r($lists); $lists = $listEndPoint->next(); } while ($lists);
You can now access the API with the scopes you requested.
4. Refresh the token on a regular basis
$client->refreshToken(); // save $client->accessToken and $client->refreshToken to database.
The token will expire requiring your user to reauthorize your app unless you refresh the token. You should refresh the token on a regular basis to avoid reauthorization.
Versioning
Since the Constant Contact API is constantly being updated, this library will track all updates on a calendar based versioning schema. The major version will be the last two digits of the year the update was released. The minor version will be the month it was released. Any bug fixes will be a patch version. So V21.8.0 would be the first August 2021 version, and V21.8.1 would be a bug fix to V21.8. All bug fixes will be included in subsequent versions, so V21.9.0 would include all fixes from the V21.8 version. YAML changes are tracked nightly and a new version will be generated automatically. Multiple YAML changes in a month will be tracked as patch versions.
Full Class Documentation
License
PHPFUI/ConstantContact is distributed under the MIT License.
PHP Versions
This library only supports PHP 8.0 and higher versions of PHP. While we would love to support PHP from the late Ming Dynasty, the advantages of modern PHP versions far out weigh quaint notions of backward compatibility. Time to upgrade.
- PHPFUI\HTMLUnitTester Readme
PHPFUI\HTMLUnitTester
PHPUnit Testing extensions for HMTL and CSS. PHPFUI\HTMLUnitTester allows you to unit test HTML and CSS for errors and warnings. Often simple errors in HTML or CSS create hard to debug issues where a simple check will reveal bad code.
This package will check detect errors and warnings in HTML and CSS in stand alone strings, files, entire directories or urls.
For the best performanance, a local install of https://github.com/validator/validator is recommended.
Installation
composer require phpfui/html-unit-tester
Configuration
It is recommended you run https://github.com/validator/validator locally. Install Java and download the .jar file. Run with the following command:
java -Xss1024k -Dnu.validator.servlet.bind-address=127.0.0.1 -cp .\vnu.jar nu.validator.servlet.Main 8888
To run unit tests with GitHub Actions, add the following lines to you workflows test yml file:
- name: Setup Java uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '11' - name: Download vnu checker run: wget https://github.com/validator/validator/releases/download/latest/vnu.jar - name: Run Nu Html Checker (v.Nu) run: java -cp vnu.jar -Xss1024k -Dnu.validator.servlet.bind-address=127.0.0.1 nu.validator.servlet.Main 8888 &
Usage
Extend your unit tests from \PHPFUI\HTMLUnitTester\Extensions
class UnitTest extends \PHPFUI\HTMLUnitTester\Extensions { public function testValidHtml() { $this->assertValidHtml('<h1>Header</h1>'); $this->assertValidHtmlPage('<!DOCTYPE html><html><head><meta charset="utf-8"/><title>Title</title></head><body><div>This is a test</div></body></html>'); } }
You can use any of the following asserts:
- assertNotWarningCss
- assertNotWarningCssFile
- assertNotWarningCssUrl
- assertNotWarningFile
- assertNotWarningHtml
- assertNotWarningHtmlPage
- assertNotWarningUrl
- assertValidCss
- assertValidCssFile
- assertValidCssUrl
- assertValidFile
- assertValidHtml
- assertValidHtmlPage
- assertValidUrl
Directory Testing
Instead of file by file testing, use assertDirectory to test an entire directory. Any files added to the directory will be automatically tested.
$this->assertDirectory('ValidCSS', 'cssDirectory', 'Invalid CSS'); $this->assertDirectory('NotWarningCSS', 'cssDirectory', 'CSS has warnings');
The error message will include the offending file name.
Examples
See examples
Documentation
Full documentation at PHPFUI\HTMLUnitTester
License
PHPFUI\HTMLUnitTester is distributed under the MIT License.
PHP Versions
This library only supports modern versions of PHP which still receive security updates. While we would love to support PHP from the late Ming Dynasty, the advantages of modern PHP versions far out weigh quaint notions of backward compatibility. Time to upgrade.
- PHPFUI\InstaDoc Readme
PHPFUI\InstaDoc Library
A quick and easy way to add documentation to your PHP project
We all document our code with PHP DocBlocks but we never seem to actually generate the documentation and add it to our project. Why? It simply takes too much time (over a minute), so we put it off till later, and later never comes.
But with PHPFUI/InstaDoc, you can document your site in about a minute (OK, maybe 2). The steps involved:
- Install PHPFUI/InstaDoc via Composer (30 seconds)
- Run installation script (30 seconds)
- Create document page (1 minute, 6 lines of code)
Two minutes to usable documentation with the following features:
PHPFUI/InstaDoc Features
- Always up to date, even with code that is not yet checked in.
- Send constructor information including parameters and default values to clipboard.
- Child and Parent class hierarchy clearly displayed and accessable.
- Quick access to highlighted PHP source with user selectable highlighting.
- Quick access to the file's git history for the local repo.
- Full support for @inheritDoc tag so child method docs are displayed correctly.
- Documents all projects loaded via Composer automatically.
- Tabbed documentation so you are not looking at irrelevant methods.
- Alphabetized everything, no more searching unalphabetized pages!
- Support for markdown and custom markdown pages.
- Ability to generate static html files for high volume sites.
- Add any local repo directories.
- Remove any Composer project you don't care about.
- 5+ line config compatible with all PHP frameworks, or standalone.
- Uses Foundation CSS framework for a great experience on mobile.
Install PHPFUI/InstaDoc (requires PHP >= 8.0)
composer require phpfui/InstaDoc
Run Installation Script
Once installed, you need to run an installation script to copy static files to your public directory. From your project root, run the following:
php vendor/phpfui/instadoc/install.php yourPublicDirectory/subDirectory
Example: php vendor/phpfui/instadoc/install.php public/PHPFUI will add all needed files to public/PHPFUI, which will avoid any conflicts with your current files. You can specify any directory by using \PHPFUI\Page::setResourcePath, but PHPFUI is recomended to keep things simple.
Create Document Page
PHPFUI/InstaDoc does not reply on any framework and can run on a standalone page. It is recommended that you do not make your documentation public, as PHPFUI/InstaDoc will display PHP source files. How you restrict access to the page is up to you. The following does not restrict access and is simply an example:
include 'yourAutoLoader.php'; // pass the directory containing your composer.json file $fileManager = new \PHPFUI\InstaDoc\FileManager('../'); // add your App class tree in, pass true as the last parameter if this namespace is in your local git repo. $fileManager->addNamespace('App', '../App', true); // load your cached files $fileManager->load(); // load child classes if you want to display them, if you don't do this step, docs will not show classes that extend the displayed class \PHPFUI\InstaDoc\ChildClasses::load(); // get the controller $controller = new \PHPFUI\InstaDoc\Controller($fileManager); // display will return a fully formed page echo $controller->display();
That is it. You are done!
Adding New Classes
PHPFUI/InstaDoc saves the classes to display in PHP serialized files. Delete those files (.serial extension) when you want to display new classes. PHPFUI/InstaDoc will regenerate automatically if the files are missing.
Add Child Classes to the Docs
\PHPFUI\InstaDoc\ChildClasses::load('../ChildClasses.serial');
Add a Global Namespace Class
The git repo path defaults to the composer directory, but you can change the path by calling:
$fileManager->addGlobalNameSpaceClass(__DIR__ . '/global/FPDF.php');
Removing a Namespace
$fileManager->excludeNamespace('Carbon');
Add git Repository Page
The git repo path defaults to the composer directory, but you can change the path by calling:
$controller->setGitRoot(getcwd() . '/../');
Add Documents To Your Docs Home Page
$controller->addHomePageMarkdown('../PHPFUI/InstaDoc/README.md');
Set Your Home Page
You may want users to get back into your system easily. Clicking on the top left menu bar will take them here:
$controller->setHomeUrl('/');
Breakup Your Documentation Into Sections
If you have a lot of source code, you might want to break it into sections, so you will need a separate file to store the index in per section:
$fileManager->setBaseFile('SubProject');
Generate Static Files
Just the doc and file pages, no git!
$controller->generate('static/file/path', [\PHPFUI\InstaDoc\Controller::DOC_PAGE, \PHPFUI\InstaDoc\Controller::FILE_PAGE, ]));
Examples and Full Class Documentation
- PHPFUI\MySQLSlowQuery Readme
PHPFUI\MySQLSlowLog\Parser
PHP Parser for MySQL and MariaDB Slow Query Logs featuring sortable results
Requirements
- Modern PHP version
- MySQL 5.7 or higher, or MariaDB
Usage
$parser = new \PHPFUI\MySQLSlowQuery\Parser($logFilePath); // Return the sessions in the file as array $sessions = $parser->getSessions(); // Return all entries in file as array, or pass session number (0 based) $entries = $parser->getEntries(); if (count($entries)) { // Get the worst offender $entry = $parser->sortEntries()->getEntries()[0]; echo 'Query ' . implode(' ', $entry->Query) . " took {$entry->Query_time} seconds at {$entry->Time}\n"; // Get the most rows examined $entry = $parser->sortEntries('Rows_examined', 'desc')->getEntries()[0]; echo 'Query ' . implode(' ', $entry->Query) . " looked at {$entry->Rows_examined} rows\n"; }
Entries
\PHPFUI\MySQLSlowQuery\Entry provides details on each query. Supported fields:
- Time
- User
- Host
- Id
- Query_time
- Lock_time
- Rows_sent
- Rows_examined
- Query (array)
- Session (zero based)
MariaDB adds the following fields:
- Thread_id
- Schema
- QC_hit
- Rows_affected
- Bytes_sent
- Tmp_tables
- Tmp_disk_tables
- Tmp_table_sizes
- Full_scan
- Full_join
- Tmp_table
- Tmp_table_on_disk
- Filesort
- Filesort_on_disk
- Merge_passes
- Priority_queue
- explain
Sessions
\PHPFUI\MySQLSlowQuery\Session contains MySQL server information and are created on server restarts and log flushes. Pass the zero based session number to getEntries for only that Session's entries. Supported fields:
- Server
- Version
- Port
- Transport
Sort Entries
By default, entries are returned in log order, but call sortEntries on the Parser to sort by any valid field (parameter 1). Sort defaults to 'desc', anything else will sort ascending.
Full Class Documentation
License
Distributed under the MIT License.
- PHPFUI\ORM 6. Migrations
PHPFUI\ORM Migrations
Note: Referenced namespaces in this document refer to the PHPFUI\ORM defaults.
Migrations convert the data from one version of the database to another. They are atomic and can be run individually or in a group. Even if run in a group, they can still be reverted individually.
Migration Table
The table
migration
keeps track of the current version of the database. It contains the version number and the time it was run for deployment tracking.Running Migrations
You use the \PHPFUI\ORM\Migrator class to run migrations. An example command line script example:
$migrate = new \PHPFUI\ORM\Migrator(); $level = (int)($argv[1] ?? 0); if ($level) { $migrate->migrateTo($level); } else { $migrate->migrate(); } \print_r($migrate->getErrors());
You can include this logic in your deployment for full control of when migrations happen. The migrateTo() method will run the migrations in the correct order (up or down). You can also run migrations individually.
Migration Architecture
Migrations are located in the \App\Migration namespace. They must be a class named Migration_X where X is an integer number. Migrations must start with 1 and be contigious integers without gaps to the last migration. Gaps in the sequence are not allowed. Migrations are run by migration number and not date to avoid sync conflicts on release that plague more common migration systems. Conflicts with migrations due to branching are handled by git at merge time.
Typical migration:
namespace PHPFUI\ORM\Migration; class Migration_1 extends \PHPFUI\ORM\Migration { public function description() : string { return 'Create migation table'; } public function down() : bool { return $this->dropTable('migration'); } public function up() : bool { // drop the table just in case $this->down(); return $this->runSQL('create table migration (migrationId int(11) NOT NULL primary key, ran TIMESTAMP DEFAULT CURRENT_TIMESTAMP);'); } }
Migrations must be inherited from \PHPFUI\ORM\Migration. They must have the following methods defined:
- description() : string, returns a human readable description of what the migration does.
- up() : bool, performs SQL statements to take the database to the next version. Returns true on success.
- down() : bool, reverts the up and leaves the database in the prior version state. Returns true on success.
Running migrations from code
The \PHPFUI\ORM\Migrator class is used to run migrations automatically. Use the migrate() method to go to the latest version, or migrateTo() for a specific version. The class handles running migrations in the correct order. The getStatus() result should be shown to the user.
Caveats
- All migrations need to run reliably up and down and back up again.
- Do not depend on \App\Table or \App\Record classes, as their definition can change over time (due to migrations), so fields existing in a early migration might not exist in the most recent version of the schema. A user upgrading from an old version to a modern version may not have the same definition of a \App\Record and the migration can throw an error.
- Some migrations that are data migrations may not need a functional down() method. It can just return true. Also other database mods (like adding an index) might not need a down migration.
- All migrations should be rerunable even if they fail mid migration.
- Alter statements are cached per table. Use executeAlters() method if you need to update a table after an alter statement.
Available methods to make migrations easier
-
getAllTables(string $type = 'BASE TABLE') : array
- Return array containing table names
-
getMySQLSetting(string $variable) : string
- Get a MySQL setting
-
executeAlters() : bool
- Run all the cached alter statements. You will need to call if this directly if you need to change a table altered in the current migration
-
runSQL(string $sql, array $input = []) : bool
- Runs the current SQL statement immediately
-
deleteDuplicateRows(string $table, array $keys) : bool
- Duplicate rows with the same key values will be deleted
-
dropPrimaryKey(string $table) : bool
- Drops the primary key
-
addPrimaryKeyAutoIncrement(string $table, string $field = '', string $newFieldName = '') : bool
- Adds a primary key to the table. If $field is not specified, it will the primary key will be the table name with Id appended. If $newFieldName is not specified, it will default to $field. This method works on an existing field only.
-
dropColumn(string $table, string $field) : bool
- Drops a column if it exists
-
dropTable(string $table) : bool
- Drops a table if it exists
-
renameTable(string $oldName, string $newName) : bool
- Renames an existing table
-
dropTables(array $tables) : void
- Drops tables contained in the array
-
dropView(string $view) : bool
- Drops a view if it exists
-
dropViews(array $views) : void
- Drops views contained in the array
-
addIndex(string $table, array $fields, string $indexType = '') : bool
- Add an index on the fields in the array.
-
dropIndex(string $table, string | array $fields) : bool
- Drops an index by the name used by addIndex
-
dropAllIndexes(string $table) : void
- Drops all indexes on a table but not the primary key.
-
indexExists(string $table, string $indexName) : bool
- Tests for existance of an index on the table
-
dropForeignKey(string $table, array $columns) : bool
- Drops the foreign key on the table
-
addForeignKey(string $toTable, string $referenceTable, array $columns, string $onDelete = 'CASCADE', string $onUpdate = 'CASCADE') : bool
- Creates a foreign key on the table referencing the given table and columns.
-
addColumn(string $table, string $field, string $parameters) : bool
- Always adds a column
-
alterColumn(string $table, string $field, string $parameters, string $newName = '') : bool
- Alters a column incluing a reneme if $newName is provided
- PHPFUI\ORM 8. Translations
PHPFUI\ORM Translation
If you are using validation, you need to provide a translation. This library uses the PHPFUI\Translation class, or provide a callback with the following parameter signature:
function translate(string $text, array $values = []) : string;
Due to static initialization issues on PHP, you will need to initialize the translation via the setTranslationCallback method.
You will need to copy the provided translations (in ./translations directory) into your project or specify the path to the vendor directory.
Suggested usage:
\PHPFUI\ORM::setTranslationCallback(\PHPFUI\Translation\Translator::trans(...)); \PHPFUI\Translation\Translator::setTranslationDirectory(__DIR__ . '/../vendor/phpfui/orm/translations'); \PHPFUI\Translation\Translator::setLocale(\Locale::getDefault());
- PHPFUI\ORM 1. Setup
PHPFUI\ORM Setup
PHPFUI\ORM takes a SQL first approach to models. You update the SQL schema first, then generate models from the SQL schema. You can use migrations to change the schema (recommended), or modify the schema by hand.
Database Schema Requirements
PHPFUI\ORM needs a strictly normalized database structure to work. The following conditions must be met:
- Table primary keys must be in the form of (table name)(id suffix). Examples would be member.memberId, purchase_order.purchase_order_id or even OrderDetail.OrderDetailId
- Consistant case is required for field and table names.
- Field names referencing other table's primary key must match the referenced table primary id field name. Example: purchase_order_detail.purchase_order_id would reference the parent purchase_order record.
Models Defined
PHPFUI\ORM mandates the following models and separate namespaces:
- Record - represents a record from a table
- Table - represents the entire SQL table
- Definition - describes the fields in the table
- Validation (optional) - defines validation rules for each field
Once generated, the Record, Table and Validation models will not be overwritten by the generation tools. Developers can add any logic to these classes they desire.
The Definition models should only be modified by the generation tools and not updated by hand.
Deletion of unused models should be done manually when the table is removed from the schema.
PHP Class Naming Conventions
All PHPFUI\ORM classes and namespaces use StudlyCase naming conventions. SQL table names are used the generate the PHP class names. Case is preserved except the first letter is the converted to upper case. Table names with underscores are converted to StudlyCase and underscores are removed. Field names are not affected and left unchanged.
Each table will generate 3 classes (4 if validators are generated) with the same class name, but in the specified namespaces (see below). Definition and Validation namespaces are children of the Record namespace.
Configuration
PHPFUI\ORM needs the following information to work with your code:
- Your Namespace Root - \PHPFUI\ORM::$namespaceRoot, Default: Root namespace directory (__DIR__ . '/../..')
- The Record Model Namespace - \PHPFUI\ORM::$recordNamespace, Default: 'App\Record'
- The Table Model Namespace - \PHPFUI\ORM::$tableNamespace, Default: 'App\Table'
- The Migration Namespace - \PHPFUI\ORM::$migrationNamespace, Default: 'App\Migration'
- Primary Key Suffix - \PHPFUI\ORM::$idSuffix, Default: 'Id'
If you are unable to use the defaults, it is recommended setting the\PHPFUI\ORM static fields immediately after your autoloader and before you open a database connection:
Example Setup
PHPFUI\ORM uses PHP's PDO model exclusively. You must create a valid PDO connection with the \PHPFUI\ORM\PDOInstance class and pass it to \PHPFUI\ORM::addConnection() to allow access to the database.
include 'vendor/autoload.php'; \PHPFUI\ORM::$namespaceRoot = __DIR__; \PHPFUI\ORM::$recordNamespace = 'App\\Model\\Record'; \PHPFUI\ORM::$tableNamespace = 'App\\Model\\Table'; \PHPFUI\ORM::$migrationNamespace = 'App\\DB\\Migration'; \PHPFUI\ORM::$idSuffix = '_id'; \PHPFUI\ORM::addConnection(new \PHPFUI\ORM\PDOInstance('sqlite:northwind.db'));
Migrations
Add the migrations to your migration namespace. Migration classes must be numbered starting at 1 in the format of Migration_X where X is the migration id. Breaks in the numbering sequence are not allowed.
Numbered migrations ensure all developers on a team run the migrations in the same order. This solves a major problem with time stamped migration in other popular ORM systems. Time stamping a migration means migrations may run in different orders by different developers when working on independent branches. Migrations can also be applied to different enviornments in different orders depending on branch merging and deployments. Numbered migrations are resolved at merge time and all developers have to apply them in the correct order. Developers are responsible for making sure they are on the correct migration before switching to new branches.
Running Migrations
Use the \PHPFUI\ORM\Migrator class to update the schema to any level. You can migrate up or down individually or in groups.
Generating Models
Once you have a compatible schema, or have modified a schema, you need to generate code. Use the \PHPFUI\ORM\Tool\Generate\CRUD class to generate all or individual models. Check out the scripts folder for an example.
Once Record and Table models are generated, they will not be overwritten by the generation tools. Feel free to add methods by hand for your application needs.
You should regenerate models when ever you update this library. Generation will only ever overwrite files in the Definition namespace, which should not be edited by hand. Record, Table, and Validation namespaces will only be generated if the file is missing.
Validation
You can also generate initial validators with the \PHPFUI\ORM\Tool\Generate\Validator class. Once generated, you should modify by hand for your application.
Validation rules can be found here: \PHPFUI\ORM\Validator.
- PHPFUI\ORM 10. Miscellaneous
PHPFUI\ORM Miscellaneous
SQLite Support
While this library is tested against SQLite, there are differences between MySQL/MariaDB syntax and SQLite syntax. Most notabliy is insertOrUpdate() and insertOrIgnore() which are not supported for SQLite. Use the custom SQL query support below instead.
Custom SQL Queries
PHFUI\ORM supports raw string queries for special cases or complex queries. The following static methods of \PHPFUI\ORM are supported:
getArrayCursor(string $sql = 'select 0 limit 0', array $input = []) : \PHPFUI\ORM\ArrayCursor
Returns an ArrayCursor from any SQL statement.
getDataObjectCursor(string $sql = 'select 0 limit 0', array $input = []) : \PHPFUI\ORM\DataObjectCursor
Returns an DataObjectCursor from any SQL statement.
getRecordCursor(\PHPFUI\ORM\Record $crud, string $sql = 'select 0 limit 0', array $input = []) : \PHPFUI\ORM\RecordCursor
Returns an RecordCursor from any SQL statement.
execute(string $sql, array $input = []) : bool
Execute any SQL string. Useful for queries where you don't care about the results, or don't return results. True is returned on success.
getRow(string $sql, array $input = []) : array<string, string>
Returns a single row of the first matching record or an empty array if an error
getRows(string $sql, array $input = [], int $fetchType = \PDO::FETCH_ASSOC) : array
Returns the query results in an array. Because this reads all records into memory at the same time, it is recommended to use an ArrayCusor for interation on the data instead.
getValue(string $sql, array $input = []) : string
Returne the value from the first field in the first row returned by the querry, or blank if error
getValueArray(string $sql, array $input = []) : array
Returns the data values of the first element in each row of the query.
\PHPFUI\ORM\Record Data Cleaning Helpers
The \PHPFUI\ORM\Record class provides some simple data cleaning functions you can use where needed.
- cleanUpperCase(string $field) Converts the field to all upper case
- cleanLowerCase(string $field) Converts the field to all lower case
- cleanNumber(string $field) removes all non-digits (0-9 and -) from string representation of a number
- cleanFloat(string $field, int $decimalPoints = 2) removes all non-digits (0-9, . and -)
- cleanPhone(string $field, string $regExSeparators = '\-\.') removes all non-digits (0-9) and regex separators
- cleanProperName(string $field) Properly capitalizes proper names if in single case. Mixed case strings are not altered.
- cleanEmail(string $field) Lowercases and strips invalid email characters. Does not validate email address.
Example Scripts
It is recommended to make versions of these example scripts customized to you needs:
cleanBackup.php
Removes various attributes of the backup to make it easier to restore on another server.
generateCRUD.php
Generates the models for your application based on your schema. You will want to change the PDO instance to your database connection string.
generateValidators.php
Generates the validators for your models based on your schema. You will want to change the PDO instance to your database connection string.
migrate.php
Example migration script.
Support Namespaces
The following namespaces are used in production but contains support classes or examples.
- PHPFUI\ORM\Tool used by model generation
- PHPFUI\ORM\Record contains example of migration table.
- PHPFUI\ORM\Table contains example of migration table.
- PHPFUI\ORM 3. Active Table
PHPFUI\ORM Active Table
While an Active Record represents a single row in the database table, an Active Table represents the entire table. \PHPFUI\ORM\Table allows you to find, select, update, insert and delete multiple records at a time.
Active Tables are easy to manipulate in code and free you from constructing SQL statements with plain text.
The following things can be set programatically:
- Join
- Select
- Where
- Having
- Limit
- Union
- Group By
- Order By
WHERE and HAVING Conditions
Conditions for WHERE and HAVING are created using the \PHPFUI\ORM\Condition class.
Equal is the default for a Condition
The following code sets up an equal ('=') condition:
$condition = new \PHPFUI\ORM\Condition('lastName', 'Rubble'));
You can add an AND or OR clause to the condition easily:
$condition->and('firstName', 'Barney');
The above will produce the PDO equivalent of
lastName = 'Rubble' AND firstName = 'Barney'
You can also OR in a condition like:
$condition->or('firstName', 'Fred');
The above will now produce the PDO equivalent of
lastName = 'Rubble' AND firstName = 'Barney' OR firstName = 'Fred'
You can parenthesize a condition by adding to another condition:
$conditionA = new \PHPFUI\ORM\Condition('lastName', 'Rubble'); $conditionA->and('firstName', 'Barney'); $conditionB = new \PHPFUI\ORM\Condition('lastName', 'Flintstone'); $conditionB->and('firstName', 'Fred'); $condition = new \PHPFUI\ORM\Condition(); $condition->or($conditionA); $condition->or($conditionB);
The above will produce the PDO equivalent of '(lastName = 'Rubble' AND firstName = 'Barney') OR (lastName = 'Flintstone' AND firstName = 'Fred')'
Other operators
You can also make conditions with the following operators as the third parameter to \PHPFUI\ORM\Condition:
- \PHPFUI\ORM\Operator\Equal
- \PHPFUI\ORM\Operator\NotEqual
- \PHPFUI\ORM\Operator\GreaterThan
- \PHPFUI\ORM\Operator\GreaterThanEqual
- \PHPFUI\ORM\Operator\In
- \PHPFUI\ORM\Operator\NotIn
- \PHPFUI\ORM\Operator\IsNotNull
- \PHPFUI\ORM\Operator\IsNull
- \PHPFUI\ORM\Operator\LessThan
- \PHPFUI\ORM\Operator\LessThanEqual
- \PHPFUI\ORM\Operator\Like
- \PHPFUI\ORM\Operator\NotLike
For example:
$conditionA = new \PHPFUI\ORM\Condition('lastName', 'R', new \PHPFUI\ORM\Operator\GreaterThanEqual()); $conditionA->and('lastName', 'S', new \PHPFUI\ORM\Operator\LessThan()); $conditionB = new \PHPFUI\ORM\Condition('lastName', 'F', new \PHPFUI\ORM\Operator\GreaterThanEqual()); $conditionB->and('lastName', 'G', new \PHPFUI\ORM\Operator\LessThan()); $condition = new \PHPFUI\ORM\Condition(); $condition->or($conditionA); $condition->or($conditionB);
The above will produce the PDO equivalent of
(lastName >= 'R' AND lastName < 'S') OR (lastName >= 'F' AND lastName < 'G')
The In and NotIn operators require the second parameter to Condition to be an array. The Like and NotLike operators will respect the % and _ characters, but you are responsible for putting them in the correct positions.
Literal and Field classes
Sometimes you need to compare something to a SQL constant or call a function. You can use
new \PHPFUI\ORM\Literal('CURRENT_TIMESTAMP()')
.If you need to specify a table for a field to make it unique, you can use
new \PHPFUI\ORM\Field('order.order_id', 'orderId')
. The second parameter is the AS clause.Once you have a condition
You can set the table to use a WHERE or HAVING condition:
$orderTable = new \App\Table\Order(); $orderTable->setWhere($whereCondition)->setHaving($havingCondition); $cursor = $orderTable->getRecordCursor();
Select fields
By default all fields (*) are selected for a table. You can add specific selects with addSelect. Use the second parameter for the as option. Or specify a complete Select clause with setSelectFields().
Limits and Offsets
You can specify a start offset by calling setOffset($offset). The number of records to return is setLimit($limit). For easy pagination, you can use setLimit($limit, $page) where $page is the page number (zero based) to start on. The offset will be computed by the $limit specified.
Group By and Order By
You can specify Group By and Order By with:
- addGroupBy($field), add an additional group by clause
- setGroupBy($field), resets the group by clause to the field specified
- addOrderBy($field), add an additional order by clause, defaults to ascending, use the second parameter to specify 'desc'
- setOrderBy($field), resets the order by clause, defaults to ascending, use the second parameter to specify 'desc'
Joins
Joins on a the primary key of the joined table can be easily accomplished with:
$orderDetailTable = new \App\Table\OrderDetail(); $orderDetailTable->addJoin('order');
If a more complicated on condition is required, you can pass a \PHPFUI\ORM\Condition object as the second parameter to addJoin
The third parameter is the join type (LEFT, INNER, OUTER, RIGHT, FULL, CROSS). Default is LEFT.
The forth parameter is the AS option.
Unions
Unions can be implimented by configuring another table with a matching number of selects and then calling addUnion. Unions can have full Join, Where, Group By, Having, Order By and Limit clauses.
$table = new \Tests\App\Table\InventoryTransactionType(); $table->addSelect('inventory_transaction_type_id', 'id'); $table->addSelect('inventory_transaction_type_name', 'name'); $table->addUnion(new \Tests\App\Table\OrderDetailStatus()); $table->addUnion(new \Tests\App\Table\OrderStatus()); $table->addUnion(new \Tests\App\Table\OrderTaxStatus()); $table->addUnion(new \Tests\App\Table\PurchaseOrderStatus()); $table->addOrderBy('name');
Explain
You can get the execution plan for the current \PHPFUI\ORM\Table query with the getExplainRows method. The results of this are dependent on the underlying database and can change.
- PHPFUI\ORM 4. Cursors
PHPFUI\ORM Cursors
Data Cursors are implemented on low level database cursors. They can be iterated over without having to read in all the records returned from a query into an array, which can use up large chunks of memory.
Three types of Data Cursors
Common properties
- All methods in the Iterator interface.
- count() returns the number of records returned in this specific query affected by the limit clause.
- total() returns the total number of records for the query without the limit clause.
All three cursors can iterate over the results of a join query. The RecordCursor will only have the fields from the table set, while ArrayCursor and DataObjectCursor will have all fields specified by the join.
ArrayCursor
An ArrayCursor returns an actual array for every interation representing a row from the query. This code list all customers with a first name of 'Fred' in the database.
$customerTable = new \App\Table\Customer(); $customerTable->setWhere(new \PHPFUI\ORM\Condition('first_name', 'Fred')); foreach ($customerTable->getArrayCursor() as $row) { echo $row['first_name'] . ' ' . $row['last_name'] . "\n"; }
DataObjectCursor
A DataObjectCursor returns a DataObject that uses object notation to access fields. It does not have any settable properties or methods associated with it. It implements ArrayAccess, but is not an array. It also can return related records on valid fields.
$customerTable = new \App\Table\Customer(); $customerTable->setWhere(new \PHPFUI\ORM\Condition('first_name', 'Fred')); foreach ($customerTable->getDataObjectCursor() as $record) { echo "Name: {$record->first_name} {$record->last_name}\n"; echo "Cell: {$record['cellPhone']\n"; }
RecordCursor
A RecordCursor returns a \PHPFUI\ORM\Record typed from the table. It is a fully functional Active Record. It also implements ArrayAccess, but is not an array.
$customerTable = new \App\Table\Customer(); $customerTable->setWhere(new \PHPFUI\ORM\Condition('first_name', 'Fred')); foreach ($customerTable->getDataObjectCursor() as $record) { echo "Name: {$record->fullName()}\n"; echo "Cell: {$record['cellPhone']\n"; $record->first_name = ucwords($record->first_name); $record->last_name = ucwords($record->last_name); $return->update(); }
Please Note: The \PHPFUI\ORM\Record reuses the same \PHPFUI\ORM\Record instance to conserve memory, so they will need to be cloned if added to an array or collection. DataObjectCursor and ArrayCursor return new objects.
- PHPFUI\ORM 5. Virtual Fields
PHPFUI\ORM Virtual Fields
Note: Referenced namespaces in this document refer to the PHPFUI\ORM defaults.
You can define virtual fields with get and set semantics for any \App\Record class. Virtual fields are evaluated before any database field, so you can override the database defaults if needed, or create new functionality.
Every \App\Record class has a static $virtualFields array defined. The key of the array is the name of the virtual field. The value for each virtual field is an array of strings. The first string is the virtual field class name. Subsequent parameters are are passed to the getValue and setValue methods.
Note that virtual fields are created each time they are accessed and not stored or cached in the \PHPFUI\ORM\Record object. This means you can not change the virtual field on the Record object itself. You can only assign it to the Record object, which will store the value represented by the virtual field in the object, but not the virtual field itself.
The VirtualField class has two properties that will always be defined for use by the derived class:
- $currentRecord is the current record that the virtual field should be based on.
- $fieldName the field name that the VirtualField object was created from. This is the key of the $virtualFields array in the Record class.
One To One and Parent Related Record Relationships
If a field is named the same way as a corresponding table and suffixed with the proper ID, PHPFUI\ORM will automatically generate a One To One or parent relationship for you.
A child record with an ID field of the parent record automatically has a parent relationship via the build in related record functionality.
In the northwind schema, order_detail records are children of an order record, as they have an order_id key.
$orderDetail = new \App\Record\OrderDetail(27); $parentOrder = $orderDetail->order;
Likewise, the Invoice record has a relationship to its Order record, and from the order, we can get the shipper's company field.
$invoice = new \App\Record\Invoice(7); echo $invoice->order->shipper->company;
Custom Related Record Relationships
Sometimes you can't name a related record with the name of the table. For example you might have an Employee table, but yet need to have several references to different employees in the same table. You might have the following fields which are all Employee Ids:
- salesPerson_id
- packedBy_id
- inspectedBy_id
You can make them all return employees with the following virtual field definitions:
class Order extends \Tests\App\Record\Definition\Order { protected static array $virtualFields = [ 'salesPerson' => [\PHPFUI\ORM\RelatedRecord::class, \App\Record\Employee::class], 'packedBy' => [\PHPFUI\ORM\RelatedRecord::class, \App\Record\Employee::class], 'inspectedBy' => [\PHPFUI\ORM\RelatedRecord::class, \App\Record\Employee::class], ]; }
Usage
echo 'Sales Person : ' . $order->salesPerson->fullName() . "\n"; echo 'Packed By : ' . $order->packedBy->initials() . "\n"; echo 'Inspected By : ' . $order->inspectedBy->first_name . "\n";
You can also assign records to the related record if it the right type.
$employee = new \App\Record\Employee(['last_name' => 'Zare']); $order->salesPerson = $employee;
Custom Virtual Fields
You can write custom classes to create virtual fields on any record. Here we are adding a gross virtual to the OrderDetail record.
class Gross extends \PHPFUI\ORM\VirtualField { public function getValue(array $parameters) : mixed { return number_format($currentRecord->unit_price * $currentRecord->quantity - $currentRecord->discount, 2); } } class OrderDetail extends \Tests\App\Record\Definition\OrderDetail { protected static array $virtualFields = [ // define a new virtual field using the above Gross class 'gross' => [Gross::class], ]; }
Child Records
OrderDetail children for an Order record can be defined as:
class Order extends \Tests\App\Record\Definition\Order { protected static array $virtualFields = [ // the OrderDetailChildren will be returned in order_detail_id order. Leave off the third array element to let SQL determine the order if you don't care. 'orderDetailChildren' => [\PHPFUI\ORM\Children::class, \Tests\App\Table\OrderDetail::class, 'order_detail_id'], ]; }
By default, child records will be automatically deleted when the parent record is deleted. You can disable this functionality for a specific \PHPFUI\ORM\Record class by setting the static property $deleteChildren to false, or by using your own Children class.
Usage
$order = new \App\Record\Order(31); foreach ($order->orderDetailChildren as $orderDetail) { echo "Gross {$orderDetail->gross} for product {$orderDetail->product->product_name}\n"; }
Many To Many
Many To Many relationships can be easily constructed with a junction table containing the primary keys of the two tables you want with a many to many relationship.
For the Product and Supplier tables, you need to add this:
class Product extends \Tests\Fixtures\Record\Definition\Product { protected static array $virtualFields = [ 'suppliers' => [\PHPFUI\ORM\ManyToMany::class, \Tests\App\Table\ProductSupplier::class, \Tests\App\Table\Supplier::class], ]; } class Supplier extends \Tests\Fixtures\Record\Definition\Supplier { protected static array $virtualFields = [ 'products' => [\PHPFUI\ORM\ManyToMany::class, \Tests\App\Table\ProductSupplier::class, \Tests\App\Table\Product::class], ]; }
Many To Many relationships also support adding records to the relations with a simple assignment. The added record is inserted automatically and should not be previously inserted.
Morph Many
Morph Many relationships (ala Eloquent) can be easily constructed with a junction table containing the primary keys of the two tables you want with a many to many relationship.
For the Product and Employee tables to share image records, you need to add this:
class Product extends \Tests\Fixtures\Record\Definition\Product { protected static array $virtualFields = [ 'photos' => [\PHPFUI\ORM\MorphMany::class, \Tests\App\Table\Image::class, 'imagable', ], ]; } class Employee extends \Tests\Fixtures\Record\Definition\Employee { protected static array $virtualFields = [ 'photos' => [\PHPFUI\ORM\MorphMany::class, \Tests\App\Table\Image::class, 'imagable', ], ]; }
Morph Many relationships also support adding records to the relations with a simple assignment. The added record is inserted automatically and should not be previously inserted.
Usage
$product = new \App\Record\Product(4); $suppliers = $product->suppliers; echo "There are {$suppliers->count()} for product {$product->product_code} - {$product->product_name}:\n"; foreach ($suppliers as $supplier) { echo $supplier->company . "\n"; }
Cast Virtual Field
Often you want to use PHP class instead of a native scalar type (string, int, float, bool) to make your life easier. The Carbon class is an excellent example of a widely used package. It would be nice to get and set Carbon objects instead of strings formatted to the MySQL date format.
Use \PHPFUI\ORM\Cast virtual field to accommplish this. The Cast virtual field works with a wide variety of packages, as its only requirements are to implement __toString and a construct from a value.
Usage
class Invoice extends \Tests\App\Record\Definition\Order { protected static array $virtualFields = [ 'due_date' => [\PHPFUI\ORM\Cast::class, \Carbon\Carbon::class], 'invoice_date' => [\PHPFUI\ORM\Cast::class, \Carbon\Carbon::class], ]; } $invoice = new Invoice(20); echo 'Lead Weeks: ' . $invoice->invoice_date->diffInWeeks($invoice->due_date);
Type Safe Enum Support
In PHP 8.1 and above, you can add enum support easily. Assume this is your enum:
namespace App\Enum; enum IncludeMembership : int { case NO = 0; case NEW_MEMBERS_ONLY = 1; case EXTEND_MEMBERSHIP = 2; case RENEW_MEMBERSHIP = 3; }
You can define the event.includeMembership field to use enums instead of integer values.
class Event extends \App\Record\Definition\Event { protected static array $virtualFields = [ 'includeMembership' => [\PHPFUI\ORM\Enum::class, \App\Enum\IncludeMembership::class], ]; }
Your code would now look like this:
if (\App\Enum\IncludeMembership::NEW_MEMBERS_ONLY == $event->includeMembership)
You can also set and save the enum directly:
$event->includeMembership = \App\Enum\IncludeMembership:NO; $event->update();
Enum assignments are type safe. Attempting to set the enum with an incorrect type will throw an exception.
- PHPFUI\ORM 9. Transactions
PHPFUI\ORM Transactions
While PHPFUI\ORM supports the traditional beginTransaction(), commit() and rollBack() on the PDO object, it is recommended you use the \PHPFUI\ORM\Transaction class.
$transaction = new \PHPFUI\ORM\Transaction(); // do some stuff if ($allGood) { $transaction->commit(); } else { $transaction->rollBack(); }
The above creates a transaction on the current database. Commit and rollback will also be called on the correct database even if you are working on another database at the time.
The main advantage of a Transaction object, it that will will rollback any changes on a thrown exception assuming the transaction object is properly scoped.
- PHPFUI\ORM 7. Validation
PHPFUI\ORM Validation
Note: Referenced namespaces in this document refer to the PHPFUI\ORM defaults.
Validator is an abstract class for \App\Record validation See \PHPFUI\ORM\Validator namespace for examples.
Individual validators are listed in the table below. Validators can be combined. For example, a field can be required, and have a minlength and maxlength. Validators can have parameters. Parameters are separated by a colon (:) and then commas for each separate parameter.
Usage
$record = new \App\Record\Example(); $record->setFrom($_POST); $validationErrors = $record->validate(); if (! validationErrors) { $insertedId = $record->insert(); }
$validationErrors is an array indexed by field name containing an array of translated errors.
foreach ($validationErrors as $field => $fieldErrors) { echo "Field {$field} has the following errors:\n"; foreach ($fieldErrors as $error) { echo $error . "\n"; } }
Validator Name Description Parameters alnum Numbers and characters only (ctype_alnum) None alpha Characters only (ctype_alpha) None bool Must be one or zero None card Credit card number (LUHN validation) None color HTML color (#fff or #fafbfc, '#' is optional) None contains Field must contain (case sensitive) comma separated list of strings cvv Credit card cvv number None date Loosely formatted date (Y-M-D) None dateISO Strictly formatted ISO Date (YYYY-MM-DD) None datetime Loosely formatted date (Y-M-D) followed by time format None day_month_year Loosely formatted date (D-M-Y) None domain Valid domain None email Valid email None ends_with Field must end with (case sensitive) comma separated list of strings enum MySQL enum value, case insensitive comma separated list of identifiersExample: enum:Get,Post,Put,Delete enum_exact MySQL enum value, case sensitive comma separated list of identifiersExample: enum:ssl,tls eq_field Equal to field field, required equal Value must be equal value, required gt_field Greater Than field field, required gte_field Greater Than or Equal to field field, required icontains Field must contain (case insensitive) comma separated list of strings iends_with Field must end with (case insensitive) comma separated list of strings integer Whole number, no fractional part None istarts_with Field must start with (case insensitive) comma separated list of strings lt_field Less Than field field, required lte_field Less Than or Equal to field field, required maxlength Length must be greater or equal Optional length, else MySQL limit maxvalue Value must be greater or equal value, required minlength Must be less than or equal number, default field size minvalue Must be less than or equal value, required month_day_year Loosely formatted date (M-D-Y) None month_year Loosely formatted Month Year None neq_field Not Equal to field field, required not_equal Value must not be equal value, required number Floating point number or whole number None required Field is required, can't be null or blank, 0 is OK None starts_with Field must start with (case sensitive) comma separated list of strings time Time (ampm or military), : separators None unique Column must be a unique value See Below url Valid URL (ftp, http, etc) None website Valid URL (http or https only) None year_month Loosely formatted Year Month None Field Comparison Validators
You can compare one field to another on the same \App\Record with the field validators.
- gt_field
- lt_field
- gte_field
- lte_field
- eq_field
- neq_field
Field validators take another field name as a parameter and perform the specified condition test. To compare against a specific value, use minvalue, maxvalue, equal or not_equal.
Unique Parameters
Without any parameters, the unique validator will make sure no other record has a matching value for the field being validated. The current record is always exempted from the unique test so it can be updated.
If there are parameters, the first parameter must be a field of the current record. If this is the only parameter, or if the next parameter is also a field of the record, then the unique test is only done with the value of this field set to the current record's value.
If the next parameter is not a field of the record, it is used as a value to match for the preceeding field for the unique test.
The above repeats until all parameters are exhausted.
Examples:
Suppose you have a table with the following fields:
- name
- company
- division
- type
You want the name to be unique per company: unique:company You want the name to be unique per division with in the company: unique:company,division You want the name to be unique for a specific type in the division: unique:type,shoes,division You want the name to be unique for a specific type and division: unique:type,shoes,division,10
NOT Operator
You can reverse any validator by preceding the validator with an ! (exclamation mark).
Example: !starts_with:/ will fail if the field starts with a /
OR Operator
You can validate a field if any one of the validators passes. Use the vertical bar (|) to separate validators. If one of the validators passes, then the the field is valid.
Example: website|starts_with:/ will validate a fully qualified http url, or a root relative url.
Optional Validation
You may need to do additional checks for a specific record type. A second parameter can be passed to the contructor which would represent the original values of the record.
You can also pass an optional method to validate to perform more complex validation. If you use an optional method, the validator will not perform the standard validations unless you specifically call the validate() method again without the optional method parameter.
Multi Validator Example
class Order extends \PHPFUI\ORM\Validator { /** @var array<string, string[]> */ public static array $validators = [ 'order_date' => ['required', 'maxlength', 'datetime', 'minvalue:2000-01-01', 'maxvalue:2099-12-31'], ]; }
- PHPFUI\ORM 2. Active Record
PHPFUI\ORM Active Record
Note: Referenced namespaces in this document refer to the PHPFUI\ORM defaults.
All database tables have a corresponding Record class in the \App\Record namespace named after the table name with an upper cased first letter.
An instance of an active record object represents on row of data in the corresponding table. All database fields are represented as publically accessible members of the active record object. For example:
$customer = new \App\Record\Customer(10); echo $customer->first_name . ' ' . $customer->last_name;
The above will read record 10 from the customer table and print the first and last name. You can also add methods to the \App\Record\Customer class to get or set common things. For example you could also use
$customer->fullName()
in the above example by adding the following to the Customer class:public function fullName() : string { return $this->first_name . ' ' . $this->last_name; }
This will update the customer record.
$customer->first_name = 'Fred'; $customer->update();
Record::__construct
A Record constructor attempts to read the specified row from the table. It can be constructed 4 ways:
- int primary key value, will load object values if the primary key value exists.
- string primary key value, will load object values if the primary key value exists.
- array record is attempted to be read from database using the values of the fields provided.
- \PHPFUI\ORM\DataObject record is constructed from an existing DataObject
- null (default) constructs an empty object.
Both int and string parameters to the constructor are type checked. Calling the constructor with a parameter can be see as the same as the following, but with type checking:
$customer = new \App\Record\Customer(); $customer->read($value);
The basic CRUD methods:
-
insert() or create()
- Adds the current record to the database. If the primary key already exists in the database, the insert fails. The auto increment primary key is updated with the value inserted.
- insert() returns the primary key value inserted, true if no primary key and the record was successfully inserted or false on error.
-
insertOrUpdate() or save()
- Will try to insert the record and on a duplicate key, will update the record with the current values.
- insertOrUpdate() returns the same values as insert().
- If the record only consists of primary keys, then this method is equivalent to insertOrIgnore().
-
insertOrIgnore()
- Will try to insert the record and on a duplicate key, will not update.
- insertOrIgnore() returns the same values as insert().
-
read(int | string | array $find)
- Will try to load the first record matching the values passed in. If $find is an array, each key is used as a where condition equal to the value.
- If not an array, read uses $find to search by primary key.
- read() returns true on success or false if no match found
-
update()
- Returns true if the record saved to the database.
-
delete()
- Deletes the record from the database. Defined child records are also deleted. You can overload delete() to do other custom work, like deleting an associated file if desired.
- delete() returns true on success
Other useful methods:
-
empty()
- Returns true if all current values are the defaults
-
loaded()
- Returns true if actually read from the database, rather than being created programitically.
-
reload()
- Gets the most recent version from the database and overwrites existing data.
-
setEmpty()
- Sets all the record values to defaults.
-
setFrom(array)
- Sets fields from the key / value array passed in.
Advanced methods:
-
clean()
- Can be overridden to perform actions before any write to the database.
-
setCustomValidator(string $className)
- Overrides the default validator with this class name.
-
validate(string $optionalMethod = '', ?self $originalRecord = NULL)
- Validates the record. You can pass an optional method to validate against and original record if required by the validation.
- Returns an array of errors indexed by field name. Empty array means the record has correctly validated.
Related Records
Related records are indicated by field name ending in the id suffix (default: 'Id'). The field name before the 'Id' must be the same as the corresponding table name. See See Virtual Fields for more advanced Related Records.
Accessing Related Records
You access the related record by the base field name (without the id suffix). The field with the id suffix is the primary key of the related record.
The following are all valid for the northwind database:
$orderDetail = new \App\Record\OrderDetail(40); echo $orderDetail->order->employee->company . "\n"; echo $orderDetail-product->list_price . "\n"; echo $orderDetail->purchase_order->supplier->company . "\n";
Null records will return the default value.
Since a related record is read from the database every time it is accessed, if you need to do more than one thing with the record, it is best of create a local copy and perform actions on the local copy to avoid multiple database reads.
$orderDetail = new \App\Record\OrderDetail(40); $supplier = $orderDetail->purchase_order->supplier; echo "Supplier Address:\n{$supplier->company}\nATTN: {$supplier->first_name} {$supplier->last_name}\n{$supplier->address}\n{$supplier->city} {$supplier->state} {$supplier->zip_postal_code}\n{$supplier->country_region}\n";
Setting Related Records
You can also set a related record. An (incomplete) example of creating a new order:
$customer = new \Tests\App\Record\Customer(); $customer->address = '123 Broadway'; $customer->business_phone = '212-987-6543'; $customer->city = 'New York'; $customer->company = 'PHPFUI'; $customer->country_region = 'USA'; $customer->fax_number = '212-345-6789'; $customer->home_phone = '987-654-3210'; $customer->job_title = 'Head Honcho'; $customer->mobile_phone = '123-456-7890'; $customer->state_province = 'NY'; $customer->web_page = 'http://www.phpfui.com'; $customer->zip_postal_code = '10021'; $order = new \Tests\App\Record\Order(); $order->employee_id = 9; $order->customer = $customer; $order->order_date = date("Y-m-d H:i:s"); $shipper = new \Tests\App\Record\Shipper(); $shipper->read(['company' => 'Shipping Company B']); $order->shipper = $shipper; $order->ship_name = $customer->company; $order->ship_address = $customer->address; $order->ship_city = $customer->city; $order->ship_state_province = $customer->state_province; $order->ship_zip_postal_code = $customer->zip_postal_code; $order->ship_country_region = $customer->country_region; $order->shipping_fee = 12.95; $order->taxes = 2.37; $order->payment_type = 'PO 54321'; $order->notes = 'Test Order'; $order->tax_rate = 5.25; $order->order_tax_status_id = 1; $order->order_status_id = 1; $order->insert();
Notice that we did not have to save the customer record. By assigning it to the order record, it was automatically saved to generate the required primary key value. The related record is not saved if it already has been assigned a primary key, it is your responsiblity to save it if you changed an existing record.
Alternate Way To Set Related Records
You can always just assign the id's directly:
$orderDetail->purchase_order_id = $purchase_order->purchase_order_id;
. Saving the OrderDetail record is up to you.Other Types Of Related Records
See Virtual Fields for information on how to impliment child or many to many relationships.
Multi Database Support
Related Records will always return a record from the currently selected database. Care must be taken when using multiple databases that any references to related records are done while the correct database instance is active. Cursors will continue to use the database in effect when they were created.
A future version of this libray may offer better multi database support.
- PHPFUI\PHPUnitSyntaxCoverage Readme
PHPUnitSyntaxCoverage
PHPUnit Extension for complete PHP Syntax Code Coverage
This package will checks for easy to miss syntax errors in all your PHP code. It will also check all your classes to see if they are loadable, but without actually instantiating the class.
Often we accidently check in code with easily detectable syntax errors, but unless the file or class is actually loaded by PHP, we might not see the error. Often the file or class is syntaxically correct, but a method signature may not match an updated class or vendor library. Normally this would only be detectable at run time, but with PHPUnitSyntaxCoverage, you can make sure all files and classes are checked.
PHPUnitSyntaxCoverage uses PhpParser to check for basic syntax errors. It then uses ReflectionClass to load any classes that are found in the source without instantiating them. This will find additional errors (such as missing or changed base classes from a package update).
Requirements
- Modern versions of PHP and PHPUnit
- Correctly configured autoloading
Installation
composer require phpfui/phpunit-syntax-coverage
Usage
Extend your unit tests from \PHPFUI\PHPUnitSyntaxCoverage\Extensions
class UnitTest extends \PHPFUI\PHPUnitSyntaxCoverage\Extensions { public function testProjectSyntax() { $this->addSkipDirectory(__DIR__ . '/../App/Examples'); $this->assertValidPHPDirectory(__DIR__ . '/../App', 'App directory has an error'); $this->assertValidPHPFile(__FILE__, 'Unit Test file not valid'); $this->assertValidPHP(' echo "hi";'); } }
You can use any of the following asserts:
- assertValidPHP(string $code, string $message = '')
- assertValidPHPDirectory(string $directory, string $message = '', bool $recurseSubdirectories = true, array $extensions = ['.php'])
- assertValidPHPFile(string $fileName, string $message = '')
Directory Testing
Instead of file by file testing, use assertValidPHPDirectory to test an entire directory. Any files added to the directory will be automatically tested.
$this->assertValidPHPDirectory(__DIR__ . '/../App', 'App directory error');
The error message will include the offending file name and line number.
Use addSkipDirectory to add simple case insensitive file matching to skip specific directories / files.
Autoloading
You must make sure autoloading is correctly configured for all classes. This means you can't pass references to classes that will not resolve correctly in your source. Use addSkipDirectory if you have test code that may not validate correctly.
Namespace Testing
The assertValidPHPFile and assertValidPHPDirectory asserts will test for the proper namespace in the file path (for PSR-0 autoloading and fully pathed PSR-4 autoloading), but you can turn off namespace testing with skipNamespaceTesting or exclude a specific namespace tests with addSkipNamespace.
PHP Version
While this library only supports currently supported versions of PHP, you can create a project and point it to PHP 5.2 or higher. The default is to prefer PHP 7 code, but to prefer or only parse PHP 5, configure phpunit.xml(.dist) with
<php> <env name="PHPFUI\PHPUnitSyntaxCoverage\Extensions_parser_type" value="X"/> </php>
Where X is one of the following numbers:
- Prefer PHP 7
- Prefer PHP 5
- Only PHP 7
- Only PHP 5
Examples
See examples
Full Class Documentation
License
PHPFUI is distributed under the MIT License.
- PHPFUI\Translation Readme
PHPFUI/Translation
A simple, fast, and memory efficient translation system for PHP.
Why another translation system? Simply for speed and reduced memory usage. Just under 500 lines of code including comments, this translation system does not have high overhead like other existing systems. Since PHP is an interpreted scripting language, speed and memory usage matter. This library attempts to solve both issues.
Supported Features
- Translations stored in native PHP arrays as key => value pairs.
- Chunked translations so only needed translations are loaded into memory.
- Supports third party Key Value store systems like memcached.
- Missing translation logging support for recording untranslated text.
- Parameter substitution using :name syntax using associative arrays.
- Pluralization including range support.
- Unlimited locales.
- invisible, TRANS, and RAW locales for debug support.
Chunked Translation Support
Chunks allow you to break up translations so all translations don't end up in memory at the same time. This is particularly useful for large projects.
Chunks are defined by any translation that starts with a period (.). These are chunked translations:
.save .cancel .messages.notFound .messages.saved .titles.firstName .titles.lastName .person.address.city .person.address.state .person.address.postalcode
The first two examples are called base chunks stored in the baseChunks.php file in the locale root directory. All base chunks are always loaded even if not requested, so they should only contain frequently used translations that would normally get loaded. The third through sixth chunks allow you to only load those chunks when required. If your page does not need titles, then the .titles chunk will not be loaded. The entire level of a chunk will be loaded at the same time. So in the last three examples, the .person.address chunk will load when .person.address.city (or state, or postalcode) is accessed.
Any translation that does not start with a period (.) is considered a native language translation and is stored in the native.php file in local root directory. All native chunks are loaded when any native chunk is loaded, except for the base locale, where they are never loaded as an optimization. This means a base locale native translation will never be listed as missing.
File Structure
You must specify a translation directory. Each locale you support will have its own directory within your translation directory. Consult the Tests/translations directory for examples.
Locale Support
Locales can be named anything your file system supports. The locale is the name of the directory in the base translation directory.
Generally you set the base locale, which is the language of your native translations (if you are using them). Then you set the user's locale. Those can be the same.
Reserved Locales for Debugging
- invisible - all translations return an empty string.
- TRANS - all translations return the literal string TRANS.
- RAW - returns the actual text passed in to be translated. No other processing is done on it.
- '' or empty string locale - not translated but fully processed for variable substitution and pluralization.
Fallback Locale Support
Too keep things as simple as possible, this library does not support falling back to a base locale if a translation is missing. This can be accomplished by preprocessing each locale. Any missing translation can be filled in with a translation from another locale. This library does not provide any support for managing translation files, as that is best left up to the developers if something custom is required. Use the var_export function to write translation files if you automate the process. This will insure your translations are parseable by PHP.
Usage
namespace PHPFUI\Translation; // specify where the translations are located Translator::setTranslationDirectory(__DIR__ . '/trans'); // set the base locale, ie. the language of any native translations (unchunked) Translator::setBaseLocale('EN-us'); // set the user's locale Translator::setLocale('ES-us'); // get the ES-us version of 'red' $translated = Translator::trans('red'); // get chunked version of colors. $translated = Translator::trans('.colors.red');
Parameters
You can pass parameters to substitute in the translation by passing an associate array as the second parameter:
// translate with parameters and pluralized $translated = Translator::trans('.messages.recordsFound', ['count' => $found]);
Parameters in the translations should start with a colon (:). A simple str_replace is used to translation the variables, so beware :name and :names will probably not be replaced in the way you might expect. It is best to use unique names where possible.
Pluralization
This library supports pluralization via different sections separated by the vertical bar (|) character. If you need '|' in your text use the HTML entity |
The count variable (:count in the translated text) is used for determining the number of items to pluralize for. It is not required to use :count in the translation, but you can if you desire.
- A count of 0 or less will select the first section.
- A count higher than the last section will return the last section.
- Other counts will select the number section, so 1 would return the 'one' section in the string 'zero|one|two'
- You can use the [first,last] notation at the start of a section to specify a matching range for the section.
- * is a wild card for matching any count. For example "[0]There are no brands|[1,9]There are under ten brands|[10,99]There are under 100 brands|[*]There are hundreds of brands"
- Combine with count parameter - "No records found|One record found|:count records found"
Full Class Documentation
License
PHPFUI/Translation is distributed under the MIT License.
PHP Versions
This library only supports modern versions of PHP which still receive security updates. While we would love to support PHP from the late Ming Dynasty, the advantages of modern PHP versions far out weigh quaint notions of backward compatibility. Time to upgrade.
- PHPMailer\PHPMailer Readme
PHPMailer – A full-featured email creation and transfer class for PHP
Features
- Probably the world's most popular code for sending email from PHP!
- Used by many open-source projects: WordPress, Drupal, 1CRM, SugarCRM, Yii, Joomla! and many more
- Integrated SMTP support – send without a local mail server
- Send emails with multiple To, CC, BCC, and Reply-to addresses
- Multipart/alternative emails for mail clients that do not read HTML email
- Add attachments, including inline
- Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings
- SMTP authentication with LOGIN, PLAIN, CRAM-MD5, and XOAUTH2 mechanisms over SMTPS and SMTP+STARTTLS transports
- Validates email addresses automatically
- Protects against header injection attacks
- Error messages in over 50 languages!
- DKIM and S/MIME signing support
- Compatible with PHP 5.5 and later, including PHP 8.2
- Namespaced to prevent name clashes
- Much more!
Why you might need it
Many PHP developers need to send email from their code. The only PHP function that supports this directly is
mail()
. However, it does not provide any assistance for making use of popular features such as encryption, authentication, HTML messages, and attachments.Formatting email correctly is surprisingly difficult. There are myriad overlapping (and conflicting) standards, requiring tight adherence to horribly complicated formatting and encoding rules – the vast majority of code that you'll find online that uses the
mail()
function directly is just plain wrong, if not unsafe!The PHP
mail()
function usually sends via a local mail server, typically fronted by asendmail
binary on Linux, BSD, and macOS platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP client allows email sending on all platforms without needing a local mail server. Be aware though, that themail()
function should be avoided when possible; it's both faster and safer to use SMTP to localhost.Please don't be tempted to do it yourself – if you don't use PHPMailer, there are many other excellent libraries that you should look at before rolling your own. Try SwiftMailer , Laminas/Mail, ZetaComponents, etc.
License
This software is distributed under the LGPL 2.1 license, along with the GPL Cooperation Commitment. Please read LICENSE for information on the software availability and distribution.
Installation & loading
PHPMailer is available on Packagist (using semantic versioning), and installation via Composer is the recommended way to install PHPMailer. Just add this line to your
composer.json
file:"phpmailer/phpmailer": "^6.9.2"
or run
composer require phpmailer/phpmailer
Note that the
vendor
folder and thevendor/autoload.php
script are generated by Composer; they are not part of PHPMailer.If you want to use XOAUTH2 authentication, you will also need to add a dependency on the
league/oauth2-client
and appropriate service adapters package in yourcomposer.json
, or take a look at by @decomplexity's SendOauth2 wrapper, especially if you're using Microsoft services.Alternatively, if you're not using Composer, you can download PHPMailer as a zip file, (note that docs and examples are not included in the zip file), then copy the contents of the PHPMailer folder into one of the
include_path
directories specified in your PHP configuration and load each class file manually:use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; require 'path/to/PHPMailer/src/Exception.php'; require 'path/to/PHPMailer/src/PHPMailer.php'; require 'path/to/PHPMailer/src/SMTP.php';
If you're not using the
SMTP
class explicitly (you're probably not), you don't need ause
line for the SMTP class. Even if you're not using exceptions, you do still need to load theException
class as it is used internally.Legacy versions
PHPMailer 5.2 (which is compatible with PHP 5.0 — 7.0) is no longer supported, even for security updates. You will find the latest version of 5.2 in the 5.2-stable branch. If you're using PHP 5.5 or later (which you should be), switch to the 6.x releases.
Upgrading from 5.2
The biggest changes are that source files are now in the
src/
folder, and PHPMailer now declares the namespacePHPMailer\PHPMailer
. This has several important effects – read the upgrade guide for more details.Minimal installation
While installing the entire package manually or with Composer is simple, convenient, and reliable, you may want to include only vital files in your project. At the very least you will need src/PHPMailer.php. If you're using SMTP, you'll need src/SMTP.php, and if you're using POP-before SMTP (very unlikely!), you'll need src/POP3.php. You can skip the language folder if you're not showing errors to users and can make do with English-only errors. If you're using XOAUTH2 you will need src/OAuth.php as well as the Composer dependencies for the services you wish to authenticate with. Really, it's much easier to use Composer!
A Simple Example
//Import PHPMailer classes into the global namespace //These must be at the top of your script, not inside a function use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\SMTP; use PHPMailer\PHPMailer\Exception; //Load Composer's autoloader require 'vendor/autoload.php'; //Create an instance; passing `true` enables exceptions $mail = new PHPMailer(true); try { //Server settings $mail->SMTPDebug = SMTP::DEBUG_SERVER; //Enable verbose debug output $mail->isSMTP(); //Send using SMTP $mail->Host = 'smtp.example.com'; //Set the SMTP server to send through $mail->SMTPAuth = true; //Enable SMTP authentication $mail->Username = 'user@example.com'; //SMTP username $mail->Password = 'secret'; //SMTP password $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption $mail->Port = 465; //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS` //Recipients $mail->setFrom('from@example.com', 'Mailer'); $mail->addAddress('joe@example.net', 'Joe User'); //Add a recipient $mail->addAddress('ellen@example.com'); //Name is optional $mail->addReplyTo('info@example.com', 'Information'); $mail->addCC('cc@example.com'); $mail->addBCC('bcc@example.com'); //Attachments $mail->addAttachment('/var/tmp/file.tar.gz'); //Add attachments $mail->addAttachment('/tmp/image.jpg', 'new.jpg'); //Optional name //Content $mail->isHTML(true); //Set email format to HTML $mail->Subject = 'Here is the subject'; $mail->Body = 'This is the HTML message body <b>in bold!</b>'; $mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; $mail->send(); echo 'Message has been sent'; } catch (Exception $e) { echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}"; }
You'll find plenty to play with in the examples folder, which covers many common scenarios including sending through Gmail, building contact forms, sending to mailing lists, and more.
If you are re-using the instance (e.g. when sending to a mailing list), you may need to clear the recipient list to avoid sending duplicate messages. See the mailing list example for further guidance.
That's it. You should now be ready to use PHPMailer!
Localization
PHPMailer defaults to English, but in the language folder, you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain ISO 639-1 language code for the translations, for example
fr
for French. To specify a language, you need to tell PHPMailer which one to use, like this://To load the French version $mail->setLanguage('fr', '/optional/path/to/language/directory/');
We welcome corrections and new languages – if you're looking for corrections, run the Language/TranslationCompletenessTest.php script in the tests folder and it will show any missing translations.
Documentation
Start reading at the GitHub wiki. If you're having trouble, head for the troubleshooting guide as it's frequently updated.
Examples of how to use PHPMailer for common scenarios can be found in the examples folder. If you're looking for a good starting point, we recommend you start with the Gmail example.
To reduce PHPMailer's deployed code footprint, examples are not included if you load PHPMailer via Composer or via GitHub's zip file download, so you'll need to either clone the git repository or use the above links to get to the examples directly.
Complete generated API documentation is available online.
You can generate complete API-level documentation by running
phpdoc
in the top-level folder, and documentation will appear in thedocs
folder, though you'll need to have PHPDocumentor installed. You may find the unit tests a good reference for how to do various operations such as encryption.If the documentation doesn't cover what you need, search the many questions on Stack Overflow, and before you ask a question about "SMTP Error: Could not connect to SMTP host.", read the troubleshooting guide.
Tests
PHPMailer tests use PHPUnit 9, with a polyfill to let 9-style tests run on older PHPUnit and PHP versions.
If this isn't passing, is there something you can do to help?
Security
Please disclose any vulnerabilities found responsibly – report security issues to the maintainers privately.
See SECURITY and PHPMailer's security advisories on GitHub.
Contributing
Please submit bug reports, suggestions, and pull requests to the GitHub issue tracker.
We're particularly interested in fixing edge cases, expanding test coverage, and updating translations.
If you found a mistake in the docs, or want to add something, go ahead and amend the wiki – anyone can edit it.
If you have git clones from prior to the move to the PHPMailer GitHub organisation, you'll need to update any remote URLs referencing the old GitHub location with a command like this from within your clone:
git remote set-url upstream https://github.com/PHPMailer/PHPMailer.git
Please don't use the SourceForge or Google Code projects any more; they are obsolete and no longer maintained.
Sponsorship
Development time and resources for PHPMailer are provided by Smartmessages.net, the world's only privacy-first email marketing system.
Donations are very welcome, whether in beer 🍺, T-shirts 👕, or cold, hard cash 💰. Sponsorship through GitHub is a simple and convenient way to say "thank you" to PHPMailer's maintainers and contributors – just click the "Sponsor" button on the project page. If your company uses PHPMailer, consider taking part in Tidelift's enterprise support programme.
PHPMailer For Enterprise
Available as part of the Tidelift Subscription.
The maintainers of PHPMailer and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open-source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. Learn more.
Changelog
See changelog.
History
- PHPMailer was originally written in 2001 by Brent R. Matzelle as a SourceForge project.
-
Marcus Bointon (
coolbru
on SF) and Andy Prevost (codeworxtech
) took over the project in 2004. - Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski.
- Marcus created his fork on GitHub in 2008.
- Jim and Marcus decide to join forces and use GitHub as the canonical and official repo for PHPMailer in 2013.
- PHPMailer moves to the PHPMailer organisation on GitHub in 2013.
What's changed since moving from SourceForge?
- Official successor to the SourceForge and Google Code projects.
- Test suite.
- Continuous integration with GitHub Actions.
- Composer support.
- Public development.
- Additional languages and language strings.
- CRAM-MD5 authentication support.
- Preserves full repo history of authors, commits, and branches from the original SourceForge project.
- PhpParser Readme
PHP Parser
This is a PHP parser written in PHP. Its purpose is to simplify static code analysis and manipulation.
Documentation for version 5.x (current; for running on PHP >= 7.4; for parsing PHP 7.0 to PHP 8.4, with limited support for parsing PHP 5.x).
Documentation for version 4.x (supported; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.3).
Features
The main features provided by this library are:
- Parsing PHP 7, and PHP 8 code into an abstract syntax tree (AST).
- Invalid code can be parsed into a partial AST.
- The AST contains accurate location information.
- Dumping the AST in human-readable form.
- Converting an AST back to PHP code.
- Formatting can be preserved for partially changed ASTs.
- Infrastructure to traverse and modify ASTs.
- Resolution of namespaced names.
- Evaluation of constant expressions.
- Builders to simplify AST construction for code generation.
- Converting an AST into JSON and back.
Quick Start
Install the library using composer:
php composer.phar require nikic/php-parser
Parse some PHP code into an AST and dump the result in human-readable form:
use PhpParser\Error; use PhpParser\NodeDumper; use PhpParser\ParserFactory; $code = <<<'CODE' function test($foo) { var_dump($foo); } CODE; $parser = (new ParserFactory())->createForNewestSupportedVersion(); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}\n"; return; } $dumper = new NodeDumper; echo $dumper->dump($ast) . "\n";
This dumps an AST looking something like this:
array( 0: Stmt_Function( attrGroups: array( ) byRef: false name: Identifier( name: test ) params: array( 0: Param( attrGroups: array( ) flags: 0 type: null byRef: false variadic: false var: Expr_Variable( name: foo ) default: null ) ) returnType: null stmts: array( 0: Stmt_Expression( expr: Expr_FuncCall( name: Name( name: var_dump ) args: array( 0: Arg( name: null value: Expr_Variable( name: foo ) byRef: false unpack: false ) ) ) ) ) ) )
Let's traverse the AST and perform some kind of modification. For example, drop all function bodies:
use PhpParser\Node; use PhpParser\Node\Stmt\Function_; use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; $traverser = new NodeTraverser(); $traverser->addVisitor(new class extends NodeVisitorAbstract { public function enterNode(Node $node) { if ($node instanceof Function_) { // Clean out the function body $node->stmts = []; } } }); $ast = $traverser->traverse($ast); echo $dumper->dump($ast) . "\n";
This gives us an AST where the
Function_::$stmts
are empty:array( 0: Stmt_Function( attrGroups: array( ) byRef: false name: Identifier( name: test ) params: array( 0: Param( attrGroups: array( ) type: null byRef: false variadic: false var: Expr_Variable( name: foo ) default: null ) ) returnType: null stmts: array( ) ) )
Finally, we can convert the new AST back to PHP code:
use PhpParser\PrettyPrinter; $prettyPrinter = new PrettyPrinter\Standard; echo $prettyPrinter->prettyPrintFile($ast);
This gives us our original code, minus the
var_dump()
call inside the function:function test($foo) { }
For a more comprehensive introduction, see the documentation.
Documentation
Component documentation:
-
Walking the AST
- Node visitors
- Modifying the AST from a visitor
- Short-circuiting traversals
- Interleaved visitors
- Simple node finding API
- Parent and sibling references
-
Name resolution
- Name resolver options
- Name resolution context
-
Pretty printing
- Converting AST back to PHP code
- Customizing formatting
- Formatting-preserving code transformations
-
AST builders
- Fluent builders for AST nodes
-
Lexer
- Emulation
- Tokens, positions and attributes
-
Error handling
- Column information for errors
- Error recovery (parsing of syntactically incorrect code)
-
Constant expression evaluation
- Evaluating constant/property/etc initializers
- Handling errors and unsupported expressions
-
JSON representation
- JSON encoding and decoding of ASTs
-
Performance
- Disabling Xdebug
- Reusing objects
- Garbage collection impact
-
Frequently asked questions
- Parent and sibling references
- Parsing PHP 7, and PHP 8 code into an abstract syntax tree (AST).
- Soundasleep Readme
html2text is a very simple script that uses DOM methods to convert HTML into a format similar to what would be rendered by a browser - perfect for places where you need a quick text representation. For example:
<html> <title>Ignored Title</title> <body> <h1>Hello, World!</h1> <p>This is some e-mail content. Even though it has whitespace and newlines, the e-mail converter will handle it correctly. <p>Even mismatched tags.</p> <div>A div</div> <div>Another div</div> <div>A div<div>within a div</div></div> <a href="http://foo.com">A link</a> </body> </html>
Will be converted into:
Hello, World! This is some e-mail content. Even though it has whitespace and newlines, the e-mail converter will handle it correctly. Even mismatched tags. A div Another div A div within a div [A link](http://foo.com)
See the original blog post or the related StackOverflow answer.
Installing
You can use Composer to add the package to your project:
{ "require": { "soundasleep/html2text": "~1.1" } }
And then use it quite simply:
$text = \Soundasleep\Html2Text::convert($html);
You can also include the supplied
html2text.php
and use$text = convert_html_to_text($html);
instead.Options
Option Default Description ignore_errors false
Set to true
to ignore any XML parsing errors.drop_links false
Set to true
to not render links as[http://foo.com](My Link)
, but rather justMy Link
.char_set 'auto'
Specify a specific character set. Pass multiple character sets (comma separated) to detect encoding, default is ASCII,UTF-8 Pass along options as a second argument to
convert
, for example:$options = array( 'ignore_errors' => true, // other options go here ); $text = \Soundasleep\Html2Text::convert($html, $options);
Tests
Some very basic tests are provided in the
tests/
directory. Run them withcomposer install && vendor/bin/phpunit
.Troubleshooting
Class 'DOMDocument' not found
You need to install the PHP XML extension for your PHP version. e.g.
apt-get install php7.4-xml
License
html2text
is licensed under MIT, making it suitable for both Eclipse and GPL projects.Other versions
Also see html2text_ruby, a Ruby implementation.
- Symfony\Component\Console Readme
Console Component
The Console component eases the creation of beautiful and testable command line interfaces.
Sponsor
Help Symfony by sponsoring its development!
Resources
Credits
Resources/bin/hiddeninput.exe
is a third party binary provided within this component. Find sources and license at https://github.com/Seldaek/hidden-input. - Symfony\Component\CssSelector Readme
CssSelector Component
The CssSelector component converts CSS selectors to XPath expressions.
Resources
Credits
This component is a port of the Python cssselect library v0.7.1, which is distributed under the BSD license.
- Symfony\Component\DependencyInjection Readme
DependencyInjection Component
The DependencyInjection component allows you to standardize and centralize the way objects are constructed in your application.
Resources
- Symfony\Component\Process Changelog
CHANGELOG
5.2.0
- added
Process::setOptions()
to setProcess
specific options - added option
create_new_console
to allow a subprocess to continue to run after the main script exited, both on Linux and on Windows
5.1.0
- added
Process::getStartTime()
to retrieve the start time of the process as float
5.0.0
- removed
Process::inheritEnvironmentVariables()
- removed
PhpProcess::setPhpBinary()
-
Process
must be instantiated with a command array, useProcess::fromShellCommandline()
when the command should be parsed by the shell - removed
Process::setCommandLine()
4.4.0
- deprecated
Process::inheritEnvironmentVariables()
: env variables are always inherited. - added
Process::getLastOutputTime()
method
4.2.0
- added the
Process::fromShellCommandline()
to run commands in a shell wrapper - deprecated passing a command as string when creating a
Process
instance - deprecated the
Process::setCommandline()
and thePhpProcess::setPhpBinary()
methods - added the
Process::waitUntil()
method to wait for the process only for a specific output, then continue the normal execution of your application
4.1.0
- added the
Process::isTtySupported()
method that allows to check for TTY support - made
PhpExecutableFinder
look for thePHP_BINARY
env var when searching the php binary - added the
ProcessSignaledException
class to properly catch signaled process errors
4.0.0
- environment variables will always be inherited
- added a second
array $env = []
argument to thestart()
,run()
,mustRun()
, andrestart()
methods of theProcess
class - added a second
array $env = []
argument to thestart()
method of thePhpProcess
class - the
ProcessUtils::escapeArgument()
method has been removed - the
areEnvironmentVariablesInherited()
,getOptions()
, andsetOptions()
methods of theProcess
class have been removed - support for passing
proc_open()
options has been removed - removed the
ProcessBuilder
class, use theProcess
class instead - removed the
getEnhanceWindowsCompatibility()
andsetEnhanceWindowsCompatibility()
methods of theProcess
class - passing a not existing working directory to the constructor of the
Symfony\Component\Process\Process
class is not supported anymore
3.4.0
- deprecated the ProcessBuilder class
- deprecated calling
Process::start()
without setting a valid working directory beforehand (viasetWorkingDirectory()
or constructor)
3.3.0
- added command line arrays in the
Process
class - added
$env
argument toProcess::start()
,run()
,mustRun()
andrestart()
methods - deprecated the
ProcessUtils::escapeArgument()
method - deprecated not inheriting environment variables
- deprecated configuring
proc_open()
options - deprecated configuring enhanced Windows compatibility
- deprecated configuring enhanced sigchild compatibility
2.5.0
- added support for PTY mode
- added the convenience method "mustRun"
- deprecation: Process::setStdin() is deprecated in favor of Process::setInput()
- deprecation: Process::getStdin() is deprecated in favor of Process::getInput()
- deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types
2.4.0
- added the ability to define an idle timeout
2.3.0
- added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows
- added Process::signal()
- added Process::getPid()
- added support for a TTY mode
2.2.0
- added ProcessBuilder::setArguments() to reset the arguments on a builder
- added a way to retrieve the standard and error output incrementally
- added Process:restart()
2.1.0
- added support for non-blocking processes (start(), wait(), isRunning(), stop())
- enhanced Windows compatibility
- added Process::getExitCodeText() that returns a string representation for the exit code returned by the process
- added ProcessBuilder
- added
- Symfony\Component\Process Readme
Process Component
The Process component executes commands in sub-processes.
Resources
- Symfony\Component\PropertyAccess Readme
PropertyAccess Component
The PropertyAccess component provides functions to read and write from/to an object or array using a simple string notation.
Resources
- Symfony\Component\PropertyInfo Readme
PropertyInfo Component
The PropertyInfo component extracts information about PHP class' properties using metadata of popular sources.
Resources
- Symfony\Component\Serializer Readme
Serializer Component
The Serializer component handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.
Resources
- Symfony\Component\String Readme
String Component
The String component provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way.
Resources
- Symfony\Component\TypeInfo Readme
TypeInfo Component
The TypeInfo component extracts PHP types information.
Getting Started
composer require symfony/type-info composer require phpstan/phpdoc-parser # to support raw string resolving
use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\TypeResolver\TypeResolver; // Instantiate a new resolver $typeResolver = TypeResolver::create(); // Then resolve types for any subject $typeResolver->resolve(new \ReflectionProperty(Dummy::class, 'id')); // returns an "int" Type instance $typeResolver->resolve('bool'); // returns a "bool" Type instance // Types can be instantiated thanks to static factories $type = Type::list(Type::nullable(Type::bool())); // Type instances have several helper methods $type->getBaseType(); // returns an "array" Type instance $type->getCollectionKeyType(); // returns an "int" Type instance $type->getCollectionValueType()->isNullable(); // returns true
Resources
- Symfony\Component\VarExporter Readme
VarExporter Component
The VarExporter component provides various tools to deal with the internal state of objects:
-
VarExporter::export()
allows exporting any serializable PHP data structure to plain PHP code. While doing so, it preserves all the semantics associated with the serialization mechanism of PHP (__wakeup
,__sleep
,Serializable
,__serialize
,__unserialize
); -
Instantiator::instantiate()
creates an object and sets its properties without calling its constructor nor any other methods; -
Hydrator::hydrate()
can set the properties of an existing object; -
Lazy*Trait
can make a class behave as a lazy-loading ghost or virtual proxy.
VarExporter::export()
The reason to use
VarExporter::export()
vsserialize()
or igbinary is performance: thanks to OPcache, the resulting code is significantly faster and more memory efficient than usingunserialize()
origbinary_unserialize()
.Unlike
var_export()
, this works on any serializable PHP value.It also provides a few improvements over
var_export()
/serialize()
:- the output is PSR-2 compatible;
- the output can be re-indented without messing up with
\r
or\n
in the data; - missing classes throw a
ClassNotFoundException
instead of being unserialized toPHP_Incomplete_Class
objects; - references involving
SplObjectStorage
,ArrayObject
orArrayIterator
instances are preserved; -
Reflection*
,IteratorIterator
andRecursiveIteratorIterator
classes throw an exception when being serialized (their unserialized version is broken anyway, see https://bugs.php.net/76737).
Instantiator and Hydrator
Instantiator::instantiate($class)
creates an object of the given class without calling its constructor nor any other methods.Hydrator::hydrate()
sets the properties of an existing object, including private and protected ones. For example:// Sets the public or protected $object->propertyName property Hydrator::hydrate($object, ['propertyName' => $propertyValue]); // Sets a private property defined on its parent Bar class: Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); // Alternative way to set the private $object->privateBarProperty property Hydrator::hydrate($object, [], [ Bar::class => ['privateBarProperty' => $propertyValue], ]);
Lazy*Trait
The component provides two lazy-loading patterns: ghost objects and virtual proxies (see https://martinfowler.com/eaaCatalog/lazyLoad.html for reference).
Ghost objects work only with concrete and non-internal classes. In the generic case, they are not compatible with using factories in their initializer.
Virtual proxies work with concrete, abstract or internal classes. They provide an API that looks like the actual objects and forward calls to them. They can cause identity problems because proxies might not be seen as equivalents to the actual objects they proxy.
Because of this identity problem, ghost objects should be preferred when possible. Exceptions thrown by the
ProxyHelper
class can help decide when it can be used or not.Ghost objects and virtual proxies both provide implementations for the
LazyObjectInterface
which allows resetting them to their initial state or to forcibly initialize them when needed. Note that resetting a ghost object skips its read-only properties. You should use a virtual proxy to reset read-only properties.LazyGhostTrait
By using
LazyGhostTrait
either directly in your classes or by usingProxyHelper::generateLazyGhost()
, you can make their instances lazy-loadable. This works by creating these instances empty and by computing their state only when accessing a property.class FooLazyGhost extends Foo { use LazyGhostTrait; } $foo = FooLazyGhost::createLazyGhost(initializer: function (Foo $instance): void { // [...] Use whatever heavy logic you need here // to compute the $dependencies of the $instance $instance->__construct(...$dependencies); // [...] Call setters, etc. if needed }); // $foo is now a lazy-loading ghost object. The initializer will // be called only when and if a *property* is accessed.
LazyProxyTrait
Alternatively,
LazyProxyTrait
can be used to create virtual proxies:$proxyCode = ProxyHelper::generateLazyProxy(new ReflectionClass(Foo::class)); // $proxyCode contains the reference to LazyProxyTrait // and should be dumped into a file in production envs eval('class FooLazyProxy'.$proxyCode); $foo = FooLazyProxy::createLazyProxy(initializer: function (): Foo { // [...] Use whatever heavy logic you need here // to compute the $dependencies of the $instance $instance = new Foo(...$dependencies); // [...] Call setters, etc. if needed return $instance; }); // $foo is now a lazy-loading virtual proxy object. The initializer will // be called only when and if a *method* is called.
Resources
-
- Symfony\Contracts\Service Readme
Symfony Service Contracts
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful and that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/main/README.md for more information.
- voku Readme
🔡 Portable ASCII
Description
It is written in PHP (PHP 7+) and can work without "mbstring", "iconv" or any other extra encoding php-extension on your server.
The benefit of Portable ASCII is that it is easy to use, easy to bundle.
The project based on ...
- Sean M. Burke's work (https://metacpan.org/pod/Text::Unidecode)
- Tomaz Solc's work (https://pypi.org/project/Unidecode/)
- Portable UTF-8 work (https://github.com/voku/portable-utf8)
- Daniel St. Jules's work (https://github.com/danielstjules/Stringy)
- Johnny Broadway's work (https://github.com/jbroadway/urlify)
- and many cherry-picks from "github"-gists and "Stack Overflow"-snippets ...
Index
- Alternative
- Install
- Why Portable ASCII?
- Requirements and Recommendations
- Usage
- Class methods
- Unit Test
- License and Copyright
Alternative
If you like a more Object Oriented Way to edit strings, then you can take a look at voku/Stringy, it's a fork of "danielstjules/Stringy" but it used the "Portable ASCII"-Class and some extra methods.
// Portable ASCII use voku\helper\ASCII; ASCII::to_transliterate('déjà σσς iıii'); // 'deja sss iiii' // voku/Stringy use Stringy\Stringy as S; $stringy = S::create('déjà σσς iıii'); $stringy->toTransliterate(); // 'deja sss iiii'
Install "Portable ASCII" via "composer require"
composer require voku/portable-ascii
Why Portable ASCII?
I need ASCII char handling in different classes and before I added this functions into "Portable UTF-8", but this repo is more modular and portable, because it has no dependencies.
Requirements and Recommendations
- No extensions are required to run this library. Portable ASCII only needs PCRE library that is available by default since PHP 4.2.0 and cannot be disabled since PHP 5.3.0. "\u" modifier support in PCRE for ASCII handling is not a must.
- PHP 7.0 is the minimum requirement
- PHP 8.0 is also supported
Usage
Example: ASCII::to_ascii()
echo ASCII::to_ascii('�Düsseldorf�', 'de'); // will output // Duesseldorf echo ASCII::to_ascii('�Düsseldorf�', 'en'); // will output // Dusseldorf
Portable ASCII | API
The API from the "ASCII"-Class is written as small static methods.
Class methods
charsArray(bool $replace_extra_symbols): array
↑ Returns an replacement array for ASCII methods.
EXAMPLE: $array = ASCII::charsArray(); var_dump($array['ru']['б']); // 'b'
Parameters:
-
bool $replace_extra_symbols [optional] <p>Add some more replacements e.g. "£" with " pound ".</p>
Return:
-
array
charsArrayWithMultiLanguageValues(bool $replace_extra_symbols): array
↑ Returns an replacement array for ASCII methods with a mix of multiple languages.
EXAMPLE: $array = ASCII::charsArrayWithMultiLanguageValues(); var_dump($array['b']); // ['β', 'б', 'ဗ', 'ბ', 'ب']
Parameters:
-
bool $replace_extra_symbols [optional] <p>Add some more replacements e.g. "£" with " pound ".</p>
Return:
-
array <p>An array of replacements.</p>
charsArrayWithOneLanguage(string $language, bool $replace_extra_symbols, bool $asOrigReplaceArray): array
↑ Returns an replacement array for ASCII methods with one language.
For example, German will map 'ä' to 'ae', while other languages will simply return e.g. 'a'.
EXAMPLE: $array = ASCII::charsArrayWithOneLanguage('ru'); $tmpKey = \array_search('yo', $array['replace']); echo $array['orig'][$tmpKey]; // 'ё'
Parameters:
-
ASCII::* $language [optional] <p>Language of the source string e.g.: en, de_at, or de-ch. (default is 'en') | ASCII::*_LANGUAGE_CODE</p>
-
bool $replace_extra_symbols [optional] <p>Add some more replacements e.g. "£" with " pound ".</p>
-
bool $asOrigReplaceArray [optional] <p>TRUE === return {orig: string[], replace: string[]} array</p>
Return:
-
array <p>An array of replacements.</p>
charsArrayWithSingleLanguageValues(bool $replace_extra_symbols, bool $asOrigReplaceArray): array
↑ Returns an replacement array for ASCII methods with multiple languages.
EXAMPLE: $array = ASCII::charsArrayWithSingleLanguageValues(); $tmpKey = \array_search('hnaik', $array['replace']); echo $array['orig'][$tmpKey]; // '၌'
Parameters:
-
bool $replace_extra_symbols [optional] <p>Add some more replacements e.g. "£" with " pound ".</p>
-
bool $asOrigReplaceArray [optional] <p>TRUE === return {orig: string[], replace: string[]} array</p>
Return:
-
array <p>An array of replacements.</p>
clean(string $str, bool $normalize_whitespace, bool $keep_non_breaking_space, bool $normalize_msword, bool $remove_invisible_characters): string
↑ Accepts a string and removes all non-UTF-8 characters from it + extras if needed.
Parameters:
-
string $str <p>The string to be sanitized.</p>
-
bool $normalize_whitespace [optional] <p>Set to true, if you need to normalize the whitespace.</p>
-
bool $keep_non_breaking_space [optional] <p>Set to true, to keep non-breaking-spaces, in combination with $normalize_whitespace</p>
-
bool $normalize_msword [optional] <p>Set to true, if you need to normalize MS Word chars e.g.: "…" => "..."</p>
-
bool $remove_invisible_characters [optional] <p>Set to false, if you not want to remove invisible characters e.g.: "\0"</p>
Return:
-
string <p>A clean UTF-8 string.</p>
getAllLanguages(): string[]
↑ Get all languages from the constants "ASCII::.*LANGUAGE_CODE".
Parameters: nothing
Return:
-
string[]
is_ascii(string $str): bool
↑ Checks if a string is 7 bit ASCII.
EXAMPLE: ASCII::is_ascii('白'); // false
Parameters:
-
string $str <p>The string to check.</p>
Return:
- `bool true if it is ASCII false otherwise
normalize_msword(string $str): string
↑ Returns a string with smart quotes, ellipsis characters, and dashes from Windows-1252 (commonly used in Word documents) replaced by their ASCII equivalents.
EXAMPLE: ASCII::normalize_msword('„Abcdef…”'); // '"Abcdef..."'
Parameters:
-
string $str <p>The string to be normalized.</p>
Return:
-
string <p>A string with normalized characters for commonly used chars in Word documents.</p>
normalize_whitespace(string $str, bool $keepNonBreakingSpace, bool $keepBidiUnicodeControls, bool $normalize_control_characters): string
↑ Normalize the whitespace.
EXAMPLE: ASCII::normalize_whitespace("abc-\xc2\xa0-öäü-\xe2\x80\xaf-\xE2\x80\xAC", true); // "abc-\xc2\xa0-öäü- -"
Parameters:
-
string $str <p>The string to be normalized.</p>
-
bool $keepNonBreakingSpace [optional] <p>Set to true, to keep non-breaking-spaces.</p>
-
bool $keepBidiUnicodeControls [optional] <p>Set to true, to keep non-printable (for the web) bidirectional text chars.</p>
-
bool $normalize_control_characters [optional] <p>Set to true, to convert e.g. LINE-, PARAGRAPH-SEPARATOR with "\n" and LINE TABULATION with "\t".</p>
Return:
-
string <p>A string with normalized whitespace.</p>
remove_invisible_characters(string $str, bool $url_encoded, string $replacement, bool $keep_basic_control_characters): string
↑ Remove invisible characters from a string.
e.g.: This prevents sandwiching null characters between ascii characters, like Java\0script.
copy&past from https://github.com/bcit-ci/CodeIgniter/blob/develop/system/core/Common.php
Parameters:
-
string $str
-
bool $url_encoded
-
string $replacement
-
bool $keep_basic_control_characters
Return:
-
string
to_ascii(string $str, string $language, bool $remove_unsupported_chars, bool $replace_extra_symbols, bool $use_transliterate, bool|null $replace_single_chars_only): string
↑ Returns an ASCII version of the string. A set of non-ASCII characters are replaced with their closest ASCII counterparts, and the rest are removed by default. The language or locale of the source string can be supplied for language-specific transliteration in any of the following formats: en, en_GB, or en-GB. For example, passing "de" results in "äöü" mapping to "aeoeue" rather than "aou" as in other languages.
EXAMPLE: ASCII::to_ascii('�Düsseldorf�', 'en'); // Dusseldorf
Parameters:
-
string $str <p>The input string.</p>
-
ASCII::* $language [optional] <p>Language of the source string. (default is 'en') | ASCII::*_LANGUAGE_CODE</p>
-
bool $remove_unsupported_chars [optional] <p>Whether or not to remove the unsupported characters.</p>
-
bool $replace_extra_symbols [optional] <p>Add some more replacements e.g. "£" with " pound ".</p>
-
bool $use_transliterate [optional] <p>Use ASCII::to_transliterate() for unknown chars.</p>
-
bool|null $replace_single_chars_only [optional] <p>Single char replacement is better for the performance, but some languages need to replace more then one char at the same time. | NULL === auto-setting, depended on the language</p>
Return:
-
string <p>A string that contains only ASCII characters.</p>
to_ascii_remap(string $str1, string $str2): string[]
↑ WARNING: This method will return broken characters and is only for special cases.
Convert two UTF-8 encoded string to a single-byte strings suitable for functions that need the same string length after the conversion.
The function simply uses (and updates) a tailored dynamic encoding (in/out map parameter) where non-ascii characters are remapped to the range [128-255] in order of appearance.
Parameters:
-
string $str1
-
string $str2
Return:
-
string[]
to_filename(string $str, bool $use_transliterate, string $fallback_char): string
↑ Convert given string to safe filename (and keep string case).
EXAMPLE: ASCII::to_filename('שדגשדג.png', true)); // 'shdgshdg.png'
Parameters:
-
string $str
-
bool $use_transliterate <p>ASCII::to_transliterate() is used by default - unsafe characters are simply replaced with hyphen otherwise.</p>
-
string $fallback_char
Return:
-
string <p>A string that contains only safe characters for a filename.</p>
to_slugify(string $str, string $separator, string $language, string[] $replacements, bool $replace_extra_symbols, bool $use_str_to_lower, bool $use_transliterate): string
↑ Converts the string into an URL slug. This includes replacing non-ASCII characters with their closest ASCII equivalents, removing remaining non-ASCII and non-alphanumeric characters, and replacing whitespace with $separator. The separator defaults to a single dash, and the string is also converted to lowercase. The language of the source string can also be supplied for language-specific transliteration.
Parameters:
-
string $str
-
string $separator [optional] <p>The string used to replace whitespace.</p>
-
ASCII::* $language [optional] <p>Language of the source string. (default is 'en') | ASCII::*_LANGUAGE_CODE</p>
-
array<string, string> $replacements [optional] <p>A map of replaceable strings.</p>
-
bool $replace_extra_symbols [optional] <p>Add some more replacements e.g. "£" with " pound ".</p>
-
bool $use_str_to_lower [optional] <p>Use "string to lower" for the input.</p>
-
bool $use_transliterate [optional] <p>Use ASCII::to_transliterate() for unknown chars.</p>
Return:
-
string <p>A string that has been converted to an URL slug.</p>
to_transliterate(string $str, string|null $unknown, bool $strict): string
↑ Returns an ASCII version of the string. A set of non-ASCII characters are replaced with their closest ASCII counterparts, and the rest are removed unless instructed otherwise.
EXAMPLE: ASCII::to_transliterate('déjà σσς iıii'); // 'deja sss iiii'
Parameters:
-
string $str <p>The input string.</p>
-
string|null $unknown [optional] <p>Character use if character unknown. (default is '?') But you can also use NULL to keep the unknown chars.</p>
-
bool $strict [optional] <p>Use "transliterator_transliterate()" from PHP-Intl
Return:
-
string <p>A String that contains only ASCII characters.</p>
Unit Test
- Composer is a prerequisite for running the tests.
composer install
- The tests can be executed by running this command from the root directory:
./vendor/bin/phpunit
Support
For support and donations please visit Github | Issues | PayPal | Patreon.
For status updates and release announcements please visit Releases | Twitter | Patreon.
For professional support please contact me.
Thanks
- Thanks to GitHub (Microsoft) for hosting the code and a good infrastructure including Issues-Managment, etc.
- Thanks to IntelliJ as they make the best IDEs for PHP and they gave me an open source license for PhpStorm!
- Thanks to Travis CI for being the most awesome, easiest continous integration tool out there!
- Thanks to StyleCI for the simple but powerful code style check.
- Thanks to PHPStan && Psalm for really great Static analysis tools and for discover bugs in the code!
License and Copyright
Released under the MIT License - see
LICENSE.txt
for details. - voku Summary
Table of content
- voku\helper Readme
:scroll: Simple Html Dom Parser for PHP
A HTML DOM parser written in PHP - let you manipulate HTML in a very easy way! This is a fork of PHP Simple HTML DOM Parser project but instead of string manipulation we use DOMDocument and modern php classes like "Symfony CssSelector".
- PHP 7.0+ & 8.0 Support
- PHP-FIG Standard
- Composer & PSR-4 support
- PHPUnit testing via Travis CI
- PHP-Quality testing via SensioLabsInsight
- UTF-8 Support (more support via "voku/portable-utf8")
- Invalid HTML Support (partly ...)
- Find tags on an HTML page with selectors just like jQuery
- Extract contents from HTML in a single line
Install via "composer require"
composer require voku/simple_html_dom composer require voku/portable-utf8 # if you need e.g. UTF-8 fixed output
Quick Start
use voku\helper\HtmlDomParser; require_once 'composer/autoload.php'; ... $dom = HtmlDomParser::str_get_html($str); // or $dom = HtmlDomParser::file_get_html($file); $element = $dom->findOne('#css-selector'); // "$element" === instance of "SimpleHtmlDomInterface" $elements = $dom->findMulti('.css-selector'); // "$elements" === instance of SimpleHtmlDomNodeInterface<int, SimpleHtmlDomInterface> $elementOrFalse = $dom->findOneOrFalse('#css-selector'); // "$elementOrFalse" === instance of "SimpleHtmlDomInterface" or false $elementsOrFalse = $dom->findMultiOrFalse('.css-selector'); // "$elementsOrFalse" === instance of SimpleHtmlDomNodeInterface<int, SimpleHtmlDomInterface> or false ...
Examples
github.com/voku/simple_html_dom/tree/master/example
API
github.com/voku/simple_html_dom/tree/master/README_API.md
Support
For support and donations please visit Github | Issues | PayPal | Patreon.
For status updates and release announcements please visit Releases | Twitter | Patreon.
For professional support please contact me.
Thanks
- Thanks to GitHub (Microsoft) for hosting the code and a good infrastructure including Issues-Managment, etc.
- Thanks to IntelliJ as they make the best IDEs for PHP and they gave me an open source license for PhpStorm!
- Thanks to Travis CI for being the most awesome, easiest continous integration tool out there!
- Thanks to StyleCI for the simple but powerfull code style check.
- Thanks to PHPStan && Psalm for relly great Static analysis tools and for discover bugs in the code!
License
- voku\helper Readme api
:scroll: Simple Html Dom Parser for PHP
DomParser API
SimpleHtmlDomNode (group of dom elements) API
SimpleHtmlDom (single dom element) API
find(string $selector, int|null $idx): mixed
↑ Find list of nodes with a CSS selector.
Parameters:
-
string $selector
-
int|null $idx
Return:
-
mixed
findMulti(string $selector): mixed
↑ Find nodes with a CSS selector.
Parameters:
-
string $selector
Return:
-
mixed
findMultiOrFalse(string $selector): mixed
↑ Find nodes with a CSS selector or false, if no element is found.
Parameters:
-
string $selector
Return:
-
mixed
findOne(string $selector): static
↑ Find one node with a CSS selector.
Parameters:
-
string $selector
Return:
-
static
findOneOrFalse(string $selector): mixed
↑ Find one node with a CSS selector or false, if no element is found.
Parameters:
-
string $selector
Return:
-
mixed
fixHtmlOutput(string $content, bool $multiDecodeNewHtmlEntity): string
↑
Parameters:
-
string $content
-
bool $multiDecodeNewHtmlEntity
Return:
-
string
getDocument(): DOMDocument
↑
Parameters: nothing
Return:
-
\DOMDocument
getElementByClass(string $class): mixed
↑ Return elements by ".class".
Parameters:
-
string $class
Return:
-
mixed
getElementById(string $id): mixed
↑ Return element by #id.
Parameters:
-
string $id
Return:
-
mixed
getElementByTagName(string $name): mixed
↑ Return element by tag name.
Parameters:
-
string $name
Return:
-
mixed
getElementsById(string $id, int|null $idx): mixed
↑ Returns elements by "#id".
Parameters:
-
string $id
-
int|null $idx
Return:
-
mixed
getElementsByTagName(string $name, int|null $idx): mixed
↑ Returns elements by tag name.
Parameters:
-
string $name
-
int|null $idx
Return:
-
mixed
html(bool $multiDecodeNewHtmlEntity): string
↑ Get dom node's outer html.
Parameters:
-
bool $multiDecodeNewHtmlEntity
Return:
-
string
innerHtml(bool $multiDecodeNewHtmlEntity): string
↑ Get dom node's inner html.
Parameters:
-
bool $multiDecodeNewHtmlEntity
Return:
-
string
innerXml(bool $multiDecodeNewHtmlEntity): string
↑ Get dom node's inner xml.
Parameters:
-
bool $multiDecodeNewHtmlEntity
Return:
-
string
loadHtml(string $html, int|null $libXMLExtraOptions): DomParserInterface
↑ Load HTML from string.
Parameters:
-
string $html
-
int|null $libXMLExtraOptions
Return:
-
\DomParserInterface
loadHtmlFile(string $filePath, int|null $libXMLExtraOptions): DomParserInterface
↑ Load HTML from file.
Parameters:
-
string $filePath
-
int|null $libXMLExtraOptions
Return:
-
\DomParserInterface
save(string $filepath): string
↑ Save the html-dom as string.
Parameters:
-
string $filepath
Return:
-
string
set_callback(callable $functionName): mixed
↑
Parameters:
-
callable $functionName
Return:
-
mixed
text(bool $multiDecodeNewHtmlEntity): string
↑ Get dom node's plain text.
Parameters:
-
bool $multiDecodeNewHtmlEntity
Return:
-
string
xml(bool $multiDecodeNewHtmlEntity, bool $htmlToXml, bool $removeXmlHeader, int $options): string
↑ Get the HTML as XML or plain XML if needed.
Parameters:
-
bool $multiDecodeNewHtmlEntity
-
bool $htmlToXml
-
bool $removeXmlHeader
-
int $options
Return:
-
string
count(): int
↑ Get the number of items in this dom node.
Parameters: nothing
Return:
-
int
find(string $selector, int $idx): SimpleHtmlDomNode|\SimpleHtmlDomNode[]|null
↑ Find list of nodes with a CSS selector.
Parameters:
-
string $selector
-
int $idx
Return:
-
\SimpleHtmlDomNode|\SimpleHtmlDomNode[]|null
findMulti(string $selector): SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
↑ Find nodes with a CSS selector.
Parameters:
-
string $selector
Return:
-
\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
findMultiOrFalse(string $selector): false|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
↑ Find nodes with a CSS selector or false, if no element is found.
Parameters:
-
string $selector
Return:
-
false|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
findOne(string $selector): SimpleHtmlDomNode|null
↑ Find one node with a CSS selector.
Parameters:
-
string $selector
Return:
-
\SimpleHtmlDomNode|null
findOneOrFalse(string $selector): false|\SimpleHtmlDomNode
↑ Find one node with a CSS selector or false, if no element is found.
Parameters:
-
string $selector
Return:
-
false|\SimpleHtmlDomNode
innerHtml(): string[]
↑ Get html of elements.
Parameters: nothing
Return:
-
string[]
innertext(): string[]
↑ alias for "$this->innerHtml()" (added for compatibly-reasons with v1.x)
Parameters: nothing
Return:
-
string[]
outertext(): string[]
↑ alias for "$this->innerHtml()" (added for compatibly-reasons with v1.x)
Parameters: nothing
Return:
-
string[]
text(): string[]
↑ Get plain text.
Parameters: nothing
Return:
-
string[]
childNodes(int $idx): SimpleHtmlDomInterface|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface|null
↑ Returns children of node.
Parameters:
-
int $idx
Return:
-
\SimpleHtmlDomInterface|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface|null
delete(): mixed
↑ Delete
Parameters: nothing
Return:
-
mixed
find(string $selector, int|null $idx): SimpleHtmlDomInterface|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
↑ Find list of nodes with a CSS selector.
Parameters:
-
string $selector
-
int|null $idx
Return:
-
\SimpleHtmlDomInterface|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
findMulti(string $selector): SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
↑ Find nodes with a CSS selector.
Parameters:
-
string $selector
Return:
-
\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
findMultiOrFalse(string $selector): false|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
↑ Find nodes with a CSS selector or false, if no element is found.
Parameters:
-
string $selector
Return:
-
false|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
findOne(string $selector): SimpleHtmlDomInterface
↑ Find one node with a CSS selector.
Parameters:
-
string $selector
Return:
-
\SimpleHtmlDomInterface
findOneOrFalse(string $selector): false|\SimpleHtmlDomInterface
↑ Find one node with a CSS selector or false, if no element is found.
Parameters:
-
string $selector
Return:
-
false|\SimpleHtmlDomInterface
firstChild(): SimpleHtmlDomInterface|null
↑ Returns the first child of node.
Parameters: nothing
Return:
-
\SimpleHtmlDomInterface|null
getAllAttributes(): string[]|null
↑ Returns an array of attributes.
Parameters: nothing
Return:
-
string[]|null
getAttribute(string $name): string
↑ Return attribute value.
Parameters:
-
string $name
Return:
-
string
getElementByClass(string $class): SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
↑ Return elements by ".class".
Parameters:
-
string $class
Return:
-
\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
getElementById(string $id): SimpleHtmlDomInterface
↑ Return element by "#id".
Parameters:
-
string $id
Return:
-
\SimpleHtmlDomInterface
getElementByTagName(string $name): SimpleHtmlDomInterface
↑ Return element by tag name.
Parameters:
-
string $name
Return:
-
\SimpleHtmlDomInterface
getElementsById(string $id, int|null $idx): SimpleHtmlDomInterface|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
↑ Returns elements by "#id".
Parameters:
-
string $id
-
int|null $idx
Return:
-
\SimpleHtmlDomInterface|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
getElementsByTagName(string $name, int|null $idx): SimpleHtmlDomInterface|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
↑ Returns elements by tag name.
Parameters:
-
string $name
-
int|null $idx
Return:
-
\SimpleHtmlDomInterface|\SimpleHtmlDomInterface[]|\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
getHtmlDomParser(): HtmlDomParser
↑ Create a new "HtmlDomParser"-object from the current context.
Parameters: nothing
Return:
-
\HtmlDomParser
getIterator(): SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface>
↑ Retrieve an external iterator.
Parameters: nothing
Return:
- `\SimpleHtmlDomNodeInterface<\SimpleHtmlDomInterface> An instance of an object implementing Iterator or Traversable
getNode(): DOMNode
↑
Parameters: nothing
Return:
-
\DOMNode
getTag(): string
↑ Return the tag of node
Parameters: nothing
Return:
-
string
hasAttribute(string $name): bool
↑ Determine if an attribute exists on the element.
Parameters:
-
string $name
Return:
-
bool
html(bool $multiDecodeNewHtmlEntity): string
↑ Get dom node's outer html.
Parameters:
-
bool $multiDecodeNewHtmlEntity
Return:
-
string
innerHtml(bool $multiDecodeNewHtmlEntity): string
↑ Get dom node's inner html.
Parameters:
-
bool $multiDecodeNewHtmlEntity
Return:
-
string
innerXml(bool $multiDecodeNewHtmlEntity): string
↑ Get dom node's inner html.
Parameters:
-
bool $multiDecodeNewHtmlEntity
Return:
-
string
isRemoved(): bool
↑ Nodes can get partially destroyed in which they're still an actual DOM node (such as \DOMElement) but almost their entire body is gone, including the
nodeType
attribute.Parameters: nothing
Return:
-
bool true if node has been destroyed
lastChild(): SimpleHtmlDomInterface|null
↑ Returns the last child of node.
Parameters: nothing
Return:
-
\SimpleHtmlDomInterface|null
nextNonWhitespaceSibling(): SimpleHtmlDomInterface|null
↑ Returns the next sibling of node, and it will ignore whitespace elements.
Parameters: nothing
Return:
-
\SimpleHtmlDomInterface|null
nextSibling(): SimpleHtmlDomInterface|null
↑ Returns the next sibling of node.
Parameters: nothing
Return:
-
\SimpleHtmlDomInterface|null
parentNode(): SimpleHtmlDomInterface
↑ Returns the parent of node.
Parameters: nothing
Return:
-
\SimpleHtmlDomInterface
previousNonWhitespaceSibling(): SimpleHtmlDomInterface|null
↑ Returns the previous sibling of node, and it will ignore whitespace elements.
Parameters: nothing
Return:
-
\SimpleHtmlDomInterface|null
previousSibling(): SimpleHtmlDomInterface|null
↑ Returns the previous sibling of node.
Parameters: nothing
Return:
-
\SimpleHtmlDomInterface|null
removeAttribute(string $name): SimpleHtmlDomInterface
↑ Remove attribute.
Parameters:
-
string $name <p>The name of the html-attribute.</p>
Return:
-
\SimpleHtmlDomInterface
removeAttributes(): SimpleHtmlDomInterface
↑ Remove all attributes
Parameters: nothing
Return:
-
\SimpleHtmlDomInterface
setAttribute(string $name, string|null $value, bool $strictEmptyValueCheck): SimpleHtmlDomInterface
↑ Set attribute value.
Parameters:
-
string $name <p>The name of the html-attribute.</p>
-
string|null $value <p>Set to NULL or empty string, to remove the attribute.</p>
- `bool $strictEmptyValueCheck $value must be NULL, to remove the attribute, so that you can set an empty string as attribute-value e.g. autofocus=""
Return:
-
\SimpleHtmlDomInterface
text(): string
↑ Get dom node's plain text.
Parameters: nothing
Return:
-
string
val(string|string[]|null $value): string|string[]|null
↑
Parameters:
- `string|string[]|null $value null === get the current input value text === set a new input value
Return:
-
string|string[]|null
-
- Webmozart\Assert Readme
Webmozart Assert
This library contains efficient assertions to test the input and output of your methods. With these assertions, you can greatly reduce the amount of coding needed to write a safe implementation.
All assertions in the
Assert
class throw anWebmozart\Assert\InvalidArgumentException
if they fail.FAQ
What's the difference to beberlei/assert?
This library is heavily inspired by Benjamin Eberlei's wonderful assert package, but fixes a usability issue with error messages that can't be fixed there without breaking backwards compatibility.
This package features usable error messages by default. However, you can also easily write custom error messages:
Assert::string($path, 'The path is expected to be a string. Got: %s');
In beberlei/assert, the ordering of the
%s
placeholders is different for every assertion. This package, on the contrary, provides consistent placeholder ordering for all assertions:-
%s
: The tested value as string, e.g."/foo/bar"
. -
%2$s
,%3$s
, ...: Additional assertion-specific values, e.g. the minimum/maximum length, allowed values, etc.
Check the source code of the assertions to find out details about the additional available placeholders.
Installation
Use Composer to install the package:
composer require webmozart/assert
Example
use Webmozart\Assert\Assert; class Employee { public function __construct($id) { Assert::integer($id, 'The employee ID must be an integer. Got: %s'); Assert::greaterThan($id, 0, 'The employee ID must be a positive integer. Got: %s'); } }
If you create an employee with an invalid ID, an exception is thrown:
new Employee('foobar'); // => Webmozart\Assert\InvalidArgumentException: // The employee ID must be an integer. Got: string new Employee(-10); // => Webmozart\Assert\InvalidArgumentException: // The employee ID must be a positive integer. Got: -10
Assertions
The
Assert
class provides the following assertions:Type Assertions
Method Description string($value, $message = '')
Check that a value is a string stringNotEmpty($value, $message = '')
Check that a value is a non-empty string integer($value, $message = '')
Check that a value is an integer integerish($value, $message = '')
Check that a value casts to an integer positiveInteger($value, $message = '')
Check that a value is a positive (non-zero) integer float($value, $message = '')
Check that a value is a float numeric($value, $message = '')
Check that a value is numeric natural($value, $message= ''')
Check that a value is a non-negative integer boolean($value, $message = '')
Check that a value is a boolean scalar($value, $message = '')
Check that a value is a scalar object($value, $message = '')
Check that a value is an object resource($value, $type = null, $message = '')
Check that a value is a resource isCallable($value, $message = '')
Check that a value is a callable isArray($value, $message = '')
Check that a value is an array isTraversable($value, $message = '')
(deprecated)Check that a value is an array or a \Traversable
isIterable($value, $message = '')
Check that a value is an array or a \Traversable
isCountable($value, $message = '')
Check that a value is an array or a \Countable
isInstanceOf($value, $class, $message = '')
Check that a value is an instanceof
a classisInstanceOfAny($value, array $classes, $message = '')
Check that a value is an instanceof
at least one class on the array of classesnotInstanceOf($value, $class, $message = '')
Check that a value is not an instanceof
a classisAOf($value, $class, $message = '')
Check that a value is of the class or has one of its parents isAnyOf($value, array $classes, $message = '')
Check that a value is of at least one of the classes or has one of its parents isNotA($value, $class, $message = '')
Check that a value is not of the class or has not one of its parents isArrayAccessible($value, $message = '')
Check that a value can be accessed as an array uniqueValues($values, $message = '')
Check that the given array contains unique values Comparison Assertions
Method Description true($value, $message = '')
Check that a value is true
false($value, $message = '')
Check that a value is false
notFalse($value, $message = '')
Check that a value is not false
null($value, $message = '')
Check that a value is null
notNull($value, $message = '')
Check that a value is not null
isEmpty($value, $message = '')
Check that a value is empty()
notEmpty($value, $message = '')
Check that a value is not empty()
eq($value, $value2, $message = '')
Check that a value equals another ( ==
)notEq($value, $value2, $message = '')
Check that a value does not equal another ( !=
)same($value, $value2, $message = '')
Check that a value is identical to another ( ===
)notSame($value, $value2, $message = '')
Check that a value is not identical to another ( !==
)greaterThan($value, $value2, $message = '')
Check that a value is greater than another greaterThanEq($value, $value2, $message = '')
Check that a value is greater than or equal to another lessThan($value, $value2, $message = '')
Check that a value is less than another lessThanEq($value, $value2, $message = '')
Check that a value is less than or equal to another range($value, $min, $max, $message = '')
Check that a value is within a range inArray($value, array $values, $message = '')
Check that a value is one of a list of values oneOf($value, array $values, $message = '')
Check that a value is one of a list of values (alias of inArray
)String Assertions
You should check that a value is a string with
Assert::string()
before making any of the following assertions.Method Description contains($value, $subString, $message = '')
Check that a string contains a substring notContains($value, $subString, $message = '')
Check that a string does not contain a substring startsWith($value, $prefix, $message = '')
Check that a string has a prefix notStartsWith($value, $prefix, $message = '')
Check that a string does not have a prefix startsWithLetter($value, $message = '')
Check that a string starts with a letter endsWith($value, $suffix, $message = '')
Check that a string has a suffix notEndsWith($value, $suffix, $message = '')
Check that a string does not have a suffix regex($value, $pattern, $message = '')
Check that a string matches a regular expression notRegex($value, $pattern, $message = '')
Check that a string does not match a regular expression unicodeLetters($value, $message = '')
Check that a string contains Unicode letters only alpha($value, $message = '')
Check that a string contains letters only digits($value, $message = '')
Check that a string contains digits only alnum($value, $message = '')
Check that a string contains letters and digits only lower($value, $message = '')
Check that a string contains lowercase characters only upper($value, $message = '')
Check that a string contains uppercase characters only length($value, $length, $message = '')
Check that a string has a certain number of characters minLength($value, $min, $message = '')
Check that a string has at least a certain number of characters maxLength($value, $max, $message = '')
Check that a string has at most a certain number of characters lengthBetween($value, $min, $max, $message = '')
Check that a string has a length in the given range uuid($value, $message = '')
Check that a string is a valid UUID ip($value, $message = '')
Check that a string is a valid IP (either IPv4 or IPv6) ipv4($value, $message = '')
Check that a string is a valid IPv4 ipv6($value, $message = '')
Check that a string is a valid IPv6 email($value, $message = '')
Check that a string is a valid e-mail address notWhitespaceOnly($value, $message = '')
Check that a string contains at least one non-whitespace character File Assertions
Method Description fileExists($value, $message = '')
Check that a value is an existing path file($value, $message = '')
Check that a value is an existing file directory($value, $message = '')
Check that a value is an existing directory readable($value, $message = '')
Check that a value is a readable path writable($value, $message = '')
Check that a value is a writable path Object Assertions
Method Description classExists($value, $message = '')
Check that a value is an existing class name subclassOf($value, $class, $message = '')
Check that a class is a subclass of another interfaceExists($value, $message = '')
Check that a value is an existing interface name implementsInterface($value, $class, $message = '')
Check that a class implements an interface propertyExists($value, $property, $message = '')
Check that a property exists in a class/object propertyNotExists($value, $property, $message = '')
Check that a property does not exist in a class/object methodExists($value, $method, $message = '')
Check that a method exists in a class/object methodNotExists($value, $method, $message = '')
Check that a method does not exist in a class/object Array Assertions
Method Description keyExists($array, $key, $message = '')
Check that a key exists in an array keyNotExists($array, $key, $message = '')
Check that a key does not exist in an array validArrayKey($key, $message = '')
Check that a value is a valid array key (int or string) count($array, $number, $message = '')
Check that an array contains a specific number of elements minCount($array, $min, $message = '')
Check that an array contains at least a certain number of elements maxCount($array, $max, $message = '')
Check that an array contains at most a certain number of elements countBetween($array, $min, $max, $message = '')
Check that an array has a count in the given range isList($array, $message = '')
Check that an array is a non-associative list isNonEmptyList($array, $message = '')
Check that an array is a non-associative list, and not empty isMap($array, $message = '')
Check that an array is associative and has strings as keys isNonEmptyMap($array, $message = '')
Check that an array is associative and has strings as keys, and is not empty Function Assertions
Method Description throws($closure, $class, $message = '')
Check that a function throws a certain exception. Subclasses of the exception class will be accepted. Collection Assertions
All of the above assertions can be prefixed with
all*()
to test the contents of an array or a\Traversable
:Assert::allIsInstanceOf($employees, 'Acme\Employee');
Nullable Assertions
All of the above assertions can be prefixed with
nullOr*()
to run the assertion only if it the value is notnull
:Assert::nullOrString($middleName, 'The middle name must be a string or null. Got: %s');
Extending Assert
The
Assert
class comes with a few methods, which can be overridden to change the class behaviour. You can also extend it to add your own assertions.Overriding methods
Overriding the following methods in your assertion class allows you to change the behaviour of the assertions:
-
public static function __callStatic($name, $arguments)
- This method is used to 'create' the
nullOr
andall
versions of the assertions.
- This method is used to 'create' the
-
protected static function valueToString($value)
- This method is used for error messages, to convert the value to a string value for displaying. You could use this for representing a value object with a
__toString
method for example.
- This method is used for error messages, to convert the value to a string value for displaying. You could use this for representing a value object with a
-
protected static function typeToString($value)
- This method is used for error messages, to convert the a value to a string representing its type.
-
protected static function strlen($value)
- This method is used to calculate string length for relevant methods, using the
mb_strlen
if available and useful.
- This method is used to calculate string length for relevant methods, using the
-
protected static function reportInvalidArgument($message)
- This method is called when an assertion fails, with the specified error message. Here you can throw your own exception, or log something.
Static analysis support
Where applicable, assertion functions are annotated to support Psalm's Assertion syntax. A dedicated PHPStan Plugin is required for proper type support.
Authors
Contribute
Contributions to the package are always welcome!
- Report any bugs or issues you find on the issue tracker.
- You can grab the source code at the package's Git repository.
License
All contents of this package are licensed under the MIT license.
-
- ZBateson\MailMimeParser Readme
zbateson/mail-mime-parser
Testable and PSR-compliant mail mime parser alternative to PHP's imap* functions and Pear libraries for reading messages in Internet Message Format RFC 822 (and later revisions RFC 2822, RFC 5322).
The goals of this project are to be:
- Well written
- Standards-compliant but forgiving
- Tested where possible
To include it for use in your project, install it via composer:
composer require zbateson/mail-mime-parser
Sponsors
A huge thank you to all my sponsors. <3
If this project's helped you, please consider sponsoring me.
Php 7 Support Dropped
As of mail-mime-parser 3.0, support for php 7 has been dropped.
New in 3.0
Most changes in 3.0 are 'backend' changes, for example switching to PHP-DI for dependency injection, and basic usage should not be affected.
The header class method 'getAllParts' includes comment parts in 3.0.
Error, validation, and logging support has been added.
For a more complete list of changes, please visit the 3.0 Upgrade Guide and the Usage Guide.
Requirements
MailMimeParser requires PHP 8.0 or newer. Tested on PHP 8.0, 8.1, 8.2 and 8.3.
Usage
use ZBateson\MailMimeParser\MailMimeParser; use ZBateson\MailMimeParser\Message; use ZBateson\MailMimeParser\Header\HeaderConsts; // use an instance of MailMimeParser as a class dependency $mailParser = new MailMimeParser(); // parse() accepts a string, resource or Psr7 StreamInterface // pass `true` as the second argument to attach the passed $handle and close // it when the returned IMessage is destroyed. $handle = fopen('file.mime', 'r'); $message = $mailParser->parse($handle, false); // returns `IMessage` // OR: use this procedurally (Message::from also accepts a string, // resource or Psr7 StreamInterface // true or false as second parameter doesn't matter if passing a string. $string = "Content-Type: text/plain\r\nSubject: Test\r\n\r\nMessage"; $message = Message::from($string, false); echo $message->getHeaderValue(HeaderConsts::FROM); // user@example.com echo $message ->getHeader(HeaderConsts::FROM) // AddressHeader ->getPersonName(); // Person Name echo $message->getSubject(); // The email's subject echo $message ->getHeader(HeaderConsts::TO) // also AddressHeader ->getAddresses()[0] // AddressPart ->getPersonName(); // Person Name echo $message ->getHeader(HeaderConsts::CC) // also AddressHeader ->getAddresses()[0] // AddressPart ->getEmail(); // user@example.com echo $message->getTextContent(); // or getHtmlContent() echo $message->getHeader('X-Foo'); // for custom or undocumented headers $att = $message->getAttachmentPart(0); // first attachment echo $att->getHeaderValue(HeaderConsts::CONTENT_TYPE); // e.g. "text/plain" echo $att->getHeaderParameter( // value of "charset" part 'content-type', 'charset' ); echo $att->getContent(); // get the attached file's contents $stream = $att->getContentStream(); // the file is decoded automatically $dest = \GuzzleHttp\Psr7\stream_for( fopen('my-file.ext') ); \GuzzleHttp\Psr7\copy_to_stream( $stream, $dest ); // OR: more simply if saving or copying to another stream $att->saveContent('my-file.ext'); // writes to my-file.ext $att->saveContent($stream); // copies to the stream // close only when $message is no longer being used. fclose($handle);
Documentation
Upgrade guides
License
BSD licensed - please see license agreement.
- ZBateson\MbWrapper Readme
zbateson/mb-wrapper
Charset conversion and string manipulation wrapper with a large defined set of aliases.
The goals of this project are to be:
- Well written
- Tested where possible
- Support as wide a range of charset aliases as possible
To include it for use in your project, please install via composer:
composer require zbateson/mb-wrapper
Php 7 Support Dropped
As of mb-wrapper 2.0, support for php 7 has been dropped.
Requirements
mb-wrapper requires PHP 8.0 or newer. Tested on PHP 8.0, 8.1, 8.2, and 8.3 on GitHub Actions.
New in 2.0
If converting or performing an operation on a string fails in iconv, an UnsupportedCharsetException is now thrown.
Description
MbWrapper is intended for use wherever mb_* or iconv_* is used. It scans supported charsets returned by mb_list_encodings(), and prefers mb_* functions, but will fallback to iconv if a charset isn't supported by the mb_ functions.
A list of aliased charsets is maintained for both mb_* and iconv, where a supported charset exists for an alias. This is useful for mail and http parsing as other systems may report encodings not recognized by mb_* or iconv.
Charset lookup is done by removing non-alphanumeric characters as well, so UTF8 will always be matched to UTF-8, etc...
Usage
The following wrapper methods are exposed:
- mb_convert_encoding, iconv with MbWrapper::convert
- mb_substr, iconv_substr with MbWrapper::getSubstr
- mb_strlen, iconv_strlen with MbWrapper::getLength
- mb_check_encoding, iconv (for verification) with MbWrapper::checkEncoding
$mbWrapper = new \ZBateson\MbWrapper\MbWrapper(); $fromCharset = 'ISO-8859-1'; $toCharset = 'UTF-8'; $mbWrapper->convert('data', $fromCharset, $toCharset); $mbWrapper->getLength('data', 'UTF-8'); $mbWrapper->substr('data', 'UTF-8', 1, 2); if ($mbWrapper->checkEncoding('data', 'UTF-8')) { echo 'Compatible'; }
License
BSD licensed - please see license agreement.
- ZBateson\StreamDecorators Readme
zbateson/stream-decorators
Psr7 stream decorators for character set conversion and common mail format content encodings.
The goals of this project are to be:
- Well written
- Standards-compliant but forgiving
- Tested where possible
To include it for use in your project, please install via composer:
composer require zbateson/stream-decorators
Php 7 Support Dropped
As of stream-decorators 2.0, support for php 7 has been dropped.
Requirements
stream-decorators requires PHP 8.0 or newer. Tested on 8.0, 8.1, 8.2 and 8.3.
New in 2.0 and 2.1
Support for guzzlehttp/psr7 1.9 dropped, min supported version is 2.0.
zbateson/mb-wrapper has been updated to 2.0 as well, which throws an UnsupportedCharsetException converting from/to an unsupported charset, which changes the behaviour of CharsetStream.
Two new classes are introduced in 2.1, DecoratedCachingStream and a TellZeroStream.
Usage
$stream = GuzzleHttp\Psr7\Utils::streamFor($handle); $b64Stream = new ZBateson\StreamDecorators\Base64Stream($stream); $charsetStream = new ZBateson\StreamDecorators\CharsetStream($b64Stream, 'UTF-32', 'UTF-8'); while (($line = GuzzleHttp\Psr7\Utils::readLine()) !== false) { echo $line, "\r\n"; }
Note that CharsetStream, depending on the target encoding, may return multiple bytes when a single 'char' is read. If using php's 'fread', this will result in a warning:
'read x bytes more data than requested (xxxx read, xxxx max) - excess data will be lost
This is because the parameter to 'fread' is bytes, and so when CharsetStream returns, say, 4 bytes representing a single UTF-32 character, fread will truncate to the first byte when requesting '1' byte. It is recommended to not convert to a stream handle (with StreamWrapper) for this reason when using CharsetStream.
The library consists of the following Psr\Http\Message\StreamInterface implementations:
- ZBateson\StreamDecorators\Base64Stream - decodes on read and encodes on write to base64.
- ZBateson\StreamDecorators\CharsetStream - encodes from $streamCharset to $stringCharset on read, and vice-versa on write.
- ZBateson\StreamDecorators\ChunkSplitStream - splits written characters into lines of $lineLength long (stream implementation of php's chunk_split).
- ZBateson\StreamDecorators\DecoratedCachingStream - a caching stream that writes to a decorated stream, and reads from the cached undecorated stream, so for instance a stream could be passed, and decorated with a Base64Stream, and when read, the returned bytes would be base64 encoded.
- ZBateson\StreamDecorators\NonClosingStream - overrides close() and detach(), and simply unsets the attached stream without closing it.
- ZBateson\StreamDecorators\PregReplaceFilterStream - calls preg_replace on with passed arguments on every read() call.
- ZBateson\StreamDecorators\QuotedPrintableStream - decodes on read and encodes on write to quoted-printable.
- ZBateson\StreamDecorators\SeekingLimitStream - similar to GuzzleHttp's LimitStream, but maintains an internal current read position, seeking to it when read() is called, and seeking back to the wrapped stream's position after reading.
- ZBateson\StreamDecorators\TellZeroStream - tell() always returns '0' -- used by DecoratedCachingStream to wrap a BufferStream in a CachingStream. CachingStream calls tell() on its wrapped stream, and BufferStream throws an exception, so TellZeroStream is used to wrap the internal BufferStream to mitigate that.
- ZBateson\StreamDecorators\UUStream - decodes on read, encodes on write to uu-encoded.
QuotedPrintableStream, Base64Stream and UUStream's constructors take a single argument of a StreamInterface. CharsetStreams's constructor also takes $streamCharset and $stringCharset as arguments respectively, ChunkSplitStream optionally takes a $lineLength argument (defaults to 76) and a $lineEnding argument (defaults to CRLF). PregReplaceFilterStream takes a $pattern argument and a $replacement argument. SeekingLimitStream takes optional $limit and $offset parameters, similar to GuzzleHttp's LimitStream.
License
BSD licensed - please see license agreement.