Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
# Conflicts:
#	CHANGELOG.md
  • Loading branch information
bgaillard committed Dec 20, 2017
2 parents ea135f2 + 4623101 commit d264bf0
Show file tree
Hide file tree
Showing 10 changed files with 434 additions and 25 deletions.
16 changes: 9 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to
[Semantic Versioning](http://semver.org/).

## [1.2.0] - 2017-12-20
* Add management of date and time parsing and conversion into compliant database string format.

## [1.1.3] - 2017-12-01
* Minor fixes of case of empty string value.

## [1.1.2] - 2017-11-30
* Minor fixes of README.md.
* Minor fixes of README.md.

## [1.1.1] - 2017-11-28
* Minor fixes for PHP 5.6.
* Minor fixes for PHP 5.6.

## [1.1.0] - 2017-11-28
* Add support for expressions with simple logical operators and without parenthesis. The or operator is writen with `-`,
the and operator is written with `+` ;
* Update minor composer dependency versions.
* Add support for expressions with simple logical operators and without parenthesis. The or operator is writen with `-`
, the and operator is written with `+` ;
* Update minor composer dependency versions.

## [1.0.0] - 2017-08-01

* Initial release.
* Initial release.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,40 @@ The following values can be used.
* string (must be quoted with simple quotes ')
* string with an ISO 8601 format for the dates

### Date and time parsing

By default when the `SqlFilterConverter` encounters a string inside an expression it simply takes it as a "standard"
string.

But you've probably business entities having date attributes and want to request those entities using data and time
filters. To do this you can set a date and time parser on the `SqlFilterConverter` to indicate him to parse date and
time string and transform them to date and time string which are compliant with the database in use.

For example configuring the `SqlFilterConverter` to parse ISO 8601 strings and convert them to MySQL date and time format
is done with the following.

```php
$sqFilterConverter = new SqlFilterConverter();
$sqlFilterConverter->setDateTimeParser(new FormatDateTimeParser());
```

By default the `FormatDateTimeParser` class uses ISO 8601 date and time parsing, but you can change its behavior with
the `FormatDateTimeParser->setFormat(string $format)` method. In generall you'll want to use one of the format provided
with the PHP `DateTime` class, that's to say one of `DateTime::ATOM`, `DateTime::COOKIE`, `DateTime::ISO8601`,
`DateTime::RFC822`, `DateTime::RFC850`, `DateTime::RFC1036`, `DateTime::RFC1123`, `DateTime::RFC2822`,
`DateTime::RFC3339`, `DateTime::RSS` or `DateTime::W3C`.

The parser parses date and time strings and convert them to PHP `DateTime` object, then internally the
`SqlFilterConverter` converts the `DateTime` object to a string which is compatible with Mysql.


For example the following transform will create a `property <= ?` expression with a value equals to
`2017-12-01 06:00:00` which is compatible with MySQL.

```php
$sqlFilter = $filterConverter->transform('property', "<='2017-12-01T06:00:00Z'");
```

## About Gomoob

At [Gomoob](https://www.gomoob.com) we build high quality software with awesome Open Source frameworks everyday. Would
Expand Down
45 changes: 45 additions & 0 deletions src/main/php/Gomoob/Filter/DateTimeParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/**
* BSD 3-Clause License
*
* Copyright (c) 2017, GOMOOB All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Gomoob\Filter;

/**
* Interface which represents a `\DateTime` parser used to convert string to `\DateTime` objects.
*
* @author Baptiste GAILLARD (baptiste.gaillard@gomoob.com)
*/
interface DateTimeParserInterface
{
/**
* Parse a string and converts it into a `\DateTime` object.
*
* @return \DateTime the resulting `\DateTime` object.
*
* @throws \InvalidArgumentException if the provided string has not the format expected by the parser.
*/
public function parse(/* string */ $str) /* : \DateTime */;
}
126 changes: 126 additions & 0 deletions src/main/php/Gomoob/Filter/Parser/FormatDateTimeParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

/**
* BSD 3-Clause License
*
* Copyright (c) 2017, GOMOOB All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Gomoob\Filter\Parser;

use Gomoob\Filter\DateTimeParserInterface;

/**
* Class which represents `DateTime` parser to parse string dates.
*
* <p>The format of the string dates can be specified using the standard <tt>\DateTime</tt> format.</p>
*
* <p>If you want to choose a format which is compliant with a specification you can use one of the following: </p>
* <ul>
* <li>DateTime::ATOM</li>
* <li>DateTime::COOKIE</li>
* <li>DateTime::ISO8601</li>
* <li>DateTime::RFC822</li>
* <li>DateTime::RFC850</li>
* <li>DateTime::RFC1036</li>
* <li>DateTime::RFC1123</li>
* <li>DateTime::RFC2822</li>
* <li>DateTime::RFC3339</li>
* <li>DateTime::RSS</li>
* <li>DateTime::W3C</li>
* </ul>
*
* @author Baptiste GAILLARD (baptiste.gaillard@gomoob.com)
*/
class FormatDateTimeParser implements DateTimeParserInterface
{
/**
* The format used to parse `\DateTime` strings.
*
* @var string
*
* @see http://www.php.net/manual/en/datetime.createfromformat.php
*/
private $format = \DateTime::ISO8601;

/**
* {@inheritDoc}
*/
public function parse(/* string */ $str) /* : \DateTime */
{
$date = \DateTime::createFromFormat($this->format, $str);

// If the conversion failed
if ($date === false) {
// If the format is ISO8601 then we try to parse with additional formats, this is because the PHP
// \DateTime:ISO8601 does not accepts all ISO8601 formats.
//
// see https://stackoverflow.com/questions/4411340/
// php-datetimecreatefromformat-doesnt-parse-iso-8601-date-time
// see https://stackoverflow.com/questions/6150280/
// get-the-iso-8601-with-seconds-decimal-fraction-of-second-date-in-php
// see https://bugs.php.net/bug.php?id=51950
if ($this->format === \DateTime::ISO8601) {
$date = \DateTime::createFromFormat('Y-m-d\TH:i:s.uP', $str);
}

// If the conversion failed
if ($date === false) {
throw new \InvalidArgumentException(
sprintf(
'%s method received an \'%s\' date string which is not compliant with the configured ' .
'format !',
__METHOD__,
$value
)
);
}
}

return $date;
}

/**
* Sets the format to us to parse date and time strings.
*
* <p>If you want to choose a format which is compliant with a specification you can use one of the following: </p>
* <ul>
* <li>DateTime::ATOM</li>
* <li>DateTime::COOKIE</li>
* <li>DateTime::ISO8601</li>
* <li>DateTime::RFC822</li>
* <li>DateTime::RFC850</li>
* <li>DateTime::RFC1036</li>
* <li>DateTime::RFC1123</li>
* <li>DateTime::RFC2822</li>
* <li>DateTime::RFC3339</li>
* <li>DateTime::RSS</li>
* <li>DateTime::W3C</li>
* </ul>
*
* @param string $format the format to use to parse date and time strings.
*/
public function setFormat(/* string */ $format)
{
$this->format = $format;
}
}
69 changes: 66 additions & 3 deletions src/main/php/Gomoob/Filter/Sql/SqlFilterConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@
*/
class SqlFilterConverter implements SqlFilterConverterInterface
{
/**
* The parser used to parse `DateTime` strings, this parser could be `null` in which can date and time parsing is
* disabled.
*
* @var \Gomoob\Filter\DateTimeParserInterface
*/
private $dateTimeParser;

/**
* Sets the parsed used to parse `DateTime` strings.
*
* Set the parser to `null` to disable `DateTime` parsing. If this is defined each one a `DateTime` is encountered
* it is converted to a date and time string which is compliant with the configured database.
*
* @param \Gomoob\Filter\DateTimeParserInterface $dateTimeParser the date and time parser to use.
*/
public function setDateTimeParser(/* DateTimeParserInterface */ $dateTimeParser)
{
$this->dateTimeParser = $dateTimeParser;
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -116,6 +137,37 @@ private function extractUnquotedString(/* TokenInterface */ $token) /* : string
return substr($string, 1, strlen($string) - 2);
}

/**
* Function used to try to parse a date and time string and convert it into an equivalent string compliant with the
* database currently in use.
*
* @param string $str the string to parse.
*
* @return string the resulting equivalent string date compliant with the database in use. Please not that if
* parsing fails the `$str` function input parameter is returned.
*/
private function parseDateTime(/* string */ $str) /* : string */
{
$sqlDateTime = $str;

// If a DateTime parser is defined
if ($this->dateTimeParser !== null) {
// Try to parse the string using the \DateTime parser
try {
// Gets the PHP DateTime
$dateTime = $this->dateTimeParser->parse($str);

// Converts the PHP DateTime to a format which is compliant with the database currently in use
// For now we only support MySQL
$sqlDateTime = $dateTime->format('Y-m-d H:i:s');
} catch (\InvalidArgumentException $ieax) {
// Expected
}
}

return $sqlDateTime;
}

/**
* Parse a filter expression from the first encountered token.
*
Expand Down Expand Up @@ -164,9 +216,12 @@ private function parseFromFirstToken(
$args[] = $this->parseNumberToken($secondToken);
break;
case FilterToken::STRING:
// Extract string without quotes
$unquotedString = $this->extractUnquotedString($secondToken);

// Try to find star tokens to know if the query if for a 'like'
$starTokenizer = new StarTokenizer();
$starTokens = $starTokenizer->tokenize($this->extractUnquotedString($secondToken));
$starTokens = $starTokenizer->tokenize($unquotedString);

// The SQL instruction to build must contain a 'like'
if (count($starTokens) > 1) {
Expand Down Expand Up @@ -201,7 +256,10 @@ private function parseFromFirstToken(

$sb .= $this->convertSimpleOperatorTokenToSqlString($firstToken);
$sb .= ' ?';
$args[] = $this->extractUnquotedString($secondToken);

// Try to take into account the string as a date, if parsing failed the unquoted string is
// simply returned
$args[] = $this->parseDateTime($unquotedString);
}

break;
Expand Down Expand Up @@ -252,7 +310,12 @@ private function parseFromFirstToken(
$sb .= $this->convertSimpleOperatorTokenToSqlString($firstToken);
$sb .= ' ?';

$args[] = $this->extractUnquotedString($secondToken);
// Extract string without quotes
$unquotedString = $this->extractUnquotedString($secondToken);

// Try to take into account the string as a date, if parsing failed the unquoted string is
// simply returned
$args[] = $this->parseDateTime($unquotedString);

break;
default:
Expand Down
17 changes: 13 additions & 4 deletions src/main/php/Gomoob/Filter/Tokenizer/AbstractTokenizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@
*
* @author Baptiste GAILLARD (baptiste.gaillard@gomoob.com)
*/
abstract class AbstractTokenizer implements TokenizerInterface {
abstract class AbstractTokenizer implements TokenizerInterface
{

/**
* Regular expression used to identify Date and time values.
*
* @var string
*/
private $dateTimeFormat = \DateTime::ISO8601;

/**
* List which holds all our token informations.
Expand All @@ -54,7 +62,8 @@ abstract class AbstractTokenizer implements TokenizerInterface {
/**
* {@inheritDoc}
*/
public function addTokenInfo(/* string */ $regex, /* int */ $tokenCode) {
public function addTokenInfo(/* string */ $regex, /* int */ $tokenCode)
{
// The user can pass a regular expression string and a token code to the method. The method will then add the
// "^" character to the user supplied regular expression. It causes the regular expression to match only the
// beginning of a string. This is needed because we will be removing any token always looking for the next token
Expand All @@ -65,7 +74,8 @@ public function addTokenInfo(/* string */ $regex, /* int */ $tokenCode) {
/**
* {@inheritDoc}
*/
public function tokenize(/* string */ $string) /* : array */ {
public function tokenize(/* string */ $string) /* : array */
{
$tokens = [];

// First we clean our string
Expand All @@ -82,7 +92,6 @@ public function tokenize(/* string */ $string) /* : array */ {

// If a known token has been encountered
if (preg_match($info->getRegex(), $s, $matches)) {

// A token has been found
$match = true;

Expand Down
Loading

0 comments on commit d264bf0

Please sign in to comment.