yakamara / yform

YForm für REDAXO 5 – Formulare im Frontend und Backend mit Verwaltung von Datenbank-Tabellen.
MIT License
77 stars 55 forks source link

Relax joins for child classes and parameter suffix #1438

Open dgrothaus-mc opened 1 year ago

dgrothaus-mc commented 1 year ago

Aus anderen ORM Bibliotheken bin ich es gewohnt, nicht nur rohe SQL Abfragen JOINen zu können, sondern auch eine weitere Instanz der ORM Klasse. Meiner Erfahrung nach sorgt das für weniger Fehler beim Ausführen von Abfragen mit JOINs, da sich die ORM Klasse großteils darum kümmert, dass die Abfrage ordentlich formuliet ist um im Zweifelsfall eine hilfreiche Fehlermeldung wirft.

Bei yform ist die Query Klasse leider sehr verschlossen und es ist für ableitende Klassen nicht möglich Einfluss auf einige essentielle Daten wie zum Beispiel die JOIns zu nehmen. Daher bitte ich, den Zugriff auf die JOINs etwas zu lockern.

Falls da von Interesse ist oder die Entscheidung beeinflusst, hier ist der relevante Auszug aus der ableitenden Klasse, welche von dieser Änderung profitieren würde:

<?php
class MeineQuery extends rex_yform_manager_query {
    /** @var int $subQueryCount Count the number of added sub queries to get the parameter prefix. */
    private int $subQueryCount = 0;
    /** @var bool $subQuery Is this query configured to be used as a sub query? */
    private bool $subQuery = false;
    /** @var bool $joinsForFetchAdded Internal flag to track if SQL JOIN conditions were already added to a query */
    private bool $joinsForFetchAdded = false;

    /**
     * Configure this query to be used as a sub query. e.g. to be {@see joinSubQuery() joined}
     *
     * @return $this
     */
    public function asSubQuery(): static
    {
        $this->resetSelect();
        $this->setParamSuffix('sq' . ++$this->subQueryCount);
        $this->subQuery = true;

        return $this;
    }

    /**
     * Return if this query is intended as a sub query for another query.
     *
     * @return bool
     */
    public function isSubQuery(): bool
    {
        return $this->subQuery;
    }

    /**
     * **WARNING** This method only returns _false_ **once**. This method should be called exactly when the joining of
     * related tables is about to happen and to prevent multiple joins if query methods are called multiple times on
     * the same object.
     *
     * @return bool Have related queries already been joined?
     * @internal
     */
    public function areJoinsForFetchAdded(): bool
    {
        if ($this->joinsForFetchAdded) {
            return true;
        }
        $this->joinsForFetchAdded = true;

        return false;
    }

    /**
     * Join a sub-query. The $query that is joined must be configured {@see asSubQuery() as a sub query}.
     *
     * @param string      $type      JOIN type: INNER|LEFT|OUTER|CROSS ...
     * @param MeineQuery  $query     The query to join
     * @param string      $alias     Alias that the result of the query is addressable later
     * @param string|null $condition [Optional] JOIN condition
     *
     * @return static
     */
    public function joinSubQuery(string $type, MeineQuery $query, string $alias, ?string $condition = null): static
    {
        if (true !== $query->isSubQuery()) {
            throw new InvalidArgumentException('The sub-query to be joined must be configured "asSubQuery()"');
        }

        /** {@see whereRaw()} is called, because it's the only place reachable from outside that combines two arrays
         * of parameters. Accessing the private member {@see rex_yform_manager_query::$params} is hard otherwise. */
        $this->whereRaw('1', $query->getParams());
        $type = mb_strtoupper($type);
        $join = "{$type} JOIN ({$query->resetOrderBy()->getQuery()}) AS " . $query->quoteIdentifier($alias);
        if ($condition) {
            $join .= ' ON ' . $condition;
        }
        $this->joins[] = $join;

        return $this;
    }
}