Commit ec831fed authored by Sergey Shadrin's avatar Sergey Shadrin

[#124524] Локализация

-added drush_language module
parent c852b6ca
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
# Drush 9 language commands
## Upgrading
At the time of this commit (Drush 9.0.0-beta5), Drush 9 needs commands using
Drupal services to be placed in a module, which was not necessary for Drush 8.
Quoting Greg Anderson in https://github.com/drush-ops/drush/issues/3050 :
<blockquote>Drush extensions that are not part of a module can be policy files
(hooks) or standalone commands, but cannot do dependency injection.
</blockquote>
As a result this plugin is no longer of type `drupal-drush` but is a normal
Drupal module. Assuming it is being used in a Composer `drupal-project`
workflow, this means:
* Removing the `drush/drush_language/` or `drush/contrib/drush_language`
directory.
* Running `composer update` to obtain the new version of the plugin implemented
as a module and placed in the `web/modules/contrib/drush_language` directory.
* Enabling the `drush_language` module to make the commands discoverable by
Drush.
{
"name": "drupal/drush_language",
"description": "Provides Drush commands for language manipulation",
"type": "drupal-module",
"license": "GPL-2.0+",
"minimum-stability": "dev",
"require": {
"webmozart/path-util": "^2.1.0"
},
"extra": {
"drush": {
"services": {
"drush.services.yml": "^9"
}
}
}
}
<?php
/**
* @file
* Default Settings.
*
* The directory relative to drupal root where custom settings can be exported
* and re-imported for deployment. Do not use a trailing slash.
*
* $settings['custom_translations_directory'] = '../custom-translations';
*/
services:
drush_language.commands:
class: \Drupal\drush_language\Commands\DrushLanguageCommands
arguments:
- '@drush_language.cli'
tags:
- { name: drush.command }
<?php
/**
* @file
* Drush (< 9) integration for the drush_language module.
*/
use Drupal\drush_language\Drush8Io;
/**
* Implements hook_drush_command().
*
* @See drush_parse_command() for a list of recognized keys.
*/
function drush_language_drush_command() {
$items = [];
$items['language-add'] = [
'description' => 'Add and import a new language definition',
'arguments' => [
'langcodes' => 'The langcodes of the languages for which a definition will be added.',
],
'aliases' => ['langadd'],
];
$items['language-default'] = [
'description' => 'Assign an enabled language as default',
'arguments' => [
'langcode' => 'The langcode of the language which will be set as the default language.',
],
'aliases' => ['langdef'],
];
$items['language-import-translations'] = [
'description' => 'Import a single .po file',
'arguments' => [
'.po file(s)' => 'Path to one or more .po files to import. If omitted, $settings[\'custom_translations_directory\'] must be set and all .po files from that directory will be taken. If langcode (overridable), project and version can be deduced from the filename, they will be stored in the translation database.',
],
'examples' => [
'Import all custom translations from the directory defined in $settings[\'custom_translations_directory\'].' => 'drush langimp',
'Import single file with explicit langcode' => 'drush langimp --langcode=ru file.po',
'Import not-customized (e.g. module) translations, without replacing custom translations, with auto langcode (these are the recognized patterns)' => 'drush langimp --langcode=eo --no-set-customized --no-replace-customized de.po foomodule.fr.po barmodule-8.x-2.2-rc1.es.po',
],
'options' => [
'langcode' => [
'description' => 'Language code to be imported. If not given, extracted from file name.',
'value' => 'optional',
],
'replace-customized' => [
'description' => 'Replace existing customized translations. Defaults to true.',
'value' => 'optional',
],
'replace-not-customized' => [
'description' => 'Replace existing not-customized translations. Defaults to true.',
'value' => 'optional',
],
'set-customized' => [
'description' => 'Set all existing translations as being customized. Defaults to true.',
'value' => 'optional',
],
'autocreate-language' => [
'description' => 'Autocreate any imported language if it does not yet exist. Defaults to true.',
'value' => 'optional',
],
],
'aliases' => ['langimp', 'language-import'],
];
$items['language-export-translations'] = [
'description' => 'Export string of a language as one or more .po files',
'examples' => [
'Export all custom translations into the directory defined in $settings[\'custom_translations_directory\'].' => 'drush langexp',
'Export all german translated strings' => 'drush langexp --langcodes=de --status=customized,not-customized --file=all-de.po',
'Export untranslated strings from all languages to current dir' => 'drush langexp --status=untranslated --file=./todo-%langcode.po',
],
'options' => [
'statuses' => [
'description' => "The statuses to export, defaults to 'customized'\nThis can be a comma-separated list of 'customized', 'not-customized', 'not-translated', or (as abbreviation) 'all'.",
'value' => 'optional',
],
'langcodes' => [
'description' => 'The language codes to export, comma-separated. Defaults to all enabled languages.',
'value' => 'optional',
],
'file' => [
'description' => 'The target file pattern. You can use %langcode as placeholder. Defaults to "%language.po". If the path is relative and does not start with ".", $settings[\'custom_translations_directory\'] must be defined and the path is relative to that directory.',
'value' => 'optional',
],
'force' => [
'description' => 'Write file even if no translations. Defaults to true.',
'value' => 'optional',
],
],
'aliases' => ['langexp', 'language-export'],
];
return $items;
}
/**
* Add a language.
*/
function drush_drush_language_language_add() {
$args = func_get_args();
if (count($args) == 0) {
return drush_set_error('DRUSH_LANGUAGE', dt('Please provide one or more language codes as arguments.'));
}
$langcodes = explode(',', $args[0]);
\Drupal::service('drush_language.cli')->add(new Drush8Io(), 'dt', $langcodes);
}
/**
* Assigns the default language.
*/
function drush_drush_language_language_default() {
$args = func_get_args();
if (count($args) == 0) {
return drush_set_error('DRUSH_LANGUAGE', dt('Please provide one or more language codes as arguments.'));
}
foreach ($args as $langcode) {
\Drupal::service('drush_language.cli')->languageDefault(new Drush8Io(), 'dt', $langcode);
}
}
/**
* Imports .po file(s).
*/
function drush_drush_language_language_import_translations() {
// Get arguments and options.
$file_paths = func_get_args();
$options = [
'langcode' => drush_get_option('langcode', NULL),
'replace-customized' => drush_get_option('set-customized', TRUE),
'replace-not-customized' => drush_get_option('replace-customized', TRUE),
'set-customized' => drush_get_option('replace-not-customized', TRUE),
'autocreate-language' => drush_get_option('autocreate-language', TRUE),
];
\Drupal::service('drush_language.cli')->importTranslations(new Drush8Io(), 'dt', $file_paths, $options);
}
/**
* Exports .po file(s).
*/
function drush_drush_language_language_export_translations() {
// Get options.
$options = [
'statuses' => drush_get_option_list('statuses', 'customized'),
'langcodes' => drush_get_option_list('langcodes'),
'file' => drush_get_option('file', '%langcode.po'),
'force' => drush_get_option('force', TRUE),
];
\Drupal::service('drush_language.cli')->exportTranslations(new Drush8Io(), 'dt', $options);
}
name: 'Drush Language commands'
type: module
description: 'Provides Drush commands for language manipulation'
core_version_requirement: ^8.7.7 || ^9 || ^10
package: Multilingual
dependencies:
- drupal:language
# Information added by Drupal.org packaging script on 2022-12-13
version: '8.x-1.0-rc5'
project: 'drush_language'
datestamp: 1670957304
services:
drush_language.cli:
class: Drupal\drush_language\Service\DrushLanguageCliService
arguments:
- '@config.factory'
- '@entity_type.manager'
- '@language_manager'
- '@module_handler'
- '@file_system'
<?php
namespace Drupal\drush_language\Commands;
use Drupal\drush_language\Service\DrushLanguageCliService;
use Drush\Commands\DrushCommands;
use Drush\Utils\StringUtils;
/**
* Implements the Drush language commands.
*/
class DrushLanguageCommands extends DrushCommands {
/**
* The interoperability cli service.
*
* @var \Drupal\drush_language\Service\DrushLanguageCliService
*/
protected $cliService;
/**
* DrushLanguageCommands constructor.
*
* @param \Drupal\drush_language\Service\DrushLanguageCliService $cliService
* The CLI service which allows interoperability.
*/
public function __construct(DrushLanguageCliService $cliService) {
$this->cliService = $cliService;
}
/**
* Add and import one or more new language definitions.
*
* @codingStandardsIgnoreStart
* @command language:add
*
* @param array $langcodes
* A comma-delimited list of langcodes for which a definition will be added.
*
* @aliases langadd,language-add
* @codingStandardsIgnoreEnd
*/
public function add(array $langcodes) {
$langcodes = StringUtils::csvToArray($langcodes);
$this->cliService->add($this->io(), 'dt', $langcodes);
}
/**
* Assign an enabled language as default.
*
* @codingStandardsIgnoreStart
* @command language:default
*
* @param string $langcode
* The langcode of the language which will be set as the default language.
*
* @aliases langdef,language-default
* @codingStandardsIgnoreEnd
*/
public function languageDefault($langcode) {
$this->cliService->languageDefault($this->io(), 'dt', $langcode);
}
// @codingStandardsIgnoreStart
/**
* Import a single .po file.
*
* @command language:import:translations
*
* @param array $poFiles
* Comma-separated list of paths .po files containing the translations.
*
* @option langcode
* Language code to be imported. If not given, extracted from file name.
* @option replace-customized
* Replace existing customized translations. Defaults to true.
* @option replace-not-customized
* Replace existing not-customized translations. Defaults to true.
* @option set-customized
* Set all existing translations as being customized. Defaults to true.
* @option autocreate-language
* Autocreate any imported language if it does not yet exist. Defaults to
* true.
*
* @usage drush langimp
* Import all custom translations from the directory defined in
* $settings['custom_translations_directory'].
* @usage drush langimp --langcode=ru file.po
* Import single file with explicit langcode.
* @usage drush langimp --langcode=eo --no-set-customized --no-replace-customized de.po foomodule.fr.po barmodule-8.x-2.2-rc1.es.po
* Import not-customized (e.g. module) translations, without replacing
* custom translations, with auto langcode (these are the recognized
* patterns)'.
*
* @aliases langimp,language-import,language-import-translations
*/
public function importTranslations(
array $poFiles,
array $options = [
'langcode' => NULL,
'replace-customized' => TRUE,
'replace-not-customized' => TRUE,
'set-customized' => TRUE,
'autocreate-language' => TRUE,
]
) {
$poFiles = StringUtils::csvToArray($poFiles);
$this->cliService->importTranslations($this->io(), 'dt', $poFiles, $options);
}
// @codingStandardsIgnoreEnd
// @codingStandardsIgnoreStart
/**
* Export string of a language as one or more .po files.
*
* @command language:export:translations
*
* @option statuses
* The statuses to export, defaults to 'customized'. This can be a
* comma-separated list of 'customized', 'not-customized', 'not-translated',
* or (as abbreviation) 'all'.
* @option langcodes
* The language codes to export, comma-separated. Defaults to all enabled
* languages.
* @option file
* The target file pattern. You can use %langcode as placeholder. Defaults
* to "%language.po". If the path is relative and does not start with ".",
* $settings[\'custom_translations_directory\'] must be defined and the path
* is relative to that directory.
* @option force
* Write file even if no translations. Defaults to true.
*
* @usage drush langexp
* Export all custom translations into the directory defined in
* $settings['custom_translations_directory'].
* @usage drush langexp --langcodes=de --status=customized,not-customized --file=all-de.po
* Export all german translated strings
* @usage drush langexp --status=untranslated --file=./todo-%langcode.po
* Export untranslated strings from all languages to current dir
*
* @aliases langexp,language-export,language-export-translations
*/
public function exportTranslations($options = [
'statuses' => ['customized'],
'langcodes' => [],
'file' => '%langcode.po',
'force' => TRUE,
]) {
try {
$this->cliService->exportTranslations($this->io(), 'dt', $options);
}
catch (\Exception $exception) {
}
}
// @codingStandardsIgnoreEnd
}
<?php
namespace Drupal\drush_language;
use Drush\Log\LogLevel;
/**
* Class Drush8Io.
*
* This is a stand in for \Symfony\Component\Console\Style\StyleInterface with
* Drush 8 so that we don't need to depend on symfony components.
*/
// @codingStandardsIgnoreStart
class Drush8Io {
public function confirm($text) {
return drush_confirm($text);
}
public function success($text) {
drush_log($text, LogLevel::SUCCESS);
}
public function error($text) {
drush_log($text, LogLevel::ERROR);
}
public function text($text) {
drush_log($text, LogLevel::NOTICE);
}
public function warning($text) {
drush_log($text, LogLevel::WARNING);
}
}
// @codingStandardsIgnoreEnd
<?php
namespace Drupal\drush_language\Service;
use Drupal\Component\Gettext\PoStreamWriter;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Site\Settings;
use Drupal\language\ConfigurableLanguageManagerInterface;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\locale\PoDatabaseReader;
use Webmozart\PathUtil\Path;
/**
* Class DrushLanguageCliService.
*
* @package Drupal\drush_language
*
* @internal This service is not an api and may change at any time.
*/
class DrushLanguageCliService {
/**
* The config.factory service.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $configFactory;
/**
* The entity_type.manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The language_manager service.
*
* @var \Drupal\language\ConfigurableLanguageManagerInterface
*/
protected $languageManager;
/**
* The module_handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* List of messages.
*
* @var array
*/
protected $errors;
/**
* DrushLanguageCommands constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The config.factory service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity_type.manager service.
* @param \Drupal\language\ConfigurableLanguageManagerInterface $languageManager
* The language_manager service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* The module_handler service.
*/
public function __construct(
ConfigFactoryInterface $configFactory,
EntityTypeManagerInterface $entityTypeManager,
ConfigurableLanguageManagerInterface $languageManager,
ModuleHandlerInterface $moduleHandler,
FileSystemInterface $fileSystem
) {
$this->configFactory = $configFactory;
$this->entityTypeManager = $entityTypeManager;
$this->languageManager = $languageManager;
$this->moduleHandler = $moduleHandler;
$this->fileSystem = $fileSystem;
$this->errors = [];
}
/**
* Add and import one or more new language definitions.
*
* @param \Symfony\Component\Console\Style\StyleInterface|\Drupal\drush_language\Drush8Io $io
* The $io interface of the cli tool calling.
* @param callable $t
* The translation function akin to t().
* @param array $langcodes
* A list of langcodes for which a definition will be added.
*/
public function add($io, callable $t, array $langcodes) {
if (empty($langcodes)) {
$io->error($t('Please provide one or more comma-separated language codes as arguments.'));
return;
}
foreach ($langcodes as $langcode) {
$messageArgs = ['@langcode' => $langcode];
// In the foreach loop because the list changes on successful iterations.
$languages = $this->languageManager->getLanguages();
// Do not re-add existing languages.
if (isset($languages[$langcode])) {
$io->warning($t('The language with code @langcode already exists.', $messageArgs));
continue;
}
// Only allow adding languages for predefined langcodes.
// In the foreach loop because the list changes on successful iterations.
$predefined = $this->languageManager->getStandardLanguageListWithoutConfigured();
if (!isset($predefined[$langcode])) {
$io->warning($t('Invalid language code: @langcode', $messageArgs));
continue;
}
// Add the language definition.
$language = ConfigurableLanguage::createFromLangcode($langcode);
$language->save();
// Download and import translations for the newly added language if
// interface translation is enabled.
if ($this->moduleHandler->moduleExists('locale')) {
\Drupal::moduleHandler()->loadInclude('locale', 'inc', 'fetch');
$options = _locale_translation_default_update_options();
if ($batch = locale_translation_batch_update_build([], [$langcode], $options)) {
batch_set($batch);
$batch =& batch_get();
$batch['progressive'] = FALSE;
// Process the batch.
drush_backend_batch_process();
}
}
$io->text($t('Added language: @langcode', $messageArgs));
}
}
/**
* Assign an enabled language as default.
*
* @param \Symfony\Component\Console\Style\StyleInterface|\Drupal\drush_language\Drush8Io $io
* The $io interface of the cli tool calling.
* @param callable $t
* The translation function akin to t().
* @param string $langcode
* The langcode of the language which will be set as the default language.
*/
public function languageDefault($io, callable $t, $langcode) {
$messageArgs = ['@langcode' => $langcode];
$languages = $this->languageManager->getLanguages();
if (!isset($languages[$langcode])) {
$io->warning($t('Specified language does not exist: @langcode', $messageArgs));
return;
}
$this->configFactory->getEditable('system.site')->set('default_langcode', $langcode)->save();
$this->languageManager->reset();
}
/**
* Import a single .po file.
*
* @param \Symfony\Component\Console\Style\StyleInterface|\Drupal\drush_language\Drush8Io $io
* The $io interface of the cli tool calling.
* @param callable $t
* The translation function akin to t().
* @param array $poFiles
* A list of paths .po files containing the translations.
* @param array $options
* The command options.
*
* @see \Drupal\locale\Form\ImportForm::submitForm
*
* @todo Implement \Drupal\locale\Form\ImportForm::buildForm
* @todo This can be simplified once https://www.drupal.org/node/2631584
* lands in Drupal core.
*/
public function importTranslations(
$io,
callable $t,
array $poFiles,
array $options = [
'langcode' => NULL,
'replace-customized' => TRUE,
'replace-not-customized' => TRUE,
'set-customized' => TRUE,
'autocreate-language' => TRUE,
]
) {
$this->moduleHandler->loadInclude('locale', 'translation.inc');
$this->moduleHandler->loadInclude('locale', 'bulk.inc');
$opt_langcode = $options['langcode'];
$opt_set_customized = $options['set-customized'];
$opt_replace_customized = $options['replace-customized'];
$opt_replace_not_customized = $options['replace-not-customized'];
$opt_autocreate_language = $options['autocreate-language'];
if (!$poFiles) {
if ($dir = Settings::get('custom_translations_directory')) {
$poFiles = glob(DRUPAL_ROOT . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . '*.po');
}
else {
$io->success($t('Nothing to do, no file given and no custom translation directory set.'));
}
}
$importer_options = array_merge(_locale_translation_default_update_options(), [
'langcode' => $opt_langcode,
'customized' => $opt_set_customized ? LOCALE_CUSTOMIZED : LOCALE_NOT_CUSTOMIZED,
'overwrite_options' => [
'customized' => (int) $opt_replace_customized,
'not_customized' => (int) $opt_replace_not_customized,
],
]);
// Import language files.
$files = [];
$langcodes_to_import = [];
foreach ($poFiles as $file_path) {
// Ensure we have the file intended for upload.
if (file_exists($file_path)) {
$file = locale_translate_file_create($file_path);
// Extract project, version and language code from the file name
// Supported:
// - {project}-{version}.{langcode}.po, {prefix}.{langcode}.po
// - {langcode}.po
// Note: $options['langcode'] will override file langcode.
$file = locale_translate_file_attach_properties($file, $importer_options);
if ($file->langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
if (!$opt_langcode) {
$io->error($t('Can not autodetect language of file @file', ['@file' => $file_path]));
return;
}
$file->langcode = $opt_langcode;
if (empty($file->version) && !empty($file->project) && !empty($file->langcode)) {
$sources = locale_translation_get_status();
$source = $sources[$file->project][$file->langcode];
if (isset($source->version)) {
$file->version = $source->version;
}
}
}
$langcodes_to_import[$file->langcode] = $file->langcode;
$files[] = $file;
}
else {
$io->error($t('File to import at @filepath not found.', ['@filepath' => $file_path]));
}
}
if ($opt_autocreate_language) {
$languages = $this->languageManager->getLanguages();
foreach ($langcodes_to_import as $langcode_to_import) {
if (!isset($languages[$langcode_to_import])) {
try {
/** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $language_storage */
$language_storage = $this->entityTypeManager->getStorage('configurable_language');
$language = $language_storage->create(['langcode' => $opt_langcode]);
$io->success($t('The language @id (@label) has been created.', [
'@id' => $language->id(),
'@label' => $language->label(),
]));
}
catch (InvalidPluginDefinitionException $exception) {
$io->error($exception->getMessage());
}
}
}
}
$batch = locale_translate_batch_build($files, $importer_options);
batch_set($batch);
// Create or update all configuration translations for this language.
if ($batch = locale_config_batch_update_components($importer_options, $langcodes_to_import)) {
batch_set($batch);
}
drush_backend_batch_process();
$io->success($t('Import complete.'));
}
/**
* Export strings of a language as a .po file.
*
* @param \Symfony\Component\Console\Style\StyleInterface|\Drupal\drush_language\Drush8Io $io
* The $io interface of the cli tool calling.
* @param callable $t
* The translation function akin to t().
* @param array $options
* The command options.
*
* @todo Implement \Drupal\locale\Form\ExportForm::buildForm
* @todo This can be simplified once https://www.drupal.org/node/2631584
* lands in Drupal core.
*
* @throws \Exception
* Invalid values passed.
*/
public function exportTranslations(
$io,
callable $t,
array $options = [
'statuses' => ['customized'],
'langcodes' => [],
'file' => '%langcode.po',
'force' => TRUE,
]
) {
// Get options.
$opt_langcodes = $options['langcodes'];
$opt_filepath = $options['file'];
$opt_force_write = $options['force'];
$opt_statuses = $options['statuses'];
// Massage options.
// Massage translation statuses.
$export_statuses_allowed = [
// internal-value => input-value.
'customized' => 'customized',
'not_customized' => 'not-customized',
'not_translated' => 'not-translated',
];
$opt_statuses = array_values($opt_statuses);
if ($opt_statuses == ['all']) {
$opt_statuses = $export_statuses_allowed;
}
$export_statuses_unknown = array_diff($opt_statuses, $export_statuses_allowed);
if ($export_statuses_unknown) {
$io->error($t('Unknown status options: @options',
['@options' => implode(', ', $export_statuses_unknown)]
));
return;
}
$export_statuses_filtered = array_intersect($export_statuses_allowed, $opt_statuses);
$export_statuses = array_fill_keys(array_keys($export_statuses_filtered), TRUE);
// Massage file path pattern.
if (!Path::isAbsolute($opt_filepath) && !('./' === substr($opt_filepath, 0, 2))) {
$opt_filedir = Settings::get('custom_translations_directory');
if (!$opt_filedir) {
$io->error($t('Can not export, relative path given and no $settings[\'custom_translations_directory\'] defined. You can instead use an absolute filename or one starting with "./".'));
return;
}
if (!Path::isAbsolute($opt_filedir)) {
$opt_filedir = DRUPAL_ROOT . DIRECTORY_SEPARATOR . $opt_filedir;
}
$opt_filepath = $opt_filedir . DIRECTORY_SEPARATOR . $opt_filepath;
}
// Massage langcodes.
if (!$opt_langcodes) {
$languages = $this->languageManager->getLanguages();
$opt_langcodes = array_keys($languages);
}
// Validate options.
// Yell if more than 1 langcode and no placeholder.
if (count($opt_langcodes) > 1 && !preg_match('/%langcode/u', $opt_filepath)) {
$io->error($t('You must use %langcode file placeholder when exporting multiple languages.'));
return;
}
// Check that all langcodes are valid, before beginning.
foreach ($opt_langcodes as $langcode) {
$language = $this->languageManager->getLanguage($langcode);
if ($language == NULL) {
$io->error($t('Unknown language: %langcode', ['%langcode' => $langcode]));
return;
}
}
// Do our work.
foreach ($opt_langcodes as $langcode) {
$filepath = preg_replace('/%langcode/u', $langcode, $opt_filepath);
$language = $this->languageManager->getLanguage($langcode);
// Check if file_path exists and is writable.
$dir = dirname($filepath);
if (!$this->fileSystem->prepareDirectory($dir)) {
$this->fileSystem->prepareDirectory($dir, FileSystemInterface::MODIFY_PERMISSIONS | FileSystemInterface::CREATE_DIRECTORY);
}
$reader = new PoDatabaseReader();
$language_name = '';
if ($language != NULL) {
$reader->setLangcode($language->getId());
$reader->setOptions($export_statuses);
$languages = $this->languageManager->getLanguages();
$language_name = isset($languages[$language->getId()]) ? $languages[$language->getId()]->getName() : '';
}
$item = $reader->readItem();
if ($item || $opt_force_write) {
$header = $reader->getHeader();
$header->setProjectName($this->configFactory->get('system.site')->get('name'));
$header->setLanguageName($language_name);
$writer = new PoStreamWriter();
$writer->setURI($filepath);
$writer->setHeader($header);
$writer->open();
if ($item) {
$writer->writeItem($item);
}
$writer->writeItems($reader);
$writer->close();
$io->success($t('Exported translations for language !langcode to file !file.', [
'!langcode' => $langcode,
'!file' => $filepath
]));
}
else {
$io->error($t('Nothing to export for language !langcode.', ['!langcode' => $langcode]));
return;
}
}
$io->success($t('Export complete.'));
}
/**
* Returns error messages created while running the import.
*
* @return array
* List of messages.
*/
public function getErrors() {
return $this->errors;
}
}
<?php
namespace Drupal\Tests\drush_language\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Simple browser test.
*
* @group drush_language
*/
class AdminPageTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'drush_language',
];
/**
* Theme to enable.
*
* @var string
*/
protected $defaultTheme = 'stark';
/**
* Tests that the /admin page returns a 200.
*/
public function testAdminPage() {
$this->drupalLogin($this->rootUser);
$this->drupalGet('admin');
$this->assertSession()->statusCodeEquals(200);
// Ensure that the test is not marked as risky because of no assertions.
// see https://gitlab.com/weitzman/drupal-test-traits/-/commit/82bf5059908f9073b3468cb7313960da72176d9a
$this->addToAssertionCount(1);
}
}
Changelog
=========
## UNRELEASED
## 1.11.0
### Added
* Added explicit (non magic) `allNullOr*` methods, with `@psalm-assert` annotations, for better Psalm support.
### Changed
* Trait methods will now check the assertion themselves, instead of using `__callStatic`
* `isList` will now deal correctly with (modified) lists that contain `NaN`
* `reportInvalidArgument` now has a return type of `never`.
### Removed
* Removed `symfony/polyfill-ctype` as a dependency, and require `ext-cytpe` instead.
* You can still require the `symfony/polyfill-ctype` in your project if you need it, as it provides `ext-ctype`
## 1.10.0
### Added
* On invalid assertion, we throw a `Webmozart\Assert\InvalidArgumentException`
* Added `Assert::positiveInteger()`
### Changed
* Using a trait with real implementations of `all*()` and `nullOr*()` methods to improve psalm compatibility.
### Removed
* Support for PHP <7.2
## 1.9.1
## Fixed
* provisional support for PHP 8.0
## 1.9.0
* added better Psalm support for `all*` & `nullOr*` methods
* These methods are now understood by Psalm through a mixin. You may need a newer version of Psalm in order to use this
* added `@psalm-pure` annotation to `Assert::notFalse()`
* added more `@psalm-assert` annotations where appropriate
## Changed
* the `all*` & `nullOr*` methods are now declared on an interface, instead of `@method` annotations.
This interface is linked to the `Assert` class with a `@mixin` annotation. Most IDE's have supported this
for a long time, and you should not lose any autocompletion capabilities. PHPStan has supported this since
version `0.12.20`. This package is marked incompatible (with a composer conflict) with phpstan version prior to that.
If you do not use PHPStan than this does not matter.
## 1.8.0
### Added
* added `Assert::notStartsWith()`
* added `Assert::notEndsWith()`
* added `Assert::inArray()`
* added `@psalm-pure` annotations to pure assertions
### Fixed
* Exception messages of comparisons between `DateTime(Immutable)` objects now display their date & time.
* Custom Exception messages for `Assert::count()` now use the values to render the exception message.
## 1.7.0 (2020-02-14)
### Added
* added `Assert::notFalse()`
* added `Assert::isAOf()`
* added `Assert::isAnyOf()`
* added `Assert::isNotA()`
## 1.6.0 (2019-11-24)
### Added
* added `Assert::validArrayKey()`
* added `Assert::isNonEmptyList()`
* added `Assert::isNonEmptyMap()`
* added `@throws InvalidArgumentException` annotations to all methods that throw.
* added `@psalm-assert` for the list type to the `isList` assertion.
### Fixed
* `ResourceBundle` & `SimpleXMLElement` now pass the `isCountable` assertions.
They are countable, without implementing the `Countable` interface.
* The doc block of `range` now has the proper variables.
* An empty array will now pass `isList` and `isMap`. As it is a valid form of both.
If a non-empty variant is needed, use `isNonEmptyList` or `isNonEmptyMap`.
### Changed
* Removed some `@psalm-assert` annotations, that were 'side effect' assertions See:
* [#144](https://github.com/webmozart/assert/pull/144)
* [#145](https://github.com/webmozart/assert/issues/145)
* [#146](https://github.com/webmozart/assert/pull/146)
* [#150](https://github.com/webmozart/assert/pull/150)
* If you use Psalm, the minimum version needed is `3.6.0`. Which is enforced through a composer conflict.
If you don't use Psalm, then this has no impact.
## 1.5.0 (2019-08-24)
### Added
* added `Assert::uniqueValues()`
* added `Assert::unicodeLetters()`
* added: `Assert::email()`
* added support for [Psalm](https://github.com/vimeo/psalm), by adding `@psalm-assert` annotations where appropriate.
### Fixed
* `Assert::endsWith()` would not give the correct result when dealing with a multibyte suffix.
* `Assert::length(), minLength, maxLength, lengthBetween` would not give the correct result when dealing with multibyte characters.
**NOTE**: These 2 changes may break your assertions if you relied on the fact that multibyte characters didn't behave correctly.
### Changed
* The names of some variables have been updated to better reflect what they are.
* All function calls are now in their FQN form, slightly increasing performance.
* Tests are now properly ran against HHVM-3.30 and PHP nightly.
### Deprecation
* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()`
* This was already done in 1.3.0, but it was only done through a silenced `trigger_error`. It is now annotated as well.
## 1.4.0 (2018-12-25)
### Added
* added `Assert::ip()`
* added `Assert::ipv4()`
* added `Assert::ipv6()`
* added `Assert::notRegex()`
* added `Assert::interfaceExists()`
* added `Assert::isList()`
* added `Assert::isMap()`
* added polyfill for ctype
### Fixed
* Special case when comparing objects implementing `__toString()`
## 1.3.0 (2018-01-29)
### Added
* added `Assert::minCount()`
* added `Assert::maxCount()`
* added `Assert::countBetween()`
* added `Assert::isCountable()`
* added `Assert::notWhitespaceOnly()`
* added `Assert::natural()`
* added `Assert::notContains()`
* added `Assert::isArrayAccessible()`
* added `Assert::isInstanceOfAny()`
* added `Assert::isIterable()`
### Fixed
* `stringNotEmpty` will no longer report "0" is an empty string
### Deprecation
* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()`
## 1.2.0 (2016-11-23)
* added `Assert::throws()`
* added `Assert::count()`
* added extension point `Assert::reportInvalidArgument()` for custom subclasses
## 1.1.0 (2016-08-09)
* added `Assert::object()`
* added `Assert::propertyExists()`
* added `Assert::propertyNotExists()`
* added `Assert::methodExists()`
* added `Assert::methodNotExists()`
* added `Assert::uuid()`
## 1.0.2 (2015-08-24)
* integrated Style CI
* add tests for minimum package dependencies on Travis CI
## 1.0.1 (2015-05-12)
* added support for PHP 5.3.3
## 1.0.0 (2015-05-12)
* first stable release
## 1.0.0-beta (2015-03-19)
* first beta release
The MIT License (MIT)
Copyright (c) 2014 Bernhard Schussek
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Webmozart Assert
================
[![Latest Stable Version](https://poser.pugx.org/webmozart/assert/v/stable.svg)](https://packagist.org/packages/webmozart/assert)
[![Total Downloads](https://poser.pugx.org/webmozart/assert/downloads.svg)](https://packagist.org/packages/webmozart/assert)
This library contains efficient assertions to test the input and output of
your methods. With these assertions, you can greatly reduce the amount of coding
needed to write a safe implementation.
All assertions in the [`Assert`] class throw 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:
```bash
composer require webmozart/assert
```
Example
-------
```php
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:
```php
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`:
```php
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`:
```php
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](https://psalm.dev/docs/annotating_code/assertion_syntax/).
A dedicated [PHPStan Plugin](https://github.com/phpstan/phpstan-webmozart-assert) is
required for proper type support.
Authors
-------
* [Bernhard Schussek] a.k.a. [@webmozart]
* [The Community Contributors]
Contribute
----------
Contributions to the package are always welcome!
* Report any bugs or issues you find on the [issue tracker].
* You can grab the source code at the package's [Git repository].
License
-------
All contents of this package are licensed under the [MIT license].
[beberlei/assert]: https://github.com/beberlei/assert
[assert package]: https://github.com/beberlei/assert
[Composer]: https://getcomposer.org
[Bernhard Schussek]: https://webmozarts.com
[The Community Contributors]: https://github.com/webmozart/assert/graphs/contributors
[issue tracker]: https://github.com/webmozart/assert/issues
[Git repository]: https://github.com/webmozart/assert
[@webmozart]: https://twitter.com/webmozart
[MIT license]: LICENSE
[`Assert`]: src/Assert.php
{
"name": "webmozart/assert",
"description": "Assertions to validate method input/output with nice error messages.",
"license": "MIT",
"keywords": [
"assert",
"check",
"validate"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"require": {
"php": "^7.2 || ^8.0",
"ext-ctype": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.5.13"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
"vimeo/psalm": "<4.6.1 || 4.6.2"
},
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Webmozart\\Assert\\Tests\\": "tests/",
"Webmozart\\Assert\\Bin\\": "bin/src"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.10-dev"
}
}
}
<?php
/*
* This file is part of the webmozart/assert package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\Assert;
use ArrayAccess;
use BadMethodCallException;
use Closure;
use Countable;
use DateTime;
use DateTimeImmutable;
use Exception;
use ResourceBundle;
use SimpleXMLElement;
use Throwable;
use Traversable;
/**
* Efficient assertions to validate the input/output of your methods.
*
* @since 1.0
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class Assert
{
use Mixin;
/**
* @psalm-pure
* @psalm-assert string $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function string($value, $message = '')
{
if (!\is_string($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a string. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert non-empty-string $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function stringNotEmpty($value, $message = '')
{
static::string($value, $message);
static::notEq($value, '', $message);
}
/**
* @psalm-pure
* @psalm-assert int $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function integer($value, $message = '')
{
if (!\is_int($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an integer. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert numeric $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function integerish($value, $message = '')
{
if (!\is_numeric($value) || $value != (int) $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an integerish value. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert positive-int $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function positiveInteger($value, $message = '')
{
if (!(\is_int($value) && $value > 0)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a positive integer. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert float $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function float($value, $message = '')
{
if (!\is_float($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a float. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert numeric $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function numeric($value, $message = '')
{
if (!\is_numeric($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a numeric. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert positive-int|0 $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function natural($value, $message = '')
{
if (!\is_int($value) || $value < 0) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a non-negative integer. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert bool $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function boolean($value, $message = '')
{
if (!\is_bool($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a boolean. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert scalar $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function scalar($value, $message = '')
{
if (!\is_scalar($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a scalar. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert object $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function object($value, $message = '')
{
if (!\is_object($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an object. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert resource $value
*
* @param mixed $value
* @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function resource($value, $type = null, $message = '')
{
if (!\is_resource($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a resource. Got: %s',
static::typeToString($value)
));
}
if ($type && $type !== \get_resource_type($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a resource of type %2$s. Got: %s',
static::typeToString($value),
$type
));
}
}
/**
* @psalm-pure
* @psalm-assert callable $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isCallable($value, $message = '')
{
if (!\is_callable($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a callable. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert array $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isArray($value, $message = '')
{
if (!\is_array($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert iterable $value
*
* @deprecated use "isIterable" or "isInstanceOf" instead
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isTraversable($value, $message = '')
{
@\trigger_error(
\sprintf(
'The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.',
__METHOD__
),
\E_USER_DEPRECATED
);
if (!\is_array($value) && !($value instanceof Traversable)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a traversable. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert array|ArrayAccess $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isArrayAccessible($value, $message = '')
{
if (!\is_array($value) && !($value instanceof ArrayAccess)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array accessible. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert countable $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isCountable($value, $message = '')
{
if (
!\is_array($value)
&& !($value instanceof Countable)
&& !($value instanceof ResourceBundle)
&& !($value instanceof SimpleXMLElement)
) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a countable. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert iterable $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isIterable($value, $message = '')
{
if (!\is_array($value) && !($value instanceof Traversable)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an iterable. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert ExpectedType $value
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isInstanceOf($value, $class, $message = '')
{
if (!($value instanceof $class)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an instance of %2$s. Got: %s',
static::typeToString($value),
$class
));
}
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert !ExpectedType $value
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notInstanceOf($value, $class, $message = '')
{
if ($value instanceof $class) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an instance other than %2$s. Got: %s',
static::typeToString($value),
$class
));
}
}
/**
* @psalm-pure
* @psalm-param array<class-string> $classes
*
* @param mixed $value
* @param array<object|string> $classes
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isInstanceOfAny($value, array $classes, $message = '')
{
foreach ($classes as $class) {
if ($value instanceof $class) {
return;
}
}
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an instance of any of %2$s. Got: %s',
static::typeToString($value),
\implode(', ', \array_map(array(static::class, 'valueToString'), $classes))
));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert ExpectedType|class-string<ExpectedType> $value
*
* @param object|string $value
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isAOf($value, $class, $message = '')
{
static::string($class, 'Expected class as a string. Got: %s');
if (!\is_a($value, $class, \is_string($value))) {
static::reportInvalidArgument(sprintf(
$message ?: 'Expected an instance of this class or to this class among its parents "%2$s". Got: %s',
static::valueToString($value),
$class
));
}
}
/**
* @psalm-pure
* @psalm-template UnexpectedType of object
* @psalm-param class-string<UnexpectedType> $class
* @psalm-assert !UnexpectedType $value
* @psalm-assert !class-string<UnexpectedType> $value
*
* @param object|string $value
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isNotA($value, $class, $message = '')
{
static::string($class, 'Expected class as a string. Got: %s');
if (\is_a($value, $class, \is_string($value))) {
static::reportInvalidArgument(sprintf(
$message ?: 'Expected an instance of this class or to this class among its parents other than "%2$s". Got: %s',
static::valueToString($value),
$class
));
}
}
/**
* @psalm-pure
* @psalm-param array<class-string> $classes
*
* @param object|string $value
* @param string[] $classes
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isAnyOf($value, array $classes, $message = '')
{
foreach ($classes as $class) {
static::string($class, 'Expected class as a string. Got: %s');
if (\is_a($value, $class, \is_string($value))) {
return;
}
}
static::reportInvalidArgument(sprintf(
$message ?: 'Expected an instance of any of this classes or any of those classes among their parents "%2$s". Got: %s',
static::valueToString($value),
\implode(', ', $classes)
));
}
/**
* @psalm-pure
* @psalm-assert empty $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isEmpty($value, $message = '')
{
if (!empty($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an empty value. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert !empty $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notEmpty($value, $message = '')
{
if (empty($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a non-empty value. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function null($value, $message = '')
{
if (null !== $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected null. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert !null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notNull($value, $message = '')
{
if (null === $value) {
static::reportInvalidArgument(
$message ?: 'Expected a value other than null.'
);
}
}
/**
* @psalm-pure
* @psalm-assert true $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function true($value, $message = '')
{
if (true !== $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be true. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert false $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function false($value, $message = '')
{
if (false !== $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be false. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert !false $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notFalse($value, $message = '')
{
if (false === $value) {
static::reportInvalidArgument(
$message ?: 'Expected a value other than false.'
);
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function ip($value, $message = '')
{
if (false === \filter_var($value, \FILTER_VALIDATE_IP)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be an IP. Got: %s',
static::valueToString($value)
));
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function ipv4($value, $message = '')
{
if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be an IPv4. Got: %s',
static::valueToString($value)
));
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function ipv6($value, $message = '')
{
if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be an IPv6. Got: %s',
static::valueToString($value)
));
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function email($value, $message = '')
{
if (false === \filter_var($value, FILTER_VALIDATE_EMAIL)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be a valid e-mail address. Got: %s',
static::valueToString($value)
));
}
}
/**
* Does non strict comparisons on the items, so ['3', 3] will not pass the assertion.
*
* @param array $values
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function uniqueValues(array $values, $message = '')
{
$allValues = \count($values);
$uniqueValues = \count(\array_unique($values));
if ($allValues !== $uniqueValues) {
$difference = $allValues - $uniqueValues;
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array of unique values, but %s of them %s duplicated',
$difference,
(1 === $difference ? 'is' : 'are')
));
}
}
/**
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function eq($value, $expect, $message = '')
{
if ($expect != $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value equal to %2$s. Got: %s',
static::valueToString($value),
static::valueToString($expect)
));
}
}
/**
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notEq($value, $expect, $message = '')
{
if ($expect == $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a different value than %s.',
static::valueToString($expect)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function same($value, $expect, $message = '')
{
if ($expect !== $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value identical to %2$s. Got: %s',
static::valueToString($value),
static::valueToString($expect)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notSame($value, $expect, $message = '')
{
if ($expect === $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value not identical to %s.',
static::valueToString($expect)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function greaterThan($value, $limit, $message = '')
{
if ($value <= $limit) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value greater than %2$s. Got: %s',
static::valueToString($value),
static::valueToString($limit)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function greaterThanEq($value, $limit, $message = '')
{
if ($value < $limit) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value greater than or equal to %2$s. Got: %s',
static::valueToString($value),
static::valueToString($limit)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function lessThan($value, $limit, $message = '')
{
if ($value >= $limit) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value less than %2$s. Got: %s',
static::valueToString($value),
static::valueToString($limit)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function lessThanEq($value, $limit, $message = '')
{
if ($value > $limit) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value less than or equal to %2$s. Got: %s',
static::valueToString($value),
static::valueToString($limit)
));
}
}
/**
* Inclusive range, so Assert::(3, 3, 5) passes.
*
* @psalm-pure
*
* @param mixed $value
* @param mixed $min
* @param mixed $max
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function range($value, $min, $max, $message = '')
{
if ($value < $min || $value > $max) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value between %2$s and %3$s. Got: %s',
static::valueToString($value),
static::valueToString($min),
static::valueToString($max)
));
}
}
/**
* A more human-readable alias of Assert::inArray().
*
* @psalm-pure
*
* @param mixed $value
* @param array $values
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function oneOf($value, array $values, $message = '')
{
static::inArray($value, $values, $message);
}
/**
* Does strict comparison, so Assert::inArray(3, ['3']) does not pass the assertion.
*
* @psalm-pure
*
* @param mixed $value
* @param array $values
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function inArray($value, array $values, $message = '')
{
if (!\in_array($value, $values, true)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected one of: %2$s. Got: %s',
static::valueToString($value),
\implode(', ', \array_map(array(static::class, 'valueToString'), $values))
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $subString
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function contains($value, $subString, $message = '')
{
if (false === \strpos($value, $subString)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain %2$s. Got: %s',
static::valueToString($value),
static::valueToString($subString)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $subString
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notContains($value, $subString, $message = '')
{
if (false !== \strpos($value, $subString)) {
static::reportInvalidArgument(\sprintf(
$message ?: '%2$s was not expected to be contained in a value. Got: %s',
static::valueToString($value),
static::valueToString($subString)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notWhitespaceOnly($value, $message = '')
{
if (\preg_match('/^\s*$/', $value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a non-whitespace string. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $prefix
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function startsWith($value, $prefix, $message = '')
{
if (0 !== \strpos($value, $prefix)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to start with %2$s. Got: %s',
static::valueToString($value),
static::valueToString($prefix)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $prefix
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notStartsWith($value, $prefix, $message = '')
{
if (0 === \strpos($value, $prefix)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value not to start with %2$s. Got: %s',
static::valueToString($value),
static::valueToString($prefix)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function startsWithLetter($value, $message = '')
{
static::string($value);
$valid = isset($value[0]);
if ($valid) {
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = \ctype_alpha($value[0]);
\setlocale(LC_CTYPE, $locale);
}
if (!$valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to start with a letter. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $suffix
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function endsWith($value, $suffix, $message = '')
{
if ($suffix !== \substr($value, -\strlen($suffix))) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to end with %2$s. Got: %s',
static::valueToString($value),
static::valueToString($suffix)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $suffix
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notEndsWith($value, $suffix, $message = '')
{
if ($suffix === \substr($value, -\strlen($suffix))) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value not to end with %2$s. Got: %s',
static::valueToString($value),
static::valueToString($suffix)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $pattern
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function regex($value, $pattern, $message = '')
{
if (!\preg_match($pattern, $value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The value %s does not match the expected pattern.',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $pattern
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notRegex($value, $pattern, $message = '')
{
if (\preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The value %s matches the pattern %s (at offset %d).',
static::valueToString($value),
static::valueToString($pattern),
$matches[0][1]
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function unicodeLetters($value, $message = '')
{
static::string($value);
if (!\preg_match('/^\p{L}+$/u', $value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain only Unicode letters. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function alpha($value, $message = '')
{
static::string($value);
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = !\ctype_alpha($value);
\setlocale(LC_CTYPE, $locale);
if ($valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain only letters. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function digits($value, $message = '')
{
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = !\ctype_digit($value);
\setlocale(LC_CTYPE, $locale);
if ($valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain digits only. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function alnum($value, $message = '')
{
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = !\ctype_alnum($value);
\setlocale(LC_CTYPE, $locale);
if ($valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain letters and digits only. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert lowercase-string $value
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function lower($value, $message = '')
{
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = !\ctype_lower($value);
\setlocale(LC_CTYPE, $locale);
if ($valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain lowercase characters only. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert !lowercase-string $value
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function upper($value, $message = '')
{
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = !\ctype_upper($value);
\setlocale(LC_CTYPE, $locale);
if ($valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain uppercase characters only. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param int $length
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function length($value, $length, $message = '')
{
if ($length !== static::strlen($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain %2$s characters. Got: %s',
static::valueToString($value),
$length
));
}
}
/**
* Inclusive min.
*
* @psalm-pure
*
* @param string $value
* @param int|float $min
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function minLength($value, $min, $message = '')
{
if (static::strlen($value) < $min) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain at least %2$s characters. Got: %s',
static::valueToString($value),
$min
));
}
}
/**
* Inclusive max.
*
* @psalm-pure
*
* @param string $value
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function maxLength($value, $max, $message = '')
{
if (static::strlen($value) > $max) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain at most %2$s characters. Got: %s',
static::valueToString($value),
$max
));
}
}
/**
* Inclusive , so Assert::lengthBetween('asd', 3, 5); passes the assertion.
*
* @psalm-pure
*
* @param string $value
* @param int|float $min
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function lengthBetween($value, $min, $max, $message = '')
{
$length = static::strlen($value);
if ($length < $min || $length > $max) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s',
static::valueToString($value),
$min,
$max
));
}
}
/**
* Will also pass if $value is a directory, use Assert::file() instead if you need to be sure it is a file.
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function fileExists($value, $message = '')
{
static::string($value);
if (!\file_exists($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The file %s does not exist.',
static::valueToString($value)
));
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function file($value, $message = '')
{
static::fileExists($value, $message);
if (!\is_file($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The path %s is not a file.',
static::valueToString($value)
));
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function directory($value, $message = '')
{
static::fileExists($value, $message);
if (!\is_dir($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The path %s is no directory.',
static::valueToString($value)
));
}
}
/**
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function readable($value, $message = '')
{
if (!\is_readable($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The path %s is not readable.',
static::valueToString($value)
));
}
}
/**
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function writable($value, $message = '')
{
if (!\is_writable($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The path %s is not writable.',
static::valueToString($value)
));
}
}
/**
* @psalm-assert class-string $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function classExists($value, $message = '')
{
if (!\class_exists($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an existing class name. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert class-string<ExpectedType>|ExpectedType $value
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function subclassOf($value, $class, $message = '')
{
if (!\is_subclass_of($value, $class)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a sub-class of %2$s. Got: %s',
static::valueToString($value),
static::valueToString($class)
));
}
}
/**
* @psalm-assert class-string $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function interfaceExists($value, $message = '')
{
if (!\interface_exists($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an existing interface name. got %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $interface
* @psalm-assert class-string<ExpectedType> $value
*
* @param mixed $value
* @param mixed $interface
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function implementsInterface($value, $interface, $message = '')
{
if (!\in_array($interface, \class_implements($value))) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an implementation of %2$s. Got: %s',
static::valueToString($value),
static::valueToString($interface)
));
}
}
/**
* @psalm-pure
* @psalm-param class-string|object $classOrObject
*
* @param string|object $classOrObject
* @param mixed $property
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function propertyExists($classOrObject, $property, $message = '')
{
if (!\property_exists($classOrObject, $property)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the property %s to exist.',
static::valueToString($property)
));
}
}
/**
* @psalm-pure
* @psalm-param class-string|object $classOrObject
*
* @param string|object $classOrObject
* @param mixed $property
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function propertyNotExists($classOrObject, $property, $message = '')
{
if (\property_exists($classOrObject, $property)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the property %s to not exist.',
static::valueToString($property)
));
}
}
/**
* @psalm-pure
* @psalm-param class-string|object $classOrObject
*
* @param string|object $classOrObject
* @param mixed $method
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function methodExists($classOrObject, $method, $message = '')
{
if (!(\is_string($classOrObject) || \is_object($classOrObject)) || !\method_exists($classOrObject, $method)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the method %s to exist.',
static::valueToString($method)
));
}
}
/**
* @psalm-pure
* @psalm-param class-string|object $classOrObject
*
* @param string|object $classOrObject
* @param mixed $method
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function methodNotExists($classOrObject, $method, $message = '')
{
if ((\is_string($classOrObject) || \is_object($classOrObject)) && \method_exists($classOrObject, $method)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the method %s to not exist.',
static::valueToString($method)
));
}
}
/**
* @psalm-pure
*
* @param array $array
* @param string|int $key
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function keyExists($array, $key, $message = '')
{
if (!(isset($array[$key]) || \array_key_exists($key, $array))) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the key %s to exist.',
static::valueToString($key)
));
}
}
/**
* @psalm-pure
*
* @param array $array
* @param string|int $key
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function keyNotExists($array, $key, $message = '')
{
if (isset($array[$key]) || \array_key_exists($key, $array)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the key %s to not exist.',
static::valueToString($key)
));
}
}
/**
* Checks if a value is a valid array key (int or string).
*
* @psalm-pure
* @psalm-assert array-key $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function validArrayKey($value, $message = '')
{
if (!(\is_int($value) || \is_string($value))) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected string or integer. Got: %s',
static::typeToString($value)
));
}
}
/**
* Does not check if $array is countable, this can generate a warning on php versions after 7.2.
*
* @param Countable|array $array
* @param int $number
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function count($array, $number, $message = '')
{
static::eq(
\count($array),
$number,
\sprintf(
$message ?: 'Expected an array to contain %d elements. Got: %d.',
$number,
\count($array)
)
);
}
/**
* Does not check if $array is countable, this can generate a warning on php versions after 7.2.
*
* @param Countable|array $array
* @param int|float $min
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function minCount($array, $min, $message = '')
{
if (\count($array) < $min) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array to contain at least %2$d elements. Got: %d',
\count($array),
$min
));
}
}
/**
* Does not check if $array is countable, this can generate a warning on php versions after 7.2.
*
* @param Countable|array $array
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function maxCount($array, $max, $message = '')
{
if (\count($array) > $max) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array to contain at most %2$d elements. Got: %d',
\count($array),
$max
));
}
}
/**
* Does not check if $array is countable, this can generate a warning on php versions after 7.2.
*
* @param Countable|array $array
* @param int|float $min
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function countBetween($array, $min, $max, $message = '')
{
$count = \count($array);
if ($count < $min || $count > $max) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d',
$count,
$min,
$max
));
}
}
/**
* @psalm-pure
* @psalm-assert list $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isList($array, $message = '')
{
if (!\is_array($array)) {
static::reportInvalidArgument(
$message ?: 'Expected list - non-associative array.'
);
}
if ($array === \array_values($array)) {
return;
}
$nextKey = -1;
foreach ($array as $k => $v) {
if ($k !== ++$nextKey) {
static::reportInvalidArgument(
$message ?: 'Expected list - non-associative array.'
);
}
}
}
/**
* @psalm-pure
* @psalm-assert non-empty-list $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isNonEmptyList($array, $message = '')
{
static::isList($array, $message);
static::notEmpty($array, $message);
}
/**
* @psalm-pure
* @psalm-template T
* @psalm-param mixed|array<T> $array
* @psalm-assert array<string, T> $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isMap($array, $message = '')
{
if (
!\is_array($array) ||
\array_keys($array) !== \array_filter(\array_keys($array), '\is_string')
) {
static::reportInvalidArgument(
$message ?: 'Expected map - associative array with string keys.'
);
}
}
/**
* @psalm-pure
* @psalm-template T
* @psalm-param mixed|array<T> $array
* @psalm-assert array<string, T> $array
* @psalm-assert !empty $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isNonEmptyMap($array, $message = '')
{
static::isMap($array, $message);
static::notEmpty($array, $message);
}
/**
* @psalm-pure
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function uuid($value, $message = '')
{
$value = \str_replace(array('urn:', 'uuid:', '{', '}'), '', $value);
// The nil UUID is special form of UUID that is specified to have all
// 128 bits set to zero.
if ('00000000-0000-0000-0000-000000000000' === $value) {
return;
}
if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Value %s is not a valid UUID.',
static::valueToString($value)
));
}
}
/**
* @psalm-param class-string<Throwable> $class
*
* @param Closure $expression
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function throws(Closure $expression, $class = 'Exception', $message = '')
{
static::string($class);
$actual = 'none';
try {
$expression();
} catch (Exception $e) {
$actual = \get_class($e);
if ($e instanceof $class) {
return;
}
} catch (Throwable $e) {
$actual = \get_class($e);
if ($e instanceof $class) {
return;
}
}
static::reportInvalidArgument($message ?: \sprintf(
'Expected to throw "%s", got "%s"',
$class,
$actual
));
}
/**
* @throws BadMethodCallException
*/
public static function __callStatic($name, $arguments)
{
if ('nullOr' === \substr($name, 0, 6)) {
if (null !== $arguments[0]) {
$method = \lcfirst(\substr($name, 6));
\call_user_func_array(array(static::class, $method), $arguments);
}
return;
}
if ('all' === \substr($name, 0, 3)) {
static::isIterable($arguments[0]);
$method = \lcfirst(\substr($name, 3));
$args = $arguments;
foreach ($arguments[0] as $entry) {
$args[0] = $entry;
\call_user_func_array(array(static::class, $method), $args);
}
return;
}
throw new BadMethodCallException('No such method: '.$name);
}
/**
* @param mixed $value
*
* @return string
*/
protected static function valueToString($value)
{
if (null === $value) {
return 'null';
}
if (true === $value) {
return 'true';
}
if (false === $value) {
return 'false';
}
if (\is_array($value)) {
return 'array';
}
if (\is_object($value)) {
if (\method_exists($value, '__toString')) {
return \get_class($value).': '.self::valueToString($value->__toString());
}
if ($value instanceof DateTime || $value instanceof DateTimeImmutable) {
return \get_class($value).': '.self::valueToString($value->format('c'));
}
return \get_class($value);
}
if (\is_resource($value)) {
return 'resource';
}
if (\is_string($value)) {
return '"'.$value.'"';
}
return (string) $value;
}
/**
* @param mixed $value
*
* @return string
*/
protected static function typeToString($value)
{
return \is_object($value) ? \get_class($value) : \gettype($value);
}
protected static function strlen($value)
{
if (!\function_exists('mb_detect_encoding')) {
return \strlen($value);
}
if (false === $encoding = \mb_detect_encoding($value)) {
return \strlen($value);
}
return \mb_strlen($value, $encoding);
}
/**
* @param string $message
*
* @throws InvalidArgumentException
*
* @psalm-pure this method is not supposed to perform side-effects
* @psalm-return never
*/
protected static function reportInvalidArgument($message)
{
throw new InvalidArgumentException($message);
}
private function __construct()
{
}
}
<?php
/*
* This file is part of the webmozart/assert package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\Assert;
class InvalidArgumentException extends \InvalidArgumentException
{
}
This source diff could not be displayed because it is too large. You can view the blob instead.
preset: symfony
enabled:
- ordered_use
- strict
disabled:
- empty_return
language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
matrix:
include:
- php: 5.3
- php: 5.4
- php: 5.5
- php: 5.6
- php: 5.6
env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable'
- php: hhvm
- php: nightly
allow_failures:
- php: hhvm
- php: nightly
fast_finish: true
install: composer update $COMPOSER_FLAGS -n
script: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover
after_script:
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi;'
Changelog
=========
* 2.3.0 (2015-12-17)
* added `Url::makeRelative()` for calculating relative paths between URLs
* fixed `Path::makeRelative()` to trim leading dots when moving outside of
the base path
* 2.2.3 (2015-10-05)
* fixed `Path::makeRelative()` to produce `..` when called with the parent
directory of a path
* 2.2.2 (2015-08-24)
* `Path::makeAbsolute()` does not fail anymore if an absolute path is passed
with a different root (partition) than the base path
* 2.2.1 (2015-08-24)
* fixed minimum versions in composer.json
* 2.2.0 (2015-08-14)
* added `Path::normalize()`
* 2.1.0 (2015-07-14)
* `Path::canonicalize()` now turns `~` into the user's home directory on
Unix and Windows 8 or later.
* 2.0.0 (2015-05-21)
* added support for streams, e.g. "phar://C:/path/to/file"
* added `Path::join()`
* all `Path` methods now throw exceptions if parameters with invalid types are
passed
* added an internal buffer to `Path::canonicalize()` in order to increase the
performance of the `Path` class
* 1.1.0 (2015-03-19)
* added `Path::getFilename()`
* added `Path::getFilenameWithoutExtension()`
* added `Path::getExtension()`
* added `Path::hasExtension()`
* added `Path::changeExtension()`
* `Path::makeRelative()` now works when the absolute path and the base path
have equal directory names beneath different base directories
(e.g. "/webmozart/css/style.css" relative to "/puli/css")
* 1.0.2 (2015-01-12)
* `Path::makeAbsolute()` fails now if the base path is not absolute
* `Path::makeRelative()` now works when a relative path is passed and the base
path is empty
* 1.0.1 (2014-12-03)
* Added PHP 5.6 to Travis.
* Fixed bug in `Path::makeRelative()` when first argument is shorter than second
* Made HHVM compatibility mandatory in .travis.yml
* Added PHP 5.3.3 to travis.yml
* 1.0.0 (2014-11-26)
* first release
The MIT License (MIT)
Copyright (c) 2014 Bernhard Schussek
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File Path Utility
=================
[![Build Status](https://travis-ci.org/webmozart/path-util.svg?branch=2.3.0)](https://travis-ci.org/webmozart/path-util)
[![Build status](https://ci.appveyor.com/api/projects/status/d5uuypr6p162gpxf/branch/master?svg=true)](https://ci.appveyor.com/project/webmozart/path-util/branch/master)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/webmozart/path-util/badges/quality-score.png?b=2.3.0)](https://scrutinizer-ci.com/g/webmozart/path-util/?branch=2.3.0)
[![Latest Stable Version](https://poser.pugx.org/webmozart/path-util/v/stable.svg)](https://packagist.org/packages/webmozart/path-util)
[![Total Downloads](https://poser.pugx.org/webmozart/path-util/downloads.svg)](https://packagist.org/packages/webmozart/path-util)
[![Dependency Status](https://www.versioneye.com/php/webmozart:path-util/2.3.0/badge.svg)](https://www.versioneye.com/php/webmozart:path-util/2.3.0)
Latest release: [2.3.0](https://packagist.org/packages/webmozart/path-util#2.3.0)
PHP >= 5.3.3
This package provides robust, cross-platform utility functions for normalizing,
comparing and modifying file paths and URLs.
Installation
------------
The utility can be installed with [Composer]:
```
$ composer require webmozart/path-util
```
Usage
-----
Use the `Path` class to handle file paths:
```php
use Webmozart\PathUtil\Path;
echo Path::canonicalize('/var/www/vhost/webmozart/../config.ini');
// => /var/www/vhost/config.ini
echo Path::canonicalize('C:\Programs\Webmozart\..\config.ini');
// => C:/Programs/config.ini
echo Path::canonicalize('~/config.ini');
// => /home/webmozart/config.ini
echo Path::makeAbsolute('config/config.yml', '/var/www/project');
// => /var/www/project/config/config.yml
echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project/uploads');
// => ../config/config.yml
$paths = array(
'/var/www/vhosts/project/httpdocs/config/config.yml',
'/var/www/vhosts/project/httpdocs/images/banana.gif',
'/var/www/vhosts/project/httpdocs/uploads/../images/nicer-banana.gif',
);
Path::getLongestCommonBasePath($paths);
// => /var/www/vhosts/project/httpdocs
Path::getFilename('/views/index.html.twig');
// => index.html.twig
Path::getFilenameWithoutExtension('/views/index.html.twig');
// => index.html
Path::getFilenameWithoutExtension('/views/index.html.twig', 'html.twig');
Path::getFilenameWithoutExtension('/views/index.html.twig', '.html.twig');
// => index
Path::getExtension('/views/index.html.twig');
// => twig
Path::hasExtension('/views/index.html.twig');
// => true
Path::hasExtension('/views/index.html.twig', 'twig');
// => true
Path::hasExtension('/images/profile.jpg', array('jpg', 'png', 'gif'));
// => true
Path::changeExtension('/images/profile.jpeg', 'jpg');
// => /images/profile.jpg
Path::join('phar://C:/Documents', 'projects/my-project.phar', 'composer.json');
// => phar://C:/Documents/projects/my-project.phar/composer.json
Path::getHomeDirectory();
// => /home/webmozart
```
Use the `Url` class to handle URLs:
```php
use Webmozart\PathUtil\Url;
echo Url::makeRelative('http://example.com/css/style.css', 'http://example.com/puli');
// => ../css/style.css
echo Url::makeRelative('http://cdn.example.com/css/style.css', 'http://example.com/puli');
// => http://cdn.example.com/css/style.css
```
Learn more in the [Documentation] and the [API Docs].
Authors
-------
* [Bernhard Schussek] a.k.a. [@webmozart]
* [The Community Contributors]
Documentation
-------------
Read the [Documentation] if you want to learn more about the contained functions.
Contribute
----------
Contributions are always welcome!
* Report any bugs or issues you find on the [issue tracker].
* You can grab the source code at the [Git repository].
Support
-------
If you are having problems, send a mail to bschussek@gmail.com or shout out to
[@webmozart] on Twitter.
License
-------
All contents of this package are licensed under the [MIT license].
[Bernhard Schussek]: http://webmozarts.com
[The Community Contributors]: https://github.com/webmozart/path-util/graphs/contributors
[Composer]: https://getcomposer.org
[Documentation]: docs/usage.md
[API Docs]: https://webmozart.github.io/path-util/api/latest/class-Webmozart.PathUtil.Path.html
[issue tracker]: https://github.com/webmozart/path-util/issues
[Git repository]: https://github.com/webmozart/path-util
[@webmozart]: https://twitter.com/webmozart
[MIT license]: LICENSE
build: false
shallow_clone: true
platform: x86
clone_folder: c:\projects\webmozart\path-util
cache:
- '%LOCALAPPDATA%\Composer\files'
init:
- SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH%
environment:
matrix:
- COMPOSER_FLAGS: ""
- COMPOSER_FLAGS: --prefer-lowest --prefer-stable
install:
- cinst -y OpenSSL.Light
- cinst -y php
- cd c:\tools\php
- copy php.ini-production php.ini /Y
- echo date.timezone="UTC" >> php.ini
- echo extension_dir=ext >> php.ini
- echo extension=php_openssl.dll >> php.ini
- echo extension=php_mbstring.dll >> php.ini
- echo extension=php_fileinfo.dll >> php.ini
- echo memory_limit=1G >> php.ini
- cd c:\projects\webmozart\path-util
- php -r "readfile('http://getcomposer.org/installer');" | php
- php composer.phar update %COMPOSER_FLAGS% --no-interaction --no-progress
test_script:
- cd c:\projects\webmozart\path-util
- vendor\bin\phpunit.bat --verbose
{
"name": "webmozart/path-util",
"description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.",
"license": "MIT",
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"require": {
"php": ">=5.3.3",
"webmozart/assert": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.6",
"sebastian/version": "^1.0.1"
},
"autoload": {
"psr-4": {
"Webmozart\\PathUtil\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Webmozart\\PathUtil\\Tests\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
}
}
Painfree Handling of File Paths
===============================
Dealing with file paths usually involves some difficulties:
* **System Heterogeneity**: File paths look different on different platforms.
UNIX file paths start with a slash ("/"), while Windows file paths start with
a system drive ("C:"). UNIX uses forward slashes, while Windows uses
backslashes by default ("\").
* **Absolute/Relative Paths**: Web applications frequently need to deal with
absolute and relative paths. Converting one to the other properly is tricky
and repetitive.
This package provides few, but robust utility methods to simplify your life
when dealing with file paths.
Canonicalization
----------------
*Canonicalization* is the transformation of a path into a normalized (the
"canonical") format. You can canonicalize a path with `Path::canonicalize()`:
```php
echo Path::canonicalize('/var/www/vhost/webmozart/../config.ini');
// => /var/www/vhost/config.ini
```
The following modifications happen during canonicalization:
* "." segments are removed;
* ".." segments are resolved;
* backslashes ("\") are converted into forward slashes ("/");
* root paths ("/" and "C:/") always terminate with a slash;
* non-root paths never terminate with a slash;
* schemes (such as "phar://") are kept;
* replace "~" with the user's home directory.
You can pass absolute paths and relative paths to `canonicalize()`. When a
relative path is passed, ".." segments at the beginning of the path are kept:
```php
echo Path::canonicalize('../uploads/../config/config.yml');
// => ../config/config.yml
```
Malformed paths are returned unchanged:
```php
echo Path::canonicalize('C:Programs/PHP/php.ini');
// => C:Programs/PHP/php.ini
```
Converting Absolute/Relative Paths
----------------------------------
Absolute/relative paths can be converted with the methods `Path::makeAbsolute()`
and `Path::makeRelative()`.
`makeAbsolute()` expects a relative path and a base path to base that relative
path upon:
```php
echo Path::makeAbsolute('config/config.yml', '/var/www/project');
// => /var/www/project/config/config.yml
```
If an absolute path is passed in the first argument, the absolute path is
returned unchanged:
```php
echo Path::makeAbsolute('/usr/share/lib/config.ini', '/var/www/project');
// => /usr/share/lib/config.ini
```
The method resolves ".." segments, if there are any:
```php
echo Path::makeAbsolute('../config/config.yml', '/var/www/project/uploads');
// => /var/www/project/config/config.yml
```
This method is very useful if you want to be able to accept relative paths (for
example, relative to the root directory of your project) and absolute paths at
the same time.
`makeRelative()` is the inverse operation to `makeAbsolute()`:
```php
echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project');
// => config/config.yml
```
If the path is not within the base path, the method will prepend ".." segments
as necessary:
```php
echo Path::makeRelative('/var/www/project/config/config.yml', '/var/www/project/uploads');
// => ../config/config.yml
```
Use `isAbsolute()` and `isRelative()` to check whether a path is absolute or
relative:
```php
Path::isAbsolute('C:\Programs\PHP\php.ini')
// => true
```
All four methods internally canonicalize the passed path.
Finding Longest Common Base Paths
---------------------------------
When you store absolute file paths on the file system, this leads to a lot of
duplicated information:
```php
return array(
'/var/www/vhosts/project/httpdocs/config/config.yml',
'/var/www/vhosts/project/httpdocs/config/routing.yml',
'/var/www/vhosts/project/httpdocs/config/services.yml',
'/var/www/vhosts/project/httpdocs/images/banana.gif',
'/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif',
);
```
Especially when storing many paths, the amount of duplicated information is
noticeable. You can use `Path::getLongestCommonBasePath()` to check a list of
paths for a common base path:
```php
$paths = array(
'/var/www/vhosts/project/httpdocs/config/config.yml',
'/var/www/vhosts/project/httpdocs/config/routing.yml',
'/var/www/vhosts/project/httpdocs/config/services.yml',
'/var/www/vhosts/project/httpdocs/images/banana.gif',
'/var/www/vhosts/project/httpdocs/uploads/images/nicer-banana.gif',
);
Path::getLongestCommonBasePath($paths);
// => /var/www/vhosts/project/httpdocs
```
Use this path together with `Path::makeRelative()` to shorten the stored paths:
```php
$bp = '/var/www/vhosts/project/httpdocs';
return array(
$bp.'/config/config.yml',
$bp.'/config/routing.yml',
$bp.'/config/services.yml',
$bp.'/images/banana.gif',
$bp.'/uploads/images/nicer-banana.gif',
);
```
`getLongestCommonBasePath()` always returns canonical paths.
Use `Path::isBasePath()` to test whether a path is a base path of another path:
```php
Path::isBasePath("/var/www", "/var/www/project");
// => true
Path::isBasePath("/var/www", "/var/www/project/..");
// => true
Path::isBasePath("/var/www", "/var/www/project/../..");
// => false
```
Finding Directories/Root Directories
------------------------------------
PHP offers the function `dirname()` to obtain the directory path of a file path.
This method has a few quirks:
* `dirname()` does not accept backslashes on UNIX
* `dirname("C:/Programs")` returns "C:", not "C:/"
* `dirname("C:/")` returns ".", not "C:/"
* `dirname("C:")` returns ".", not "C:/"
* `dirname("Programs")` returns ".", not ""
* `dirname()` does not canonicalize the result
`Path::getDirectory()` fixes these shortcomings:
```php
echo Path::getDirectory("C:\Programs");
// => C:/
```
Additionally, you can use `Path::getRoot()` to obtain the root of a path:
```php
echo Path::getRoot("/etc/apache2/sites-available");
// => /
echo Path::getRoot("C:\Programs\Apache\Config");
// => C:/
```
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="Path-Util Test Suite">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<!-- Whitelist for code coverage -->
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
</whitelist>
</filter>
</phpunit>
<?php
/*
* This file is part of the webmozart/path-util package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\PathUtil;
use InvalidArgumentException;
use RuntimeException;
use Webmozart\Assert\Assert;
/**
* Contains utility methods for handling path strings.
*
* The methods in this class are able to deal with both UNIX and Windows paths
* with both forward and backward slashes. All methods return normalized parts
* containing only forward slashes and no excess "." and ".." segments.
*
* @since 1.0
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Thomas Schulz <mail@king2500.net>
*/
final class Path
{
/**
* The number of buffer entries that triggers a cleanup operation.
*/
const CLEANUP_THRESHOLD = 1250;
/**
* The buffer size after the cleanup operation.
*/
const CLEANUP_SIZE = 1000;
/**
* Buffers input/output of {@link canonicalize()}.
*
* @var array
*/
private static $buffer = array();
/**
* The size of the buffer.
*
* @var int
*/
private static $bufferSize = 0;
/**
* Canonicalizes the given path.
*
* During normalization, all slashes are replaced by forward slashes ("/").
* Furthermore, all "." and ".." segments are removed as far as possible.
* ".." segments at the beginning of relative paths are not removed.
*
* ```php
* echo Path::canonicalize("\webmozart\puli\..\css\style.css");
* // => /webmozart/css/style.css
*
* echo Path::canonicalize("../css/./style.css");
* // => ../css/style.css
* ```
*
* This method is able to deal with both UNIX and Windows paths.
*
* @param string $path A path string.
*
* @return string The canonical path.
*
* @since 1.0 Added method.
* @since 2.0 Method now fails if $path is not a string.
* @since 2.1 Added support for `~`.
*/
public static function canonicalize($path)
{
if ('' === $path) {
return '';
}
Assert::string($path, 'The path must be a string. Got: %s');
// This method is called by many other methods in this class. Buffer
// the canonicalized paths to make up for the severe performance
// decrease.
if (isset(self::$buffer[$path])) {
return self::$buffer[$path];
}
// Replace "~" with user's home directory.
if ('~' === $path[0]) {
$path = static::getHomeDirectory().substr($path, 1);
}
$path = str_replace('\\', '/', $path);
list($root, $pathWithoutRoot) = self::split($path);
$parts = explode('/', $pathWithoutRoot);
$canonicalParts = array();
// Collapse "." and "..", if possible
foreach ($parts as $part) {
if ('.' === $part || '' === $part) {
continue;
}
// Collapse ".." with the previous part, if one exists
// Don't collapse ".." if the previous part is also ".."
if ('..' === $part && count($canonicalParts) > 0
&& '..' !== $canonicalParts[count($canonicalParts) - 1]) {
array_pop($canonicalParts);
continue;
}
// Only add ".." prefixes for relative paths
if ('..' !== $part || '' === $root) {
$canonicalParts[] = $part;
}
}
// Add the root directory again
self::$buffer[$path] = $canonicalPath = $root.implode('/', $canonicalParts);
++self::$bufferSize;
// Clean up regularly to prevent memory leaks
if (self::$bufferSize > self::CLEANUP_THRESHOLD) {
self::$buffer = array_slice(self::$buffer, -self::CLEANUP_SIZE, null, true);
self::$bufferSize = self::CLEANUP_SIZE;
}
return $canonicalPath;
}
/**
* Normalizes the given path.
*
* During normalization, all slashes are replaced by forward slashes ("/").
* Contrary to {@link canonicalize()}, this method does not remove invalid
* or dot path segments. Consequently, it is much more efficient and should
* be used whenever the given path is known to be a valid, absolute system
* path.
*
* This method is able to deal with both UNIX and Windows paths.
*
* @param string $path A path string.
*
* @return string The normalized path.
*
* @since 2.2 Added method.
*/
public static function normalize($path)
{
Assert::string($path, 'The path must be a string. Got: %s');
return str_replace('\\', '/', $path);
}
/**
* Returns the directory part of the path.
*
* This method is similar to PHP's dirname(), but handles various cases
* where dirname() returns a weird result:
*
* - dirname() does not accept backslashes on UNIX
* - dirname("C:/webmozart") returns "C:", not "C:/"
* - dirname("C:/") returns ".", not "C:/"
* - dirname("C:") returns ".", not "C:/"
* - dirname("webmozart") returns ".", not ""
* - dirname() does not canonicalize the result
*
* This method fixes these shortcomings and behaves like dirname()
* otherwise.
*
* The result is a canonical path.
*
* @param string $path A path string.
*
* @return string The canonical directory part. Returns the root directory
* if the root directory is passed. Returns an empty string
* if a relative path is passed that contains no slashes.
* Returns an empty string if an empty string is passed.
*
* @since 1.0 Added method.
* @since 2.0 Method now fails if $path is not a string.
*/
public static function getDirectory($path)
{
if ('' === $path) {
return '';
}
$path = static::canonicalize($path);
// Maintain scheme
if (false !== ($pos = strpos($path, '://'))) {
$scheme = substr($path, 0, $pos + 3);
$path = substr($path, $pos + 3);
} else {
$scheme = '';
}
if (false !== ($pos = strrpos($path, '/'))) {
// Directory equals root directory "/"
if (0 === $pos) {
return $scheme.'/';
}
// Directory equals Windows root "C:/"
if (2 === $pos && ctype_alpha($path[0]) && ':' === $path[1]) {
return $scheme.substr($path, 0, 3);
}
return $scheme.substr($path, 0, $pos);
}
return '';
}
/**
* Returns canonical path of the user's home directory.
*
* Supported operating systems:
*
* - UNIX
* - Windows8 and upper
*
* If your operation system or environment isn't supported, an exception is thrown.
*
* The result is a canonical path.
*
* @return string The canonical home directory
*
* @throws RuntimeException If your operation system or environment isn't supported
*
* @since 2.1 Added method.
*/
public static function getHomeDirectory()
{
// For UNIX support
if (getenv('HOME')) {
return static::canonicalize(getenv('HOME'));
}
// For >= Windows8 support
if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) {
return static::canonicalize(getenv('HOMEDRIVE').getenv('HOMEPATH'));
}
throw new RuntimeException("Your environment or operation system isn't supported");
}
/**
* Returns the root directory of a path.
*
* The result is a canonical path.
*
* @param string $path A path string.
*
* @return string The canonical root directory. Returns an empty string if
* the given path is relative or empty.
*
* @since 1.0 Added method.
* @since 2.0 Method now fails if $path is not a string.
*/
public static function getRoot($path)
{
if ('' === $path) {
return '';
}
Assert::string($path, 'The path must be a string. Got: %s');
// Maintain scheme
if (false !== ($pos = strpos($path, '://'))) {
$scheme = substr($path, 0, $pos + 3);
$path = substr($path, $pos + 3);
} else {
$scheme = '';
}
// UNIX root "/" or "\" (Windows style)
if ('/' === $path[0] || '\\' === $path[0]) {
return $scheme.'/';
}
$length = strlen($path);
// Windows root
if ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
// Special case: "C:"
if (2 === $length) {
return $scheme.$path.'/';
}
// Normal case: "C:/ or "C:\"
if ('/' === $path[2] || '\\' === $path[2]) {
return $scheme.$path[0].$path[1].'/';
}
}
return '';
}
/**
* Returns the file name from a file path.
*
* @param string $path The path string.
*
* @return string The file name.
*
* @since 1.1 Added method.
* @since 2.0 Method now fails if $path is not a string.
*/
public static function getFilename($path)
{
if ('' === $path) {
return '';
}
Assert::string($path, 'The path must be a string. Got: %s');
return basename($path);
}
/**
* Returns the file name without the extension from a file path.
*
* @param string $path The path string.
* @param string|null $extension If specified, only that extension is cut
* off (may contain leading dot).
*
* @return string The file name without extension.
*
* @since 1.1 Added method.
* @since 2.0 Method now fails if $path or $extension have invalid types.
*/
public static function getFilenameWithoutExtension($path, $extension = null)
{
if ('' === $path) {
return '';
}
Assert::string($path, 'The path must be a string. Got: %s');
Assert::nullOrString($extension, 'The extension must be a string or null. Got: %s');
if (null !== $extension) {
// remove extension and trailing dot
return rtrim(basename($path, $extension), '.');
}
return pathinfo($path, PATHINFO_FILENAME);
}
/**
* Returns the extension from a file path.
*
* @param string $path The path string.
* @param bool $forceLowerCase Forces the extension to be lower-case
* (requires mbstring extension for correct
* multi-byte character handling in extension).
*
* @return string The extension of the file path (without leading dot).
*
* @since 1.1 Added method.
* @since 2.0 Method now fails if $path is not a string.
*/
public static function getExtension($path, $forceLowerCase = false)
{
if ('' === $path) {
return '';
}
Assert::string($path, 'The path must be a string. Got: %s');
$extension = pathinfo($path, PATHINFO_EXTENSION);
if ($forceLowerCase) {
$extension = self::toLower($extension);
}
return $extension;
}
/**
* Returns whether the path has an extension.
*
* @param string $path The path string.
* @param string|array|null $extensions If null or not provided, checks if
* an extension exists, otherwise
* checks for the specified extension
* or array of extensions (with or
* without leading dot).
* @param bool $ignoreCase Whether to ignore case-sensitivity
* (requires mbstring extension for
* correct multi-byte character
* handling in the extension).
*
* @return bool Returns `true` if the path has an (or the specified)
* extension and `false` otherwise.
*
* @since 1.1 Added method.
* @since 2.0 Method now fails if $path or $extensions have invalid types.
*/
public static function hasExtension($path, $extensions = null, $ignoreCase = false)
{
if ('' === $path) {
return false;
}
$extensions = is_object($extensions) ? array($extensions) : (array) $extensions;
Assert::allString($extensions, 'The extensions must be strings. Got: %s');
$actualExtension = self::getExtension($path, $ignoreCase);
// Only check if path has any extension
if (empty($extensions)) {
return '' !== $actualExtension;
}
foreach ($extensions as $key => $extension) {
if ($ignoreCase) {
$extension = self::toLower($extension);
}
// remove leading '.' in extensions array
$extensions[$key] = ltrim($extension, '.');
}
return in_array($actualExtension, $extensions);
}
/**
* Changes the extension of a path string.
*
* @param string $path The path string with filename.ext to change.
* @param string $extension New extension (with or without leading dot).
*
* @return string The path string with new file extension.
*
* @since 1.1 Added method.
* @since 2.0 Method now fails if $path or $extension is not a string.
*/
public static function changeExtension($path, $extension)
{
if ('' === $path) {
return '';
}
Assert::string($extension, 'The extension must be a string. Got: %s');
$actualExtension = self::getExtension($path);
$extension = ltrim($extension, '.');
// No extension for paths
if ('/' === substr($path, -1)) {
return $path;
}
// No actual extension in path
if (empty($actualExtension)) {
return $path.('.' === substr($path, -1) ? '' : '.').$extension;
}
return substr($path, 0, -strlen($actualExtension)).$extension;
}
/**
* Returns whether a path is absolute.
*
* @param string $path A path string.
*
* @return bool Returns true if the path is absolute, false if it is
* relative or empty.
*
* @since 1.0 Added method.
* @since 2.0 Method now fails if $path is not a string.
*/
public static function isAbsolute($path)
{
if ('' === $path) {
return false;
}
Assert::string($path, 'The path must be a string. Got: %s');
// Strip scheme
if (false !== ($pos = strpos($path, '://'))) {
$path = substr($path, $pos + 3);
}
// UNIX root "/" or "\" (Windows style)
if ('/' === $path[0] || '\\' === $path[0]) {
return true;
}
// Windows root
if (strlen($path) > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
// Special case: "C:"
if (2 === strlen($path)) {
return true;
}
// Normal case: "C:/ or "C:\"
if ('/' === $path[2] || '\\' === $path[2]) {
return true;
}
}
return false;
}
/**
* Returns whether a path is relative.
*
* @param string $path A path string.
*
* @return bool Returns true if the path is relative or empty, false if
* it is absolute.
*
* @since 1.0 Added method.
* @since 2.0 Method now fails if $path is not a string.
*/
public static function isRelative($path)
{
return !static::isAbsolute($path);
}
/**
* Turns a relative path into an absolute path.
*
* Usually, the relative path is appended to the given base path. Dot
* segments ("." and "..") are removed/collapsed and all slashes turned
* into forward slashes.
*
* ```php
* echo Path::makeAbsolute("../style.css", "/webmozart/puli/css");
* // => /webmozart/puli/style.css
* ```
*
* If an absolute path is passed, that path is returned unless its root
* directory is different than the one of the base path. In that case, an
* exception is thrown.
*
* ```php
* Path::makeAbsolute("/style.css", "/webmozart/puli/css");
* // => /style.css
*
* Path::makeAbsolute("C:/style.css", "C:/webmozart/puli/css");
* // => C:/style.css
*
* Path::makeAbsolute("C:/style.css", "/webmozart/puli/css");
* // InvalidArgumentException
* ```
*
* If the base path is not an absolute path, an exception is thrown.
*
* The result is a canonical path.
*
* @param string $path A path to make absolute.
* @param string $basePath An absolute base path.
*
* @return string An absolute path in canonical form.
*
* @throws InvalidArgumentException If the base path is not absolute or if
* the given path is an absolute path with
* a different root than the base path.
*
* @since 1.0 Added method.
* @since 2.0 Method now fails if $path or $basePath is not a string.
* @since 2.2.2 Method does not fail anymore of $path and $basePath are
* absolute, but on different partitions.
*/
public static function makeAbsolute($path, $basePath)
{
Assert::stringNotEmpty($basePath, 'The base path must be a non-empty string. Got: %s');
if (!static::isAbsolute($basePath)) {
throw new InvalidArgumentException(sprintf(
'The base path "%s" is not an absolute path.',
$basePath
));
}
if (static::isAbsolute($path)) {
return static::canonicalize($path);
}
if (false !== ($pos = strpos($basePath, '://'))) {
$scheme = substr($basePath, 0, $pos + 3);
$basePath = substr($basePath, $pos + 3);
} else {
$scheme = '';
}
return $scheme.self::canonicalize(rtrim($basePath, '/\\').'/'.$path);
}
/**
* Turns a path into a relative path.
*
* The relative path is created relative to the given base path:
*
* ```php
* echo Path::makeRelative("/webmozart/style.css", "/webmozart/puli");
* // => ../style.css
* ```
*
* If a relative path is passed and the base path is absolute, the relative
* path is returned unchanged:
*
* ```php
* Path::makeRelative("style.css", "/webmozart/puli/css");
* // => style.css
* ```
*
* If both paths are relative, the relative path is created with the
* assumption that both paths are relative to the same directory:
*
* ```php
* Path::makeRelative("style.css", "webmozart/puli/css");
* // => ../../../style.css
* ```
*
* If both paths are absolute, their root directory must be the same,
* otherwise an exception is thrown:
*
* ```php
* Path::makeRelative("C:/webmozart/style.css", "/webmozart/puli");
* // InvalidArgumentException
* ```
*
* If the passed path is absolute, but the base path is not, an exception
* is thrown as well:
*
* ```php
* Path::makeRelative("/webmozart/style.css", "webmozart/puli");
* // InvalidArgumentException
* ```
*
* If the base path is not an absolute path, an exception is thrown.
*
* The result is a canonical path.
*
* @param string $path A path to make relative.
* @param string $basePath A base path.
*
* @return string A relative path in canonical form.
*
* @throws InvalidArgumentException If the base path is not absolute or if
* the given path has a different root
* than the base path.
*
* @since 1.0 Added method.
* @since 2.0 Method now fails if $path or $basePath is not a string.
*/
public static function makeRelative($path, $basePath)
{
Assert::string($basePath, 'The base path must be a string. Got: %s');
$path = static::canonicalize($path);
$basePath = static::canonicalize($basePath);
list($root, $relativePath) = self::split($path);
list($baseRoot, $relativeBasePath) = self::split($basePath);
// If the base path is given as absolute path and the path is already
// relative, consider it to be relative to the given absolute path
// already
if ('' === $root && '' !== $baseRoot) {
// If base path is already in its root
if ('' === $relativeBasePath) {
$relativePath = ltrim($relativePath, './\\');
}
return $relativePath;
}
// If the passed path is absolute, but the base path is not, we
// cannot generate a relative path
if ('' !== $root && '' === $baseRoot) {
throw new InvalidArgumentException(sprintf(
'The absolute path "%s" cannot be made relative to the '.
'relative path "%s". You should provide an absolute base '.
'path instead.',
$path,
$basePath
));
}
// Fail if the roots of the two paths are different
if ($baseRoot && $root !== $baseRoot) {
throw new InvalidArgumentException(sprintf(
'The path "%s" cannot be made relative to "%s", because they '.
'have different roots ("%s" and "%s").',
$path,
$basePath,
$root,
$baseRoot
));
}
if ('' === $relativeBasePath) {
return $relativePath;
}
// Build a "../../" prefix with as many "../" parts as necessary
$parts = explode('/', $relativePath);
$baseParts = explode('/', $relativeBasePath);
$dotDotPrefix = '';
// Once we found a non-matching part in the prefix, we need to add
// "../" parts for all remaining parts
$match = true;
foreach ($baseParts as $i => $basePart) {
if ($match && isset($parts[$i]) && $basePart === $parts[$i]) {
unset($parts[$i]);
continue;
}
$match = false;
$dotDotPrefix .= '../';
}
return rtrim($dotDotPrefix.implode('/', $parts), '/');
}
/**
* Returns whether the given path is on the local filesystem.
*
* @param string $path A path string.
*
* @return bool Returns true if the path is local, false for a URL.
*
* @since 1.0 Added method.
* @since 2.0 Method now fails if $path is not a string.
*/
public static function isLocal($path)
{
Assert::string($path, 'The path must be a string. Got: %s');
return '' !== $path && false === strpos($path, '://');
}
/**
* Returns the longest common base path of a set of paths.
*
* Dot segments ("." and "..") are removed/collapsed and all slashes turned
* into forward slashes.
*
* ```php
* $basePath = Path::getLongestCommonBasePath(array(
* '/webmozart/css/style.css',
* '/webmozart/css/..'
* ));
* // => /webmozart
* ```
*
* The root is returned if no common base path can be found:
*
* ```php
* $basePath = Path::getLongestCommonBasePath(array(
* '/webmozart/css/style.css',
* '/puli/css/..'
* ));
* // => /
* ```
*
* If the paths are located on different Windows partitions, `null` is
* returned.
*
* ```php
* $basePath = Path::getLongestCommonBasePath(array(
* 'C:/webmozart/css/style.css',
* 'D:/webmozart/css/..'
* ));
* // => null
* ```
*
* @param array $paths A list of paths.
*
* @return string|null The longest common base path in canonical form or
* `null` if the paths are on different Windows
* partitions.
*
* @since 1.0 Added method.
* @since 2.0 Method now fails if $paths are not strings.
*/
public static function getLongestCommonBasePath(array $paths)
{
Assert::allString($paths, 'The paths must be strings. Got: %s');
list($bpRoot, $basePath) = self::split(self::canonicalize(reset($paths)));
for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) {
list($root, $path) = self::split(self::canonicalize(current($paths)));
// If we deal with different roots (e.g. C:/ vs. D:/), it's time
// to quit
if ($root !== $bpRoot) {
return null;
}
// Make the base path shorter until it fits into path
while (true) {
if ('.' === $basePath) {
// No more base paths
$basePath = '';
// Next path
continue 2;
}
// Prevent false positives for common prefixes
// see isBasePath()
if (0 === strpos($path.'/', $basePath.'/')) {
// Next path
continue 2;
}
$basePath = dirname($basePath);
}
}
return $bpRoot.$basePath;
}
/**
* Joins two or more path strings.
*
* The result is a canonical path.
*
* @param string[]|string $paths Path parts as parameters or array.
*
* @return string The joint path.
*
* @since 2.0 Added method.
*/
public static function join($paths)
{
if (!is_array($paths)) {
$paths = func_get_args();
}
Assert::allString($paths, 'The paths must be strings. Got: %s');
$finalPath = null;
$wasScheme = false;
foreach ($paths as $path) {
$path = (string) $path;
if ('' === $path) {
continue;
}
if (null === $finalPath) {
// For first part we keep slashes, like '/top', 'C:\' or 'phar://'
$finalPath = $path;
$wasScheme = (strpos($path, '://') !== false);
continue;
}
// Only add slash if previous part didn't end with '/' or '\'
if (!in_array(substr($finalPath, -1), array('/', '\\'))) {
$finalPath .= '/';
}
// If first part included a scheme like 'phar://' we allow current part to start with '/', otherwise trim
$finalPath .= $wasScheme ? $path : ltrim($path, '/');
$wasScheme = false;
}
if (null === $finalPath) {
return '';
}
return self::canonicalize($finalPath);
}
/**
* Returns whether a path is a base path of another path.
*
* Dot segments ("." and "..") are removed/collapsed and all slashes turned
* into forward slashes.
*
* ```php
* Path::isBasePath('/webmozart', '/webmozart/css');
* // => true
*
* Path::isBasePath('/webmozart', '/webmozart');
* // => true
*
* Path::isBasePath('/webmozart', '/webmozart/..');
* // => false
*
* Path::isBasePath('/webmozart', '/puli');
* // => false
* ```
*
* @param string $basePath The base path to test.
* @param string $ofPath The other path.
*
* @return bool Whether the base path is a base path of the other path.
*
* @since 1.0 Added method.
* @since 2.0 Method now fails if $basePath or $ofPath is not a string.
*/
public static function isBasePath($basePath, $ofPath)
{
Assert::string($basePath, 'The base path must be a string. Got: %s');
$basePath = self::canonicalize($basePath);
$ofPath = self::canonicalize($ofPath);
// Append slashes to prevent false positives when two paths have
// a common prefix, for example /base/foo and /base/foobar.
// Don't append a slash for the root "/", because then that root
// won't be discovered as common prefix ("//" is not a prefix of
// "/foobar/").
return 0 === strpos($ofPath.'/', rtrim($basePath, '/').'/');
}
/**
* Splits a part into its root directory and the remainder.
*
* If the path has no root directory, an empty root directory will be
* returned.
*
* If the root directory is a Windows style partition, the resulting root
* will always contain a trailing slash.
*
* list ($root, $path) = Path::split("C:/webmozart")
* // => array("C:/", "webmozart")
*
* list ($root, $path) = Path::split("C:")
* // => array("C:/", "")
*
* @param string $path The canonical path to split.
*
* @return string[] An array with the root directory and the remaining
* relative path.
*/
private static function split($path)
{
if ('' === $path) {
return array('', '');
}
// Remember scheme as part of the root, if any
if (false !== ($pos = strpos($path, '://'))) {
$root = substr($path, 0, $pos + 3);
$path = substr($path, $pos + 3);
} else {
$root = '';
}
$length = strlen($path);
// Remove and remember root directory
if ('/' === $path[0]) {
$root .= '/';
$path = $length > 1 ? substr($path, 1) : '';
} elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
if (2 === $length) {
// Windows special case: "C:"
$root .= $path.'/';
$path = '';
} elseif ('/' === $path[2]) {
// Windows normal case: "C:/"..
$root .= substr($path, 0, 3);
$path = $length > 3 ? substr($path, 3) : '';
}
}
return array($root, $path);
}
/**
* Converts string to lower-case (multi-byte safe if mbstring is installed).
*
* @param string $str The string
*
* @return string Lower case string
*/
private static function toLower($str)
{
if (function_exists('mb_strtolower')) {
return mb_strtolower($str, mb_detect_encoding($str));
}
return strtolower($str);
}
private function __construct()
{
}
}
<?php
/*
* This file is part of the webmozart/path-util package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\PathUtil;
use InvalidArgumentException;
use Webmozart\Assert\Assert;
/**
* Contains utility methods for handling URL strings.
*
* The methods in this class are able to deal with URLs.
*
* @since 2.3
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Claudio Zizza <claudio@budgegeria.de>
*/
final class Url
{
/**
* Turns a URL into a relative path.
*
* The result is a canonical path. This class is using functionality of Path class.
*
* @see Path
*
* @param string $url A URL to make relative.
* @param string $baseUrl A base URL.
*
* @return string
*
* @throws InvalidArgumentException If the URL and base URL does
* not match.
*/
public static function makeRelative($url, $baseUrl)
{
Assert::string($url, 'The URL must be a string. Got: %s');
Assert::string($baseUrl, 'The base URL must be a string. Got: %s');
Assert::contains($baseUrl, '://', '%s is not an absolute Url.');
list($baseHost, $basePath) = self::split($baseUrl);
if (false === strpos($url, '://')) {
if (0 === strpos($url, '/')) {
$host = $baseHost;
} else {
$host = '';
}
$path = $url;
} else {
list($host, $path) = self::split($url);
}
if ('' !== $host && $host !== $baseHost) {
throw new InvalidArgumentException(sprintf(
'The URL "%s" cannot be made relative to "%s" since their host names are different.',
$host,
$baseHost
));
}
return Path::makeRelative($path, $basePath);
}
/**
* Splits a URL into its host and the path.
*
* ```php
* list ($root, $path) = Path::split("http://example.com/webmozart")
* // => array("http://example.com", "/webmozart")
*
* list ($root, $path) = Path::split("http://example.com")
* // => array("http://example.com", "")
* ```
*
* @param string $url The URL to split.
*
* @return string[] An array with the host and the path of the URL.
*
* @throws InvalidArgumentException If $url is not a URL.
*/
private static function split($url)
{
$pos = strpos($url, '://');
$scheme = substr($url, 0, $pos + 3);
$url = substr($url, $pos + 3);
if (false !== ($pos = strpos($url, '/'))) {
$host = substr($url, 0, $pos);
$url = substr($url, $pos);
} else {
// No path, only host
$host = $url;
$url = '/';
}
// At this point, we have $scheme, $host and $path
$root = $scheme.$host;
return array($root, $url);
}
}
<?php
/*
* This file is part of the webmozart/path-util package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\PathUtil\Tests;
use Webmozart\PathUtil\Path;
/**
* @since 1.0
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Thomas Schulz <mail@king2500.net>
*/
class PathTest extends \PHPUnit_Framework_TestCase
{
protected $storedEnv = array();
public function setUp()
{
$this->storedEnv['HOME'] = getenv('HOME');
$this->storedEnv['HOMEDRIVE'] = getenv('HOMEDRIVE');
$this->storedEnv['HOMEPATH'] = getenv('HOMEPATH');
putenv('HOME=/home/webmozart');
putenv('HOMEDRIVE=');
putenv('HOMEPATH=');
}
public function tearDown()
{
putenv('HOME='.$this->storedEnv['HOME']);
putenv('HOMEDRIVE='.$this->storedEnv['HOMEDRIVE']);
putenv('HOMEPATH='.$this->storedEnv['HOMEPATH']);
}
public function provideCanonicalizationTests()
{
return array(
// relative paths (forward slash)
array('css/./style.css', 'css/style.css'),
array('css/../style.css', 'style.css'),
array('css/./../style.css', 'style.css'),
array('css/.././style.css', 'style.css'),
array('css/../../style.css', '../style.css'),
array('./css/style.css', 'css/style.css'),
array('../css/style.css', '../css/style.css'),
array('./../css/style.css', '../css/style.css'),
array('.././css/style.css', '../css/style.css'),
array('../../css/style.css', '../../css/style.css'),
array('', ''),
array('.', ''),
array('..', '..'),
array('./..', '..'),
array('../.', '..'),
array('../..', '../..'),
// relative paths (backslash)
array('css\\.\\style.css', 'css/style.css'),
array('css\\..\\style.css', 'style.css'),
array('css\\.\\..\\style.css', 'style.css'),
array('css\\..\\.\\style.css', 'style.css'),
array('css\\..\\..\\style.css', '../style.css'),
array('.\\css\\style.css', 'css/style.css'),
array('..\\css\\style.css', '../css/style.css'),
array('.\\..\\css\\style.css', '../css/style.css'),
array('..\\.\\css\\style.css', '../css/style.css'),
array('..\\..\\css\\style.css', '../../css/style.css'),
// absolute paths (forward slash, UNIX)
array('/css/style.css', '/css/style.css'),
array('/css/./style.css', '/css/style.css'),
array('/css/../style.css', '/style.css'),
array('/css/./../style.css', '/style.css'),
array('/css/.././style.css', '/style.css'),
array('/./css/style.css', '/css/style.css'),
array('/../css/style.css', '/css/style.css'),
array('/./../css/style.css', '/css/style.css'),
array('/.././css/style.css', '/css/style.css'),
array('/../../css/style.css', '/css/style.css'),
// absolute paths (backslash, UNIX)
array('\\css\\style.css', '/css/style.css'),
array('\\css\\.\\style.css', '/css/style.css'),
array('\\css\\..\\style.css', '/style.css'),
array('\\css\\.\\..\\style.css', '/style.css'),
array('\\css\\..\\.\\style.css', '/style.css'),
array('\\.\\css\\style.css', '/css/style.css'),
array('\\..\\css\\style.css', '/css/style.css'),
array('\\.\\..\\css\\style.css', '/css/style.css'),
array('\\..\\.\\css\\style.css', '/css/style.css'),
array('\\..\\..\\css\\style.css', '/css/style.css'),
// absolute paths (forward slash, Windows)
array('C:/css/style.css', 'C:/css/style.css'),
array('C:/css/./style.css', 'C:/css/style.css'),
array('C:/css/../style.css', 'C:/style.css'),
array('C:/css/./../style.css', 'C:/style.css'),
array('C:/css/.././style.css', 'C:/style.css'),
array('C:/./css/style.css', 'C:/css/style.css'),
array('C:/../css/style.css', 'C:/css/style.css'),
array('C:/./../css/style.css', 'C:/css/style.css'),
array('C:/.././css/style.css', 'C:/css/style.css'),
array('C:/../../css/style.css', 'C:/css/style.css'),
// absolute paths (backslash, Windows)
array('C:\\css\\style.css', 'C:/css/style.css'),
array('C:\\css\\.\\style.css', 'C:/css/style.css'),
array('C:\\css\\..\\style.css', 'C:/style.css'),
array('C:\\css\\.\\..\\style.css', 'C:/style.css'),
array('C:\\css\\..\\.\\style.css', 'C:/style.css'),
array('C:\\.\\css\\style.css', 'C:/css/style.css'),
array('C:\\..\\css\\style.css', 'C:/css/style.css'),
array('C:\\.\\..\\css\\style.css', 'C:/css/style.css'),
array('C:\\..\\.\\css\\style.css', 'C:/css/style.css'),
array('C:\\..\\..\\css\\style.css', 'C:/css/style.css'),
// Windows special case
array('C:', 'C:/'),
// Don't change malformed path
array('C:css/style.css', 'C:css/style.css'),
// absolute paths (stream, UNIX)
array('phar:///css/style.css', 'phar:///css/style.css'),
array('phar:///css/./style.css', 'phar:///css/style.css'),
array('phar:///css/../style.css', 'phar:///style.css'),
array('phar:///css/./../style.css', 'phar:///style.css'),
array('phar:///css/.././style.css', 'phar:///style.css'),
array('phar:///./css/style.css', 'phar:///css/style.css'),
array('phar:///../css/style.css', 'phar:///css/style.css'),
array('phar:///./../css/style.css', 'phar:///css/style.css'),
array('phar:///.././css/style.css', 'phar:///css/style.css'),
array('phar:///../../css/style.css', 'phar:///css/style.css'),
// absolute paths (stream, Windows)
array('phar://C:/css/style.css', 'phar://C:/css/style.css'),
array('phar://C:/css/./style.css', 'phar://C:/css/style.css'),
array('phar://C:/css/../style.css', 'phar://C:/style.css'),
array('phar://C:/css/./../style.css', 'phar://C:/style.css'),
array('phar://C:/css/.././style.css', 'phar://C:/style.css'),
array('phar://C:/./css/style.css', 'phar://C:/css/style.css'),
array('phar://C:/../css/style.css', 'phar://C:/css/style.css'),
array('phar://C:/./../css/style.css', 'phar://C:/css/style.css'),
array('phar://C:/.././css/style.css', 'phar://C:/css/style.css'),
array('phar://C:/../../css/style.css', 'phar://C:/css/style.css'),
// paths with "~" UNIX
array('~/css/style.css', '/home/webmozart/css/style.css'),
array('~/css/./style.css', '/home/webmozart/css/style.css'),
array('~/css/../style.css', '/home/webmozart/style.css'),
array('~/css/./../style.css', '/home/webmozart/style.css'),
array('~/css/.././style.css', '/home/webmozart/style.css'),
array('~/./css/style.css', '/home/webmozart/css/style.css'),
array('~/../css/style.css', '/home/css/style.css'),
array('~/./../css/style.css', '/home/css/style.css'),
array('~/.././css/style.css', '/home/css/style.css'),
array('~/../../css/style.css', '/css/style.css'),
);
}
/**
* @dataProvider provideCanonicalizationTests
*/
public function testCanonicalize($path, $canonicalized)
{
$this->assertSame($canonicalized, Path::canonicalize($path));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testCanonicalizeFailsIfInvalidPath()
{
Path::canonicalize(array());
}
public function provideGetDirectoryTests()
{
return array(
array('/webmozart/puli/style.css', '/webmozart/puli'),
array('/webmozart/puli', '/webmozart'),
array('/webmozart', '/'),
array('/', '/'),
array('', ''),
array('\\webmozart\\puli\\style.css', '/webmozart/puli'),
array('\\webmozart\\puli', '/webmozart'),
array('\\webmozart', '/'),
array('\\', '/'),
array('C:/webmozart/puli/style.css', 'C:/webmozart/puli'),
array('C:/webmozart/puli', 'C:/webmozart'),
array('C:/webmozart', 'C:/'),
array('C:/', 'C:/'),
array('C:', 'C:/'),
array('C:\\webmozart\\puli\\style.css', 'C:/webmozart/puli'),
array('C:\\webmozart\\puli', 'C:/webmozart'),
array('C:\\webmozart', 'C:/'),
array('C:\\', 'C:/'),
array('phar:///webmozart/puli/style.css', 'phar:///webmozart/puli'),
array('phar:///webmozart/puli', 'phar:///webmozart'),
array('phar:///webmozart', 'phar:///'),
array('phar:///', 'phar:///'),
array('phar://C:/webmozart/puli/style.css', 'phar://C:/webmozart/puli'),
array('phar://C:/webmozart/puli', 'phar://C:/webmozart'),
array('phar://C:/webmozart', 'phar://C:/'),
array('phar://C:/', 'phar://C:/'),
array('webmozart/puli/style.css', 'webmozart/puli'),
array('webmozart/puli', 'webmozart'),
array('webmozart', ''),
array('webmozart\\puli\\style.css', 'webmozart/puli'),
array('webmozart\\puli', 'webmozart'),
array('webmozart', ''),
array('/webmozart/./puli/style.css', '/webmozart/puli'),
array('/webmozart/../puli/style.css', '/puli'),
array('/webmozart/./../puli/style.css', '/puli'),
array('/webmozart/.././puli/style.css', '/puli'),
array('/webmozart/../../puli/style.css', '/puli'),
array('/.', '/'),
array('/..', '/'),
array('C:webmozart', ''),
);
}
/**
* @dataProvider provideGetDirectoryTests
*/
public function testGetDirectory($path, $directory)
{
$this->assertSame($directory, Path::getDirectory($path));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testGetDirectoryFailsIfInvalidPath()
{
Path::getDirectory(array());
}
public function provideGetFilenameTests()
{
return array(
array('/webmozart/puli/style.css', 'style.css'),
array('/webmozart/puli/STYLE.CSS', 'STYLE.CSS'),
array('/webmozart/puli/style.css/', 'style.css'),
array('/webmozart/puli/', 'puli'),
array('/webmozart/puli', 'puli'),
array('/', ''),
array('', ''),
);
}
/**
* @dataProvider provideGetFilenameTests
*/
public function testGetFilename($path, $filename)
{
$this->assertSame($filename, Path::getFilename($path));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testGetFilenameFailsIfInvalidPath()
{
Path::getFilename(array());
}
public function provideGetFilenameWithoutExtensionTests()
{
return array(
array('/webmozart/puli/style.css.twig', null, 'style.css'),
array('/webmozart/puli/style.css.', null, 'style.css'),
array('/webmozart/puli/style.css', null, 'style'),
array('/webmozart/puli/.style.css', null, '.style'),
array('/webmozart/puli/', null, 'puli'),
array('/webmozart/puli', null, 'puli'),
array('/', null, ''),
array('', null, ''),
array('/webmozart/puli/style.css', 'css', 'style'),
array('/webmozart/puli/style.css', '.css', 'style'),
array('/webmozart/puli/style.css', 'twig', 'style.css'),
array('/webmozart/puli/style.css', '.twig', 'style.css'),
array('/webmozart/puli/style.css', '', 'style.css'),
array('/webmozart/puli/style.css.', '', 'style.css'),
array('/webmozart/puli/style.css.', '.', 'style.css'),
array('/webmozart/puli/style.css.', '.css', 'style.css'),
array('/webmozart/puli/.style.css', 'css', '.style'),
array('/webmozart/puli/.style.css', '.css', '.style'),
);
}
/**
* @dataProvider provideGetFilenameWithoutExtensionTests
*/
public function testGetFilenameWithoutExtension($path, $extension, $filename)
{
$this->assertSame($filename, Path::getFilenameWithoutExtension($path, $extension));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testGetFilenameWithoutExtensionFailsIfInvalidPath()
{
Path::getFilenameWithoutExtension(array(), '.css');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The extension must be a string or null. Got: array
*/
public function testGetFilenameWithoutExtensionFailsIfInvalidExtension()
{
Path::getFilenameWithoutExtension('/style.css', array());
}
public function provideGetExtensionTests()
{
$tests = array(
array('/webmozart/puli/style.css.twig', false, 'twig'),
array('/webmozart/puli/style.css', false, 'css'),
array('/webmozart/puli/style.css.', false, ''),
array('/webmozart/puli/', false, ''),
array('/webmozart/puli', false, ''),
array('/', false, ''),
array('', false, ''),
array('/webmozart/puli/style.CSS', false, 'CSS'),
array('/webmozart/puli/style.CSS', true, 'css'),
array('/webmozart/puli/style.ÄÖÜ', false, 'ÄÖÜ'),
);
if (extension_loaded('mbstring')) {
// This can only be tested, when mbstring is installed
$tests[] = array('/webmozart/puli/style.ÄÖÜ', true, 'äöü');
}
return $tests;
}
/**
* @dataProvider provideGetExtensionTests
*/
public function testGetExtension($path, $forceLowerCase, $extension)
{
$this->assertSame($extension, Path::getExtension($path, $forceLowerCase));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testGetExtensionFailsIfInvalidPath()
{
Path::getExtension(array());
}
public function provideHasExtensionTests()
{
$tests = array(
array(true, '/webmozart/puli/style.css.twig', null, false),
array(true, '/webmozart/puli/style.css', null, false),
array(false, '/webmozart/puli/style.css.', null, false),
array(false, '/webmozart/puli/', null, false),
array(false, '/webmozart/puli', null, false),
array(false, '/', null, false),
array(false, '', null, false),
array(true, '/webmozart/puli/style.css.twig', 'twig', false),
array(false, '/webmozart/puli/style.css.twig', 'css', false),
array(true, '/webmozart/puli/style.css', 'css', false),
array(true, '/webmozart/puli/style.css', '.css', false),
array(true, '/webmozart/puli/style.css.', '', false),
array(false, '/webmozart/puli/', 'ext', false),
array(false, '/webmozart/puli', 'ext', false),
array(false, '/', 'ext', false),
array(false, '', 'ext', false),
array(false, '/webmozart/puli/style.css', 'CSS', false),
array(true, '/webmozart/puli/style.css', 'CSS', true),
array(false, '/webmozart/puli/style.CSS', 'css', false),
array(true, '/webmozart/puli/style.CSS', 'css', true),
array(true, '/webmozart/puli/style.ÄÖÜ', 'ÄÖÜ', false),
array(true, '/webmozart/puli/style.css', array('ext', 'css'), false),
array(true, '/webmozart/puli/style.css', array('.ext', '.css'), false),
array(true, '/webmozart/puli/style.css.', array('ext', ''), false),
array(false, '/webmozart/puli/style.css', array('foo', 'bar', ''), false),
array(false, '/webmozart/puli/style.css', array('.foo', '.bar', ''), false),
);
if (extension_loaded('mbstring')) {
// This can only be tested, when mbstring is installed
$tests[] = array(true, '/webmozart/puli/style.ÄÖÜ', 'äöü', true);
$tests[] = array(true, '/webmozart/puli/style.ÄÖÜ', array('äöü'), true);
}
return $tests;
}
/**
* @dataProvider provideHasExtensionTests
*/
public function testHasExtension($hasExtension, $path, $extension, $ignoreCase)
{
$this->assertSame($hasExtension, Path::hasExtension($path, $extension, $ignoreCase));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testHasExtensionFailsIfInvalidPath()
{
Path::hasExtension(array());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The extensions must be strings. Got: stdClass
*/
public function testHasExtensionFailsIfInvalidExtension()
{
Path::hasExtension('/style.css', (object) array());
}
public function provideChangeExtensionTests()
{
return array(
array('/webmozart/puli/style.css.twig', 'html', '/webmozart/puli/style.css.html'),
array('/webmozart/puli/style.css', 'sass', '/webmozart/puli/style.sass'),
array('/webmozart/puli/style.css', '.sass', '/webmozart/puli/style.sass'),
array('/webmozart/puli/style.css', '', '/webmozart/puli/style.'),
array('/webmozart/puli/style.css.', 'twig', '/webmozart/puli/style.css.twig'),
array('/webmozart/puli/style.css.', '', '/webmozart/puli/style.css.'),
array('/webmozart/puli/style.css', 'äöü', '/webmozart/puli/style.äöü'),
array('/webmozart/puli/style.äöü', 'css', '/webmozart/puli/style.css'),
array('/webmozart/puli/', 'css', '/webmozart/puli/'),
array('/webmozart/puli', 'css', '/webmozart/puli.css'),
array('/', 'css', '/'),
array('', 'css', ''),
);
}
/**
* @dataProvider provideChangeExtensionTests
*/
public function testChangeExtension($path, $extension, $pathExpected)
{
static $call = 0;
$this->assertSame($pathExpected, Path::changeExtension($path, $extension));
++$call;
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testChangeExtensionFailsIfInvalidPath()
{
Path::changeExtension(array(), '.sass');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The extension must be a string. Got: array
*/
public function testChangeExtensionFailsIfInvalidExtension()
{
Path::changeExtension('/style.css', array());
}
public function provideIsAbsolutePathTests()
{
return array(
array('/css/style.css', true),
array('/', true),
array('css/style.css', false),
array('', false),
array('\\css\\style.css', true),
array('\\', true),
array('css\\style.css', false),
array('C:/css/style.css', true),
array('D:/', true),
array('E:\\css\\style.css', true),
array('F:\\', true),
array('phar:///css/style.css', true),
array('phar:///', true),
// Windows special case
array('C:', true),
// Not considered absolute
array('C:css/style.css', false),
);
}
/**
* @dataProvider provideIsAbsolutePathTests
*/
public function testIsAbsolute($path, $isAbsolute)
{
$this->assertSame($isAbsolute, Path::isAbsolute($path));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testIsAbsoluteFailsIfInvalidPath()
{
Path::isAbsolute(array());
}
/**
* @dataProvider provideIsAbsolutePathTests
*/
public function testIsRelative($path, $isAbsolute)
{
$this->assertSame(!$isAbsolute, Path::isRelative($path));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testIsRelativeFailsIfInvalidPath()
{
Path::isRelative(array());
}
public function provideGetRootTests()
{
return array(
array('/css/style.css', '/'),
array('/', '/'),
array('css/style.css', ''),
array('', ''),
array('\\css\\style.css', '/'),
array('\\', '/'),
array('css\\style.css', ''),
array('C:/css/style.css', 'C:/'),
array('C:/', 'C:/'),
array('C:', 'C:/'),
array('D:\\css\\style.css', 'D:/'),
array('D:\\', 'D:/'),
array('phar:///css/style.css', 'phar:///'),
array('phar:///', 'phar:///'),
array('phar://C:/css/style.css', 'phar://C:/'),
array('phar://C:/', 'phar://C:/'),
array('phar://C:', 'phar://C:/'),
);
}
/**
* @dataProvider provideGetRootTests
*/
public function testGetRoot($path, $root)
{
$this->assertSame($root, Path::getRoot($path));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testGetRootFailsIfInvalidPath()
{
Path::getRoot(array());
}
public function providePathTests()
{
return array(
// relative to absolute path
array('css/style.css', '/webmozart/puli', '/webmozart/puli/css/style.css'),
array('../css/style.css', '/webmozart/puli', '/webmozart/css/style.css'),
array('../../css/style.css', '/webmozart/puli', '/css/style.css'),
// relative to root
array('css/style.css', '/', '/css/style.css'),
array('css/style.css', 'C:', 'C:/css/style.css'),
array('css/style.css', 'C:/', 'C:/css/style.css'),
// same sub directories in different base directories
array('../../puli/css/style.css', '/webmozart/css', '/puli/css/style.css'),
array('', '/webmozart/puli', '/webmozart/puli'),
array('..', '/webmozart/puli', '/webmozart'),
);
}
public function provideMakeAbsoluteTests()
{
return array_merge($this->providePathTests(), array(
// collapse dots
array('css/./style.css', '/webmozart/puli', '/webmozart/puli/css/style.css'),
array('css/../style.css', '/webmozart/puli', '/webmozart/puli/style.css'),
array('css/./../style.css', '/webmozart/puli', '/webmozart/puli/style.css'),
array('css/.././style.css', '/webmozart/puli', '/webmozart/puli/style.css'),
array('./css/style.css', '/webmozart/puli', '/webmozart/puli/css/style.css'),
array('css\\.\\style.css', '\\webmozart\\puli', '/webmozart/puli/css/style.css'),
array('css\\..\\style.css', '\\webmozart\\puli', '/webmozart/puli/style.css'),
array('css\\.\\..\\style.css', '\\webmozart\\puli', '/webmozart/puli/style.css'),
array('css\\..\\.\\style.css', '\\webmozart\\puli', '/webmozart/puli/style.css'),
array('.\\css\\style.css', '\\webmozart\\puli', '/webmozart/puli/css/style.css'),
// collapse dots on root
array('./css/style.css', '/', '/css/style.css'),
array('../css/style.css', '/', '/css/style.css'),
array('../css/./style.css', '/', '/css/style.css'),
array('../css/../style.css', '/', '/style.css'),
array('../css/./../style.css', '/', '/style.css'),
array('../css/.././style.css', '/', '/style.css'),
array('.\\css\\style.css', '\\', '/css/style.css'),
array('..\\css\\style.css', '\\', '/css/style.css'),
array('..\\css\\.\\style.css', '\\', '/css/style.css'),
array('..\\css\\..\\style.css', '\\', '/style.css'),
array('..\\css\\.\\..\\style.css', '\\', '/style.css'),
array('..\\css\\..\\.\\style.css', '\\', '/style.css'),
array('./css/style.css', 'C:/', 'C:/css/style.css'),
array('../css/style.css', 'C:/', 'C:/css/style.css'),
array('../css/./style.css', 'C:/', 'C:/css/style.css'),
array('../css/../style.css', 'C:/', 'C:/style.css'),
array('../css/./../style.css', 'C:/', 'C:/style.css'),
array('../css/.././style.css', 'C:/', 'C:/style.css'),
array('.\\css\\style.css', 'C:\\', 'C:/css/style.css'),
array('..\\css\\style.css', 'C:\\', 'C:/css/style.css'),
array('..\\css\\.\\style.css', 'C:\\', 'C:/css/style.css'),
array('..\\css\\..\\style.css', 'C:\\', 'C:/style.css'),
array('..\\css\\.\\..\\style.css', 'C:\\', 'C:/style.css'),
array('..\\css\\..\\.\\style.css', 'C:\\', 'C:/style.css'),
array('./css/style.css', 'phar:///', 'phar:///css/style.css'),
array('../css/style.css', 'phar:///', 'phar:///css/style.css'),
array('../css/./style.css', 'phar:///', 'phar:///css/style.css'),
array('../css/../style.css', 'phar:///', 'phar:///style.css'),
array('../css/./../style.css', 'phar:///', 'phar:///style.css'),
array('../css/.././style.css', 'phar:///', 'phar:///style.css'),
array('./css/style.css', 'phar://C:/', 'phar://C:/css/style.css'),
array('../css/style.css', 'phar://C:/', 'phar://C:/css/style.css'),
array('../css/./style.css', 'phar://C:/', 'phar://C:/css/style.css'),
array('../css/../style.css', 'phar://C:/', 'phar://C:/style.css'),
array('../css/./../style.css', 'phar://C:/', 'phar://C:/style.css'),
array('../css/.././style.css', 'phar://C:/', 'phar://C:/style.css'),
// absolute paths
array('/css/style.css', '/webmozart/puli', '/css/style.css'),
array('\\css\\style.css', '/webmozart/puli', '/css/style.css'),
array('C:/css/style.css', 'C:/webmozart/puli', 'C:/css/style.css'),
array('D:\\css\\style.css', 'D:/webmozart/puli', 'D:/css/style.css'),
));
}
/**
* @dataProvider provideMakeAbsoluteTests
*/
public function testMakeAbsolute($relativePath, $basePath, $absolutePath)
{
$this->assertSame($absolutePath, Path::makeAbsolute($relativePath, $basePath));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testMakeAbsoluteFailsIfInvalidPath()
{
Path::makeAbsolute(array(), '/webmozart/puli');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base path must be a non-empty string. Got: array
*/
public function testMakeAbsoluteFailsIfInvalidBasePath()
{
Path::makeAbsolute('css/style.css', array());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base path "webmozart/puli" is not an absolute path.
*/
public function testMakeAbsoluteFailsIfBasePathNotAbsolute()
{
Path::makeAbsolute('css/style.css', 'webmozart/puli');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base path must be a non-empty string. Got: ""
*/
public function testMakeAbsoluteFailsIfBasePathEmpty()
{
Path::makeAbsolute('css/style.css', '');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base path must be a non-empty string. Got: NULL
*/
public function testMakeAbsoluteFailsIfBasePathNull()
{
Path::makeAbsolute('css/style.css', null);
}
public function provideAbsolutePathsWithDifferentRoots()
{
return array(
array('C:/css/style.css', '/webmozart/puli'),
array('C:/css/style.css', '\\webmozart\\puli'),
array('C:\\css\\style.css', '/webmozart/puli'),
array('C:\\css\\style.css', '\\webmozart\\puli'),
array('/css/style.css', 'C:/webmozart/puli'),
array('/css/style.css', 'C:\\webmozart\\puli'),
array('\\css\\style.css', 'C:/webmozart/puli'),
array('\\css\\style.css', 'C:\\webmozart\\puli'),
array('D:/css/style.css', 'C:/webmozart/puli'),
array('D:/css/style.css', 'C:\\webmozart\\puli'),
array('D:\\css\\style.css', 'C:/webmozart/puli'),
array('D:\\css\\style.css', 'C:\\webmozart\\puli'),
array('phar:///css/style.css', '/webmozart/puli'),
array('/css/style.css', 'phar:///webmozart/puli'),
array('phar://C:/css/style.css', 'C:/webmozart/puli'),
array('phar://C:/css/style.css', 'C:\\webmozart\\puli'),
array('phar://C:\\css\\style.css', 'C:/webmozart/puli'),
array('phar://C:\\css\\style.css', 'C:\\webmozart\\puli'),
);
}
/**
* @dataProvider provideAbsolutePathsWithDifferentRoots
*/
public function testMakeAbsoluteDoesNotFailIfDifferentRoot($basePath, $absolutePath)
{
// If a path in partition D: is passed, but $basePath is in partition
// C:, the path should be returned unchanged
$this->assertSame(Path::canonicalize($absolutePath), Path::makeAbsolute($absolutePath, $basePath));
}
public function provideMakeRelativeTests()
{
$paths = array_map(function (array $arguments) {
return array($arguments[2], $arguments[1], $arguments[0]);
}, $this->providePathTests());
return array_merge($paths, array(
array('/webmozart/puli/./css/style.css', '/webmozart/puli', 'css/style.css'),
array('/webmozart/puli/../css/style.css', '/webmozart/puli', '../css/style.css'),
array('/webmozart/puli/.././css/style.css', '/webmozart/puli', '../css/style.css'),
array('/webmozart/puli/./../css/style.css', '/webmozart/puli', '../css/style.css'),
array('/webmozart/puli/../../css/style.css', '/webmozart/puli', '../../css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/./puli', 'css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/../puli', '../webmozart/puli/css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/./../puli', '../webmozart/puli/css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/.././puli', '../webmozart/puli/css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/../../puli', '../webmozart/puli/css/style.css'),
// first argument shorter than second
array('/css', '/webmozart/puli', '../../css'),
// second argument shorter than first
array('/webmozart/puli', '/css', '../webmozart/puli'),
array('\\webmozart\\puli\\css\\style.css', '\\webmozart\\puli', 'css/style.css'),
array('\\webmozart\\css\\style.css', '\\webmozart\\puli', '../css/style.css'),
array('\\css\\style.css', '\\webmozart\\puli', '../../css/style.css'),
array('C:/webmozart/puli/css/style.css', 'C:/webmozart/puli', 'css/style.css'),
array('C:/webmozart/css/style.css', 'C:/webmozart/puli', '../css/style.css'),
array('C:/css/style.css', 'C:/webmozart/puli', '../../css/style.css'),
array('C:\\webmozart\\puli\\css\\style.css', 'C:\\webmozart\\puli', 'css/style.css'),
array('C:\\webmozart\\css\\style.css', 'C:\\webmozart\\puli', '../css/style.css'),
array('C:\\css\\style.css', 'C:\\webmozart\\puli', '../../css/style.css'),
array('phar:///webmozart/puli/css/style.css', 'phar:///webmozart/puli', 'css/style.css'),
array('phar:///webmozart/css/style.css', 'phar:///webmozart/puli', '../css/style.css'),
array('phar:///css/style.css', 'phar:///webmozart/puli', '../../css/style.css'),
array('phar://C:/webmozart/puli/css/style.css', 'phar://C:/webmozart/puli', 'css/style.css'),
array('phar://C:/webmozart/css/style.css', 'phar://C:/webmozart/puli', '../css/style.css'),
array('phar://C:/css/style.css', 'phar://C:/webmozart/puli', '../../css/style.css'),
// already relative + already in root basepath
array('../style.css', '/', 'style.css'),
array('./style.css', '/', 'style.css'),
array('../../style.css', '/', 'style.css'),
array('..\\style.css', 'C:\\', 'style.css'),
array('.\\style.css', 'C:\\', 'style.css'),
array('..\\..\\style.css', 'C:\\', 'style.css'),
array('../style.css', 'C:/', 'style.css'),
array('./style.css', 'C:/', 'style.css'),
array('../../style.css', 'C:/', 'style.css'),
array('..\\style.css', '\\', 'style.css'),
array('.\\style.css', '\\', 'style.css'),
array('..\\..\\style.css', '\\', 'style.css'),
array('../style.css', 'phar:///', 'style.css'),
array('./style.css', 'phar:///', 'style.css'),
array('../../style.css', 'phar:///', 'style.css'),
array('..\\style.css', 'phar://C:\\', 'style.css'),
array('.\\style.css', 'phar://C:\\', 'style.css'),
array('..\\..\\style.css', 'phar://C:\\', 'style.css'),
array('css/../style.css', '/', 'style.css'),
array('css/./style.css', '/', 'css/style.css'),
array('css\\..\\style.css', 'C:\\', 'style.css'),
array('css\\.\\style.css', 'C:\\', 'css/style.css'),
array('css/../style.css', 'C:/', 'style.css'),
array('css/./style.css', 'C:/', 'css/style.css'),
array('css\\..\\style.css', '\\', 'style.css'),
array('css\\.\\style.css', '\\', 'css/style.css'),
array('css/../style.css', 'phar:///', 'style.css'),
array('css/./style.css', 'phar:///', 'css/style.css'),
array('css\\..\\style.css', 'phar://C:\\', 'style.css'),
array('css\\.\\style.css', 'phar://C:\\', 'css/style.css'),
// already relative
array('css/style.css', '/webmozart/puli', 'css/style.css'),
array('css\\style.css', '\\webmozart\\puli', 'css/style.css'),
// both relative
array('css/style.css', 'webmozart/puli', '../../css/style.css'),
array('css\\style.css', 'webmozart\\puli', '../../css/style.css'),
// relative to empty
array('css/style.css', '', 'css/style.css'),
array('css\\style.css', '', 'css/style.css'),
// different slashes in path and base path
array('/webmozart/puli/css/style.css', '\\webmozart\\puli', 'css/style.css'),
array('\\webmozart\\puli\\css\\style.css', '/webmozart/puli', 'css/style.css'),
));
}
/**
* @dataProvider provideMakeRelativeTests
*/
public function testMakeRelative($absolutePath, $basePath, $relativePath)
{
$this->assertSame($relativePath, Path::makeRelative($absolutePath, $basePath));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testMakeRelativeFailsIfInvalidPath()
{
Path::makeRelative(array(), '/webmozart/puli');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base path must be a string. Got: array
*/
public function testMakeRelativeFailsIfInvalidBasePath()
{
Path::makeRelative('/webmozart/puli/css/style.css', array());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The absolute path "/webmozart/puli/css/style.css" cannot be made relative to the relative path "webmozart/puli". You should provide an absolute base path instead.
*/
public function testMakeRelativeFailsIfAbsolutePathAndBasePathNotAbsolute()
{
Path::makeRelative('/webmozart/puli/css/style.css', 'webmozart/puli');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The absolute path "/webmozart/puli/css/style.css" cannot be made relative to the relative path "". You should provide an absolute base path instead.
*/
public function testMakeRelativeFailsIfAbsolutePathAndBasePathEmpty()
{
Path::makeRelative('/webmozart/puli/css/style.css', '');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base path must be a string. Got: NULL
*/
public function testMakeRelativeFailsIfBasePathNull()
{
Path::makeRelative('/webmozart/puli/css/style.css', null);
}
/**
* @dataProvider provideAbsolutePathsWithDifferentRoots
* @expectedException \InvalidArgumentException
*/
public function testMakeRelativeFailsIfDifferentRoot($absolutePath, $basePath)
{
Path::makeRelative($absolutePath, $basePath);
}
public function provideIsLocalTests()
{
return array(
array('/bg.png', true),
array('bg.png', true),
array('http://example.com/bg.png', false),
array('http://example.com', false),
array('', false),
);
}
/**
* @dataProvider provideIsLocalTests
*/
public function testIsLocal($path, $isLocal)
{
$this->assertSame($isLocal, Path::isLocal($path));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testIsLocalFailsIfInvalidPath()
{
Path::isLocal(array());
}
public function provideGetLongestCommonBasePathTests()
{
return array(
// same paths
array(array('/base/path', '/base/path'), '/base/path'),
array(array('C:/base/path', 'C:/base/path'), 'C:/base/path'),
array(array('C:\\base\\path', 'C:\\base\\path'), 'C:/base/path'),
array(array('C:/base/path', 'C:\\base\\path'), 'C:/base/path'),
array(array('phar:///base/path', 'phar:///base/path'), 'phar:///base/path'),
array(array('phar://C:/base/path', 'phar://C:/base/path'), 'phar://C:/base/path'),
// trailing slash
array(array('/base/path/', '/base/path'), '/base/path'),
array(array('C:/base/path/', 'C:/base/path'), 'C:/base/path'),
array(array('C:\\base\\path\\', 'C:\\base\\path'), 'C:/base/path'),
array(array('C:/base/path/', 'C:\\base\\path'), 'C:/base/path'),
array(array('phar:///base/path/', 'phar:///base/path'), 'phar:///base/path'),
array(array('phar://C:/base/path/', 'phar://C:/base/path'), 'phar://C:/base/path'),
array(array('/base/path', '/base/path/'), '/base/path'),
array(array('C:/base/path', 'C:/base/path/'), 'C:/base/path'),
array(array('C:\\base\\path', 'C:\\base\\path\\'), 'C:/base/path'),
array(array('C:/base/path', 'C:\\base\\path\\'), 'C:/base/path'),
array(array('phar:///base/path', 'phar:///base/path/'), 'phar:///base/path'),
array(array('phar://C:/base/path', 'phar://C:/base/path/'), 'phar://C:/base/path'),
// first in second
array(array('/base/path/sub', '/base/path'), '/base/path'),
array(array('C:/base/path/sub', 'C:/base/path'), 'C:/base/path'),
array(array('C:\\base\\path\\sub', 'C:\\base\\path'), 'C:/base/path'),
array(array('C:/base/path/sub', 'C:\\base\\path'), 'C:/base/path'),
array(array('phar:///base/path/sub', 'phar:///base/path'), 'phar:///base/path'),
array(array('phar://C:/base/path/sub', 'phar://C:/base/path'), 'phar://C:/base/path'),
// second in first
array(array('/base/path', '/base/path/sub'), '/base/path'),
array(array('C:/base/path', 'C:/base/path/sub'), 'C:/base/path'),
array(array('C:\\base\\path', 'C:\\base\\path\\sub'), 'C:/base/path'),
array(array('C:/base/path', 'C:\\base\\path\\sub'), 'C:/base/path'),
array(array('phar:///base/path', 'phar:///base/path/sub'), 'phar:///base/path'),
array(array('phar://C:/base/path', 'phar://C:/base/path/sub'), 'phar://C:/base/path'),
// first is prefix
array(array('/base/path/di', '/base/path/dir'), '/base/path'),
array(array('C:/base/path/di', 'C:/base/path/dir'), 'C:/base/path'),
array(array('C:\\base\\path\\di', 'C:\\base\\path\\dir'), 'C:/base/path'),
array(array('C:/base/path/di', 'C:\\base\\path\\dir'), 'C:/base/path'),
array(array('phar:///base/path/di', 'phar:///base/path/dir'), 'phar:///base/path'),
array(array('phar://C:/base/path/di', 'phar://C:/base/path/dir'), 'phar://C:/base/path'),
// second is prefix
array(array('/base/path/dir', '/base/path/di'), '/base/path'),
array(array('C:/base/path/dir', 'C:/base/path/di'), 'C:/base/path'),
array(array('C:\\base\\path\\dir', 'C:\\base\\path\\di'), 'C:/base/path'),
array(array('C:/base/path/dir', 'C:\\base\\path\\di'), 'C:/base/path'),
array(array('phar:///base/path/dir', 'phar:///base/path/di'), 'phar:///base/path'),
array(array('phar://C:/base/path/dir', 'phar://C:/base/path/di'), 'phar://C:/base/path'),
// root is common base path
array(array('/first', '/second'), '/'),
array(array('C:/first', 'C:/second'), 'C:/'),
array(array('C:\\first', 'C:\\second'), 'C:/'),
array(array('C:/first', 'C:\\second'), 'C:/'),
array(array('phar:///first', 'phar:///second'), 'phar:///'),
array(array('phar://C:/first', 'phar://C:/second'), 'phar://C:/'),
// windows vs unix
array(array('/base/path', 'C:/base/path'), null),
array(array('C:/base/path', '/base/path'), null),
array(array('/base/path', 'C:\\base\\path'), null),
array(array('phar:///base/path', 'phar://C:/base/path'), null),
// different partitions
array(array('C:/base/path', 'D:/base/path'), null),
array(array('C:/base/path', 'D:\\base\\path'), null),
array(array('C:\\base\\path', 'D:\\base\\path'), null),
array(array('phar://C:/base/path', 'phar://D:/base/path'), null),
// three paths
array(array('/base/path/foo', '/base/path', '/base/path/bar'), '/base/path'),
array(array('C:/base/path/foo', 'C:/base/path', 'C:/base/path/bar'), 'C:/base/path'),
array(array('C:\\base\\path\\foo', 'C:\\base\\path', 'C:\\base\\path\\bar'), 'C:/base/path'),
array(array('C:/base/path//foo', 'C:/base/path', 'C:\\base\\path\\bar'), 'C:/base/path'),
array(array('phar:///base/path/foo', 'phar:///base/path', 'phar:///base/path/bar'), 'phar:///base/path'),
array(array('phar://C:/base/path/foo', 'phar://C:/base/path', 'phar://C:/base/path/bar'), 'phar://C:/base/path'),
// three paths with root
array(array('/base/path/foo', '/', '/base/path/bar'), '/'),
array(array('C:/base/path/foo', 'C:/', 'C:/base/path/bar'), 'C:/'),
array(array('C:\\base\\path\\foo', 'C:\\', 'C:\\base\\path\\bar'), 'C:/'),
array(array('C:/base/path//foo', 'C:/', 'C:\\base\\path\\bar'), 'C:/'),
array(array('phar:///base/path/foo', 'phar:///', 'phar:///base/path/bar'), 'phar:///'),
array(array('phar://C:/base/path/foo', 'phar://C:/', 'phar://C:/base/path/bar'), 'phar://C:/'),
// three paths, different roots
array(array('/base/path/foo', 'C:/base/path', '/base/path/bar'), null),
array(array('/base/path/foo', 'C:\\base\\path', '/base/path/bar'), null),
array(array('C:/base/path/foo', 'D:/base/path', 'C:/base/path/bar'), null),
array(array('C:\\base\\path\\foo', 'D:\\base\\path', 'C:\\base\\path\\bar'), null),
array(array('C:/base/path//foo', 'D:/base/path', 'C:\\base\\path\\bar'), null),
array(array('phar:///base/path/foo', 'phar://C:/base/path', 'phar:///base/path/bar'), null),
array(array('phar://C:/base/path/foo', 'phar://D:/base/path', 'phar://C:/base/path/bar'), null),
// only one path
array(array('/base/path'), '/base/path'),
array(array('C:/base/path'), 'C:/base/path'),
array(array('C:\\base\\path'), 'C:/base/path'),
array(array('phar:///base/path'), 'phar:///base/path'),
array(array('phar://C:/base/path'), 'phar://C:/base/path'),
);
}
/**
* @dataProvider provideGetLongestCommonBasePathTests
*/
public function testGetLongestCommonBasePath(array $paths, $basePath)
{
$this->assertSame($basePath, Path::getLongestCommonBasePath($paths));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The paths must be strings. Got: array
*/
public function testGetLongestCommonBasePathFailsIfInvalidPath()
{
Path::getLongestCommonBasePath(array(array()));
}
public function provideIsBasePathTests()
{
return array(
// same paths
array('/base/path', '/base/path', true),
array('C:/base/path', 'C:/base/path', true),
array('C:\\base\\path', 'C:\\base\\path', true),
array('C:/base/path', 'C:\\base\\path', true),
array('phar:///base/path', 'phar:///base/path', true),
array('phar://C:/base/path', 'phar://C:/base/path', true),
// trailing slash
array('/base/path/', '/base/path', true),
array('C:/base/path/', 'C:/base/path', true),
array('C:\\base\\path\\', 'C:\\base\\path', true),
array('C:/base/path/', 'C:\\base\\path', true),
array('phar:///base/path/', 'phar:///base/path', true),
array('phar://C:/base/path/', 'phar://C:/base/path', true),
array('/base/path', '/base/path/', true),
array('C:/base/path', 'C:/base/path/', true),
array('C:\\base\\path', 'C:\\base\\path\\', true),
array('C:/base/path', 'C:\\base\\path\\', true),
array('phar:///base/path', 'phar:///base/path/', true),
array('phar://C:/base/path', 'phar://C:/base/path/', true),
// first in second
array('/base/path/sub', '/base/path', false),
array('C:/base/path/sub', 'C:/base/path', false),
array('C:\\base\\path\\sub', 'C:\\base\\path', false),
array('C:/base/path/sub', 'C:\\base\\path', false),
array('phar:///base/path/sub', 'phar:///base/path', false),
array('phar://C:/base/path/sub', 'phar://C:/base/path', false),
// second in first
array('/base/path', '/base/path/sub', true),
array('C:/base/path', 'C:/base/path/sub', true),
array('C:\\base\\path', 'C:\\base\\path\\sub', true),
array('C:/base/path', 'C:\\base\\path\\sub', true),
array('phar:///base/path', 'phar:///base/path/sub', true),
array('phar://C:/base/path', 'phar://C:/base/path/sub', true),
// first is prefix
array('/base/path/di', '/base/path/dir', false),
array('C:/base/path/di', 'C:/base/path/dir', false),
array('C:\\base\\path\\di', 'C:\\base\\path\\dir', false),
array('C:/base/path/di', 'C:\\base\\path\\dir', false),
array('phar:///base/path/di', 'phar:///base/path/dir', false),
array('phar://C:/base/path/di', 'phar://C:/base/path/dir', false),
// second is prefix
array('/base/path/dir', '/base/path/di', false),
array('C:/base/path/dir', 'C:/base/path/di', false),
array('C:\\base\\path\\dir', 'C:\\base\\path\\di', false),
array('C:/base/path/dir', 'C:\\base\\path\\di', false),
array('phar:///base/path/dir', 'phar:///base/path/di', false),
array('phar://C:/base/path/dir', 'phar://C:/base/path/di', false),
// root
array('/', '/second', true),
array('C:/', 'C:/second', true),
array('C:', 'C:/second', true),
array('C:\\', 'C:\\second', true),
array('C:/', 'C:\\second', true),
array('phar:///', 'phar:///second', true),
array('phar://C:/', 'phar://C:/second', true),
// windows vs unix
array('/base/path', 'C:/base/path', false),
array('C:/base/path', '/base/path', false),
array('/base/path', 'C:\\base\\path', false),
array('/base/path', 'phar:///base/path', false),
array('phar:///base/path', 'phar://C:/base/path', false),
// different partitions
array('C:/base/path', 'D:/base/path', false),
array('C:/base/path', 'D:\\base\\path', false),
array('C:\\base\\path', 'D:\\base\\path', false),
array('C:/base/path', 'phar://C:/base/path', false),
array('phar://C:/base/path', 'phar://D:/base/path', false),
);
}
/**
* @dataProvider provideIsBasePathTests
*/
public function testIsBasePath($path, $ofPath, $result)
{
$this->assertSame($result, Path::isBasePath($path, $ofPath));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base path must be a string. Got: array
*/
public function testIsBasePathFailsIfInvalidBasePath()
{
Path::isBasePath(array(), '/base/path');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The path must be a string. Got: array
*/
public function testIsBasePathFailsIfInvalidPath()
{
Path::isBasePath('/base/path', array());
}
public function provideJoinTests()
{
return array(
array('', '', ''),
array('/path/to/test', '', '/path/to/test'),
array('/path/to//test', '', '/path/to/test'),
array('', '/path/to/test', '/path/to/test'),
array('', '/path/to//test', '/path/to/test'),
array('/path/to/test', 'subdir', '/path/to/test/subdir'),
array('/path/to/test/', 'subdir', '/path/to/test/subdir'),
array('/path/to/test', '/subdir', '/path/to/test/subdir'),
array('/path/to/test/', '/subdir', '/path/to/test/subdir'),
array('/path/to/test', './subdir', '/path/to/test/subdir'),
array('/path/to/test/', './subdir', '/path/to/test/subdir'),
array('/path/to/test/', '../parentdir', '/path/to/parentdir'),
array('/path/to/test', '../parentdir', '/path/to/parentdir'),
array('path/to/test/', '/subdir', 'path/to/test/subdir'),
array('path/to/test', '/subdir', 'path/to/test/subdir'),
array('../path/to/test', '/subdir', '../path/to/test/subdir'),
array('path', '../../subdir', '../subdir'),
array('/path', '../../subdir', '/subdir'),
array('../path', '../../subdir', '../../subdir'),
array(array('/path/to/test', 'subdir'), '', '/path/to/test/subdir'),
array(array('/path/to/test', '/subdir'), '', '/path/to/test/subdir'),
array(array('/path/to/test/', 'subdir'), '', '/path/to/test/subdir'),
array(array('/path/to/test/', '/subdir'), '', '/path/to/test/subdir'),
array(array('/path'), '', '/path'),
array(array('/path', 'to', '/test'), '', '/path/to/test'),
array(array('/path', '', '/test'), '', '/path/test'),
array(array('path', 'to', 'test'), '', 'path/to/test'),
array(array(), '', ''),
array('base/path', 'to/test', 'base/path/to/test'),
array('C:\\path\\to\\test', 'subdir', 'C:/path/to/test/subdir'),
array('C:\\path\\to\\test\\', 'subdir', 'C:/path/to/test/subdir'),
array('C:\\path\\to\\test', '/subdir', 'C:/path/to/test/subdir'),
array('C:\\path\\to\\test\\', '/subdir', 'C:/path/to/test/subdir'),
array('/', 'subdir', '/subdir'),
array('/', '/subdir', '/subdir'),
array('C:/', 'subdir', 'C:/subdir'),
array('C:/', '/subdir', 'C:/subdir'),
array('C:\\', 'subdir', 'C:/subdir'),
array('C:\\', '/subdir', 'C:/subdir'),
array('C:', 'subdir', 'C:/subdir'),
array('C:', '/subdir', 'C:/subdir'),
array('phar://', '/path/to/test', 'phar:///path/to/test'),
array('phar:///', '/path/to/test', 'phar:///path/to/test'),
array('phar:///path/to/test', 'subdir', 'phar:///path/to/test/subdir'),
array('phar:///path/to/test', 'subdir/', 'phar:///path/to/test/subdir'),
array('phar:///path/to/test', '/subdir', 'phar:///path/to/test/subdir'),
array('phar:///path/to/test/', 'subdir', 'phar:///path/to/test/subdir'),
array('phar:///path/to/test/', '/subdir', 'phar:///path/to/test/subdir'),
array('phar://', 'C:/path/to/test', 'phar://C:/path/to/test'),
array('phar://', 'C:\\path\\to\\test', 'phar://C:/path/to/test'),
array('phar://C:/path/to/test', 'subdir', 'phar://C:/path/to/test/subdir'),
array('phar://C:/path/to/test', 'subdir/', 'phar://C:/path/to/test/subdir'),
array('phar://C:/path/to/test', '/subdir', 'phar://C:/path/to/test/subdir'),
array('phar://C:/path/to/test/', 'subdir', 'phar://C:/path/to/test/subdir'),
array('phar://C:/path/to/test/', '/subdir', 'phar://C:/path/to/test/subdir'),
array('phar://C:', 'path/to/test', 'phar://C:/path/to/test'),
array('phar://C:', '/path/to/test', 'phar://C:/path/to/test'),
array('phar://C:/', 'path/to/test', 'phar://C:/path/to/test'),
array('phar://C:/', '/path/to/test', 'phar://C:/path/to/test'),
);
}
/**
* @dataProvider provideJoinTests
*/
public function testJoin($path1, $path2, $result)
{
$this->assertSame($result, Path::join($path1, $path2));
}
public function testJoinVarArgs()
{
$this->assertSame('/path', Path::join('/path'));
$this->assertSame('/path/to', Path::join('/path', 'to'));
$this->assertSame('/path/to/test', Path::join('/path', 'to', '/test'));
$this->assertSame('/path/to/test/subdir', Path::join('/path', 'to', '/test', 'subdir/'));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The paths must be strings. Got: array
*/
public function testJoinFailsIfInvalidPath()
{
Path::join('/path', array());
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Your environment or operation system isn't supported
*/
public function testGetHomeDirectoryFailsIfNotSupportedOperationSystem()
{
putenv('HOME=');
Path::getHomeDirectory();
}
public function testGetHomeDirectoryForUnix()
{
$this->assertEquals('/home/webmozart', Path::getHomeDirectory());
}
public function testGetHomeDirectoryForWindows()
{
putenv('HOME=');
putenv('HOMEDRIVE=C:');
putenv('HOMEPATH=/users/webmozart');
$this->assertEquals('C:/users/webmozart', Path::getHomeDirectory());
}
public function testNormalize()
{
$this->assertSame('C:/Foo/Bar/test', Path::normalize('C:\\Foo\\Bar/test'));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testNormalizeFailsIfNoString()
{
Path::normalize(true);
}
}
<?php
/*
* This file is part of the webmozart/path-util package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\PathUtil\Tests;
use Webmozart\PathUtil\Url;
/**
* @since 2.3
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Claudio Zizza <claudio@budgegeria.de>
*/
class UrlTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideMakeRelativeTests
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelative($absolutePath, $basePath, $relativePath)
{
$host = 'http://example.com';
$relative = Url::makeRelative($host.$absolutePath, $host.$basePath);
$this->assertSame($relativePath, $relative);
$relative = Url::makeRelative($absolutePath, $host.$basePath);
$this->assertSame($relativePath, $relative);
}
/**
* @dataProvider provideMakeRelativeIsAlreadyRelativeTests
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeIsAlreadyRelative($absolutePath, $basePath, $relativePath)
{
$host = 'http://example.com';
$relative = Url::makeRelative($absolutePath, $host.$basePath);
$this->assertSame($relativePath, $relative);
}
/**
* @dataProvider provideMakeRelativeTests
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeWithFullUrl($absolutePath, $basePath, $relativePath)
{
$host = 'ftp://user:password@example.com:8080';
$relative = Url::makeRelative($host.$absolutePath, $host.$basePath);
$this->assertSame($relativePath, $relative);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The URL must be a string. Got: array
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfInvalidUrl()
{
Url::makeRelative(array(), 'http://example.com/webmozart/puli');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base URL must be a string. Got: array
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfInvalidBaseUrl()
{
Url::makeRelative('http://example.com/webmozart/puli/css/style.css', array());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage "webmozart/puli" is not an absolute Url.
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfBaseUrlNoUrl()
{
Url::makeRelative('http://example.com/webmozart/puli/css/style.css', 'webmozart/puli');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage "" is not an absolute Url.
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfBaseUrlEmpty()
{
Url::makeRelative('http://example.com/webmozart/puli/css/style.css', '');
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The base URL must be a string. Got: NULL
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfBaseUrlNull()
{
Url::makeRelative('http://example.com/webmozart/puli/css/style.css', null);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The URL "http://example.com" cannot be made relative to "http://example2.com" since
* their host names are different.
* @covers Webmozart\PathUtil\Url
*/
public function testMakeRelativeFailsIfDifferentDomains()
{
Url::makeRelative('http://example.com/webmozart/puli/css/style.css', 'http://example2.com/webmozart/puli');
}
public function provideMakeRelativeTests()
{
return array(
array('/webmozart/puli/css/style.css', '/webmozart/puli', 'css/style.css'),
array('/webmozart/puli/css/style.css?key=value&key2=value', '/webmozart/puli', 'css/style.css?key=value&key2=value'),
array('/webmozart/puli/css/style.css?key[]=value&key[]=value', '/webmozart/puli', 'css/style.css?key[]=value&key[]=value'),
array('/webmozart/css/style.css', '/webmozart/puli', '../css/style.css'),
array('/css/style.css', '/webmozart/puli', '../../css/style.css'),
array('/', '/', ''),
// relative to root
array('/css/style.css', '/', 'css/style.css'),
// same sub directories in different base directories
array('/puli/css/style.css', '/webmozart/css', '../../puli/css/style.css'),
array('/webmozart/puli/./css/style.css', '/webmozart/puli', 'css/style.css'),
array('/webmozart/puli/../css/style.css', '/webmozart/puli', '../css/style.css'),
array('/webmozart/puli/.././css/style.css', '/webmozart/puli', '../css/style.css'),
array('/webmozart/puli/./../css/style.css', '/webmozart/puli', '../css/style.css'),
array('/webmozart/puli/../../css/style.css', '/webmozart/puli', '../../css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/./puli', 'css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/../puli', '../webmozart/puli/css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/./../puli', '../webmozart/puli/css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/.././puli', '../webmozart/puli/css/style.css'),
array('/webmozart/puli/css/style.css', '/webmozart/../../puli', '../webmozart/puli/css/style.css'),
// first argument shorter than second
array('/css', '/webmozart/puli', '../../css'),
// second argument shorter than first
array('/webmozart/puli', '/css', '../webmozart/puli'),
array('', '', ''),
);
}
public function provideMakeRelativeIsAlreadyRelativeTests()
{
return array(
array('css/style.css', '/webmozart/puli', 'css/style.css'),
array('css/style.css', '', 'css/style.css'),
array('css/../style.css', '', 'style.css'),
array('css/./style.css', '', 'css/style.css'),
array('../style.css', '/', 'style.css'),
array('./style.css', '/', 'style.css'),
array('../../style.css', '/', 'style.css'),
array('../../style.css', '', 'style.css'),
array('./style.css', '', 'style.css'),
array('../style.css', '', 'style.css'),
array('./../style.css', '', 'style.css'),
array('css/./../style.css', '', 'style.css'),
array('css//style.css', '', 'css/style.css'),
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment