PHPFUI Tests Latest Packagist release

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

PHPFUI/InstaDoc

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 Tests Latest Packagist release

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/InstaDoc

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.

    Continuous Integration PHP Lint PHPStan

    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.

    Total Downloads Integrate

    Table of Contents

    1. How
    2. Why
      1. Using simply clone
      2. Overriding __clone()
      3. With DeepCopy
    3. How it works
    4. Going further
      1. Matchers
        1. Property name
        2. Specific property
        3. Type
      2. Filters
        1. SetNullFilter
        2. KeepFilter
        3. DoctrineCollectionFilter
        4. DoctrineEmptyCollectionFilter
        5. DoctrineProxyFilter
        6. ReplaceFilter
        7. ShallowCopyFilter
    5. Edge cases
    6. Contributing
      1. Tests

    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 :(

    association graph

    Using simply clone

    Using clone

    Overriding __clone()

    Overriding __clone

    With DeepCopy

    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 implementing DeepCopy\Filter\Filter and $matcher implementing DeepCopy\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 by DeepCopy\Matcher
    • DeepCopy\TypeFilter applies a transformation to any element matched by DeepCopy\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:

    1. loading the data
    2. 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 the DoctrineEmptyCollectionFilter

    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 the DoctrineProxyFilter with the ChainableFilter 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)

    1. 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)'
    1. 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 the ReplaceFilter 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

    Run tests Total Downloads Monthly Downloads Daily Downloads Latest Stable Version

    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

    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 is None
      • Could be specified using the consts: CompressManagerFactory::GZIP, CompressManagerFactory::BZIP2 or CompressManagerFactory::NONE
    • 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 is utf8
      • utf8 is compatible option and utf8mb4 is for full utf8 compliance
      • Could be specified using the consts: DumpSettings::UTF8 or DumpSettings::UTF8MB4
      • MySQL docs 5.7
    • 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
    • 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:

    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

    Build Status StyleCI License Downloads

    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). Your HEAD 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 a RuntimeException will be thrown. If you want to prevent this, set the debug option to false. This will make Repository 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 method getDiffStaged():

    $diff = $wc->getDiffStaged();

    Pending modifications

    You can get pending modifications on tracked files by calling method getDiffPending():

    $diff = $wc->getDiffPending();
  • GuzzleHttp Readme

    Guzzle

    Guzzle, PHP HTTP client

    Latest Version Build Status Total Downloads

    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:

    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 a GuzzleHttp\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 the wait 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, a GuzzleHttp\Promise\RejectionException is thrown and the reason can be obtained by calling the getReason 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 the wait 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 the cancel() 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 to true 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, or rejected.

    • 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.

    CI Static analysis

    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 implement Psr\Http\Message\StreamInterface by proxying to an underlying stream. Just use the StreamDecoratorTrait 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 than 0.

    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 and if-none-match.

    GuzzleHttp\Psr7\Header::normalize (deprecated)

    public static function normalize(string|array $header): array

    Header::normalize() is deprecated in favor of Header::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 (like http_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 implements Iterator, 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 call next 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: When null 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 return false 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 the GuzzleHttp\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%b1bhttp://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.orghttp://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, and file://localhost/myfile are equivalent according to RFC 3986.

      Example: file://localhost/myfilefile:///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.htmlhttp://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.htmlhttp://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

    Unit Tests Latest Packagist release Monthly downloads on Packagist

    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 as Highlight\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:

    1. explicit mode
    2. 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=\&quot;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=\&quot;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 the HighlightUtilities namespace to introduce additional functionality without the need for another dependency.

    Available functions:

    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

    BSD

  • ICalendarOrg Readme

    Zap Calendar iCalendar Library Tests Latest Packagist release

    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

    Latest Version Total Downloads Software License Build Status Coverage Status Quality Score Psalm Type Coverage CII Best Practices Sponsor development of this project

    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 and allow_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 the CommonMarkConverter 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

    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:

    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:

    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:

    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

    Latest Version Total Downloads Software License Build Status Coverage Status Quality Score Sponsor development of this project

    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&quot;),
            '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:

    🏷️ 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.

    Build Status Latest Version Total Downloads Quality Score SensioLabs Insight PHP7 Ready

    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 Geocoder 2.x or/and PHP 5.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, and WGS84.

    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 extend League\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() by serie().

    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 and wkt 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

    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

    Changelog

    See the changelog file

    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.

    Bitdeli Badge

  • League\Geotools\Tests Readme

    Geotools

    Geotools is a PHP geo-related library, built atop Geocoder and React libraries.

    Build Status Latest Version Total Downloads Quality Score SensioLabs Insight PHP7 Ready

    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 Geocoder 2.x or/and PHP 5.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, and WGS84.

    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 extend League\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() by serie().

    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 and wkt 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

    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

    Changelog

    See the changelog file

    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.

    Bitdeli Badge

  • Maknz\Slack Readme

    Slack for PHP

    Build Status Code Coverage Scrutinizer Code Quality StyleCI | Slack Welcome

    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 or 8.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 image
    response_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

    Slack Welcome

    If you have any questions how to use or contribute,
    you are welcome in our Slack Workspace.

    Contributing

    Slack Welcome

    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 Tests

    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 default

    try {
        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

    License: MIT Qa workflow Scrutinizer Code Coverage Scrutinizer Code Quality Stable Version Unstable Version

    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 the getDocComment() method, such as an object of type ReflectionClass, 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 Tests Latest Packagist release

    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

    PHPFUI/InstaDoc

    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 Tests Latest Packagist release

    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

    PHPFUI/InstaDoc

    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 Tests Latest Packagist release

    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 Tests Latest Packagist release

    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/InstaDoc

  • PHPFUI\MySQLSlowQuery Readme

    PHPFUI\MySQLSlowLog\Parser Tests Latest Packagist release

    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

    PHPFUI/InstaDoc

    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:

    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 Tests Latest Packagist release

    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:

    1. Prefer PHP 7
    2. Prefer PHP 5
    3. Only PHP 7
    4. Only PHP 5

    Examples

    See examples

    Full Class Documentation

    PHPFUI/InstaDoc

    License

    PHPFUI is distributed under the MIT License.

  • PHPFUI\Translation Readme

    PHPFUI/Translation Tests Latest Packagist release

    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 &verbar;

    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

    PHPFUI/InstaDoc

    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

    SWUbanner

    PHPMailer

    PHPMailer – A full-featured email creation and transfer class for PHP

    Test status codecov.io Latest Stable Version Total Downloads License API Docs OpenSSF Scorecard

    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 a sendmail 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 the mail() 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 the vendor/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 your composer.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 a use line for the SMTP class. Even if you're not using exceptions, you do still need to load the Exception 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 namespace PHPMailer\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 the docs 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.

    Test status

    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

    Coverage Status

    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

    1. Introduction
    2. Usage of basic components

    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
  • Soundasleep Readme

    example workflow Total Downloads

    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 just My 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 with composer 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 set Process 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, use Process::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 the PhpProcess::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 the PHP_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 the start(), run(), mustRun(), and restart() methods of the Process class
    • added a second array $env = [] argument to the start() method of the PhpProcess class
    • the ProcessUtils::escapeArgument() method has been removed
    • the areEnvironmentVariablesInherited(), getOptions(), and setOptions() methods of the Process class have been removed
    • support for passing proc_open() options has been removed
    • removed the ProcessBuilder class, use the Process class instead
    • removed the getEnhanceWindowsCompatibility() and setEnhanceWindowsCompatibility() methods of the Process 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 (via setWorkingDirectory() or constructor)

    3.3.0

    • added command line arrays in the Process class
    • added $env argument to Process::start(), run(), mustRun() and restart() 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
  • 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() vs serialize() or igbinary is performance: thanks to OPcache, the resulting code is significantly faster and more memory efficient than using unserialize() or igbinary_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 to PHP_Incomplete_Class objects;
    • references involving SplObjectStorage, ArrayObject or ArrayIterator instances are preserved;
    • Reflection*, IteratorIterator and RecursiveIteratorIterator 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 using ProxyHelper::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

    SWUbanner

    Build Status Build status codecov.io Codacy Badge Latest Stable Version Total Downloads License Donate to this project using Paypal Donate to this project using Patreon

    🔡 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 ...

    Index

    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

    1. Composer is a prerequisite for running the tests.
    composer install
    
    1. 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

    Build Status Coverage Status Codacy Badge Latest Stable Version Total Downloads License Donate to this project using Paypal Donate to this project using Patreon

    :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

    FOSSA Status

  • 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

    Latest Stable Version Total Downloads

    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 an Webmozart\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 class
    isInstanceOfAny($value, array $classes, $message = '') Check that a value is an instanceof at least one class on the array of classes
    notInstanceOf($value, $class, $message = '') Check that a value is not an instanceof a class
    isAOf($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 not null:

    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 and all versions of the assertions.
    • 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.
    • 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.
    • 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!

    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).

    Build Status Code Coverage Scrutinizer Code Quality Total Downloads Latest Stable Version

    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

    SecuMailer

    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.

    Tests Code Coverage Scrutinizer Code Quality Total Downloads Latest Stable Version

    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.

    Tests Code Coverage Scrutinizer Code Quality Total Downloads Latest Stable Version

    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.

© 2024 Bruce Wells
Search Namespaces \ Classes
Configuration