diff --git a/composer.lock b/composer.lock
index c3cba04..6a56b5f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "ef93e7890c8849fe9123c4c557e58e45",
+ "content-hash": "3449d9f432f7ba89aa4794432fe52259",
"packages": [],
"packages-dev": [
{
@@ -243,6 +243,59 @@
},
"time": "2022-02-21T01:04:05+00:00"
},
+ {
+ "name": "phpstan/phpstan",
+ "version": "2.1.46",
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a193923fc2d6325ef4e741cf3af8c3e8f54dbf25",
+ "reference": "a193923fc2d6325ef4e741cf3af8c3e8f54dbf25",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "security": "https://github.com/phpstan/phpstan/security/policy",
+ "source": "https://github.com/phpstan/phpstan-src"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "time": "2026-04-01T09:25:14+00:00"
+ },
{
"name": "phpunit/php-code-coverage",
"version": "10.1.16",
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..16f0d34
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,4 @@
+parameters:
+ paths:
+ - src
+ level: max
diff --git a/phpunit.xml b/phpunit.xml
index 9b873f6..c217093 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -3,13 +3,13 @@
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
- cacheDirectory=".phpunit.cache"
+ cacheDirectory="tests/_output/.phpunit.result.cache"
beStrictAboutOutputDuringTests="false"
failOnRisky="false"
failOnWarning="true">
- tests
+ tests/unit
@@ -19,4 +19,3 @@
-
diff --git a/resources/docker/develop/Dockerfile b/resources/docker/develop/Dockerfile
index 022831d..5a68a02 100644
--- a/resources/docker/develop/Dockerfile
+++ b/resources/docker/develop/Dockerfile
@@ -15,36 +15,36 @@ COPY --from=ghcr.io/php/pie:bin /pie /usr/bin/pie
# Add user and group
RUN groupadd -g "${GID}" "${GROUP}" \
- && RUN useradd -l -m -u "${UID}" -g "${GID}" "${USER}" \
- && RUN usermod -s /bin/bash "${USER}" \
- && RUN mkdir /app \
- && RUN chown "${USER}":"${GROUP}" /app \
- && RUN chmod 0770 /app \
+ && useradd -l -m -u "${UID}" -g "${GID}" "${USER}" \
+ && usermod -s /bin/bash "${USER}" \
+ && mkdir /app \
+ && chown "${USER}":"${GROUP}" /app \
+ && chmod 0770 /app \
# Install needed packages \
- && RUN apt update -y \
- && RUN apt install -yq --no-install-recommends apt-utils git locales nano sudo unzip wget zip \
+ && apt update -y \
+ && apt install -yq --no-install-recommends apt-utils git locales nano sudo unzip wget zip \
# Configure locales \
- && RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
- && RUN dpkg-reconfigure --frontend=noninteractive locales \
- && RUN update-locale LANG=en_US.UTF-8 \
+ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
+ && dpkg-reconfigure --frontend=noninteractive locales \
+ && update-locale LANG=en_US.UTF-8 \
# Install xdebug for code coverage \
- && RUN apt install -yq --no-install-recommends $PHPIZE_DEPS \
- && RUN pie install xdebug/xdebug \
- && RUN apt-get purge -y --auto-remove $PHPIZE_DEPS \
+ && apt install -yq --no-install-recommends $PHPIZE_DEPS \
+ && pie install xdebug/xdebug \
+ && apt-get purge -y --auto-remove $PHPIZE_DEPS \
# Copy ini file \
- && RUN mv /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini \
+ && mv /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini \
# Set correct pid file location and permissions \
- && RUN mkdir -p /run/php \
- && RUN chown "${USER}":"${GROUP}" /run/php \
- && RUN chmod 0770 /run/php \
+ && mkdir -p /run/php \
+ && chown "${USER}":"${GROUP}" /run/php \
+ && chmod 0770 /run/php \
# Cleanup \
- && RUN apt-get autoremove --purge -y \
- && RUN apt-get autoclean -y \
- && RUN apt-get clean -y \
- && RUN rm -rf /tmp/* /var/tmp/* \
- && RUN find /var/cache/apt/archives /var/lib/apt/lists -not -name lock -type f -delete \
- && RUN find /var/cache -type f -delete \
- && RUN find /var/log -type f -delete \
+ && apt-get autoremove --purge -y \
+ && apt-get autoclean -y \
+ && apt-get clean -y \
+ && rm -rf /tmp/* /var/tmp/* \
+ && find /var/cache/apt/archives /var/lib/apt/lists -not -name lock -type f -delete \
+ && find /var/cache -type f -delete \
+ && find /var/log -type f -delete \
COPY resources/docker/develop/.bashrc /home/${USER}/.bashrc
COPY resources/docker/develop/extra.ini /usr/local/etc/php/conf.d/
diff --git a/tests/AbstractUnitTestCase.php b/tests/AbstractUnitTestCase.php
new file mode 100644
index 0000000..f3de57a
--- /dev/null
+++ b/tests/AbstractUnitTestCase.php
@@ -0,0 +1,19 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE.txt
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace Phalcon\Phql\Tests;
+
+use PHPUnit\Framework\TestCase;
+
+abstract class AbstractUnitTestCase extends TestCase
+{
+}
diff --git a/tests/unit/Parser/PhqlParserTest.php b/tests/unit/Parser/PhqlParserTest.php
new file mode 100644
index 0000000..b7b479f
--- /dev/null
+++ b/tests/unit/Parser/PhqlParserTest.php
@@ -0,0 +1,193 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE.txt
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Phalcon\Phql\Tests\Unit\Parser;
+
+use Phalcon\Phql\Parser\Parser;
+use Phalcon\Phql\Tests\AbstractUnitTestCase;
+
+final class PhqlParserTest extends AbstractUnitTestCase
+{
+ private Parser $parser;
+
+ protected function setUp(): void
+ {
+ $this->parser = new Parser();
+ }
+
+ /**
+ * Test: Select with limit
+ * Original: tests-old/001.phpt
+ */
+ public function testSelectWithLimit(): void
+ {
+ $phql = 'SELECT r.* FROM Robots r LIMIT 10';
+ $result = $this->parser->parse($phql);
+
+ $this->assertIsArray($result);
+ $this->assertEquals(309, $result['type']);
+ $this->assertArrayHasKey('select', $result);
+ $this->assertArrayHasKey('limit', $result);
+
+ $this->assertArrayHasKey('columns', $result['select']);
+ $this->assertIsArray($result['select']['columns']);
+ $this->assertCount(1, $result['select']['columns']);
+
+ $this->assertEquals(353, $result['select']['columns'][0]['type']);
+ $this->assertEquals('r', $result['select']['columns'][0]['column']);
+
+ $this->assertArrayHasKey('tables', $result['select']);
+ $this->assertEquals('Robots', $result['select']['tables']['qualifiedName']['name']);
+ $this->assertEquals('r', $result['select']['tables']['alias']);
+
+ $this->assertEquals('10', $result['limit']['number']['value']);
+ }
+
+ /**
+ * Test: Select with BETWEEN
+ * Original: tests-old/002.phpt
+ */
+ public function testSelectWithBetween(): void
+ {
+ $phql = <<parser->parse($phql);
+
+ $this->assertIsArray($result);
+ $this->assertEquals(309, $result['type']);
+ $this->assertArrayHasKey('select', $result);
+ $this->assertArrayHasKey('where', $result);
+
+ $this->assertEquals('column_name', $result['select']['columns'][0]['column']['name']);
+ $this->assertEquals('table_name', $result['select']['tables']['qualifiedName']['name']);
+ $this->assertEquals('column_name', $result['where']['left']['name']);
+ $this->assertEquals('value1', $result['where']['right']['left']['name']);
+ $this->assertEquals('value2', $result['where']['right']['right']['name']);
+ }
+
+ /**
+ * Test: Using FQCN for source model
+ * Original: tests-old/003.phpt
+ */
+ public function testUsingFQCNForSourceModel(): void
+ {
+ $phql = <<parser->parse($phql);
+
+ $this->assertIsArray($result);
+ $this->assertEquals(309, $result['type']);
+ $this->assertArrayHasKey('select', $result);
+
+ $this->assertEquals('AVG', $result['select']['columns'][0]['column']['name']);
+ $this->assertEquals('inv_total', $result['select']['columns'][0]['column']['arguments'][0]['name']);
+ $this->assertEquals('average', $result['select']['columns'][0]['alias']);
+
+ $this->assertEquals('[Phalcon\\Tests\\Models\\Invoices]', $result['select']['tables']['qualifiedName']['name']);
+ }
+
+ /**
+ * Test: Select with NOT BETWEEN
+ * Original: tests-old/bug14253.phpt
+ */
+ public function testSelectWithNotBetween(): void
+ {
+ $phql = <<parser->parse($phql);
+
+ $this->assertIsArray($result);
+ $this->assertEquals(309, $result['type']);
+ $this->assertArrayHasKey('select', $result);
+ $this->assertArrayHasKey('where', $result);
+
+ $this->assertCount(3, $result['select']['columns']);
+ $this->assertEquals('Id', $result['select']['columns'][0]['column']['name']);
+ $this->assertEquals('ProductName', $result['select']['columns'][1]['column']['name']);
+ $this->assertEquals('UnitPrice', $result['select']['columns'][2]['column']['name']);
+ $this->assertEquals('Product', $result['select']['tables']['qualifiedName']['name']);
+
+ $this->assertEquals(332, $result['where']['type']); // NOT BETWEEN type
+ $this->assertEquals('UnitPrice', $result['where']['left']['name']);
+ $this->assertEquals('5', $result['where']['right']['left']['value']);
+ $this->assertEquals('100', $result['where']['right']['right']['value']);
+ }
+
+ /**
+ * Test: Using spaces in column alias
+ * Original: tests-old/bug14535.phpt
+ */
+ public function testUsingSpacesInColumnAlias(): void
+ {
+ $phql = <<parser->parse($phql);
+
+ $this->assertIsArray($result);
+ $this->assertEquals(309, $result['type']);
+ $this->assertArrayHasKey('select', $result);
+
+ $this->assertCount(2, $result['select']['columns']);
+ $this->assertEquals('People', $result['select']['columns'][0]['column']['domain']);
+ $this->assertEquals('firstName', $result['select']['columns'][0]['column']['name']);
+ $this->assertEquals('[First Name]', $result['select']['columns'][0]['alias']);
+
+ $this->assertEquals('People', $result['select']['columns'][1]['column']['domain']);
+ $this->assertEquals('lastName', $result['select']['columns'][1]['column']['name']);
+ $this->assertEquals('[Last Name]', $result['select']['columns'][1]['alias']);
+
+ $this->assertEquals('People', $result['select']['tables']['qualifiedName']['name']);
+ }
+
+ /**
+ * Test: Delete with WHERE conditions using AND/OR
+ */
+ public function testDeleteWithWhereAndOr(): void
+ {
+ $phql = "DELETE FROM co_invoices "
+ . "WHERE inv_total > :test: "
+ . "AND inv_cst_id = 2 "
+ . "OR inv_status_flag = 3 ";
+
+ $parser = new Parser(true);
+ $result = $parser->parse($phql);
+
+ $this->assertIsArray($result);
+ $this->assertArrayHasKey('type', $result);
+ $this->assertArrayHasKey('delete', $result);
+
+ $this->assertArrayHasKey('tables', $result['delete']);
+ $this->assertEquals('co_invoices', $result['delete']['tables']['qualifiedName']['name']);
+
+ $this->assertArrayHasKey('where', $result);
+ }
+}
\ No newline at end of file