vendor/contao/core-bundle/src/Resources/contao/library/Contao/Model.php line 913

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Contao.
  4.  *
  5.  * (c) Leo Feyer
  6.  *
  7.  * @license LGPL-3.0-or-later
  8.  */
  9. namespace Contao;
  10. use Contao\Database\Result;
  11. use Contao\Database\Statement;
  12. use Contao\Model\Collection;
  13. use Contao\Model\QueryBuilder;
  14. use Contao\Model\Registry;
  15. /**
  16.  * Reads objects from and writes them to to the database
  17.  *
  18.  * The class allows you to find and automatically join database records and to
  19.  * convert the result into objects. It also supports creating new objects and
  20.  * persisting them in the database.
  21.  *
  22.  * Usage:
  23.  *
  24.  *     // Write
  25.  *     $user = new UserModel();
  26.  *     $user->name = 'Leo Feyer';
  27.  *     $user->city = 'Wuppertal';
  28.  *     $user->save();
  29.  *
  30.  *     // Read
  31.  *     $user = UserModel::findByCity('Wuppertal');
  32.  *
  33.  *     while ($user->next())
  34.  *     {
  35.  *         echo $user->name;
  36.  *     }
  37.  *
  38.  * @property integer $id        The ID
  39.  * @property string  $customTpl A custom template
  40.  *
  41.  * @author Leo Feyer <https://github.com/leofeyer>
  42.  */
  43. abstract class Model
  44. {
  45.     /**
  46.      * Insert flag
  47.      */
  48.     const INSERT 1;
  49.     /**
  50.      * Update flag
  51.      */
  52.     const UPDATE 2;
  53.     /**
  54.      * Table name
  55.      * @var string
  56.      */
  57.     protected static $strTable;
  58.     /**
  59.      * Primary key
  60.      * @var string
  61.      */
  62.     protected static $strPk 'id';
  63.     /**
  64.      * Class name cache
  65.      * @var array
  66.      */
  67.     protected static $arrClassNames = array();
  68.     /**
  69.      * Data
  70.      * @var array
  71.      */
  72.     protected $arrData = array();
  73.     /**
  74.      * Modified keys
  75.      * @var array
  76.      */
  77.     protected $arrModified = array();
  78.     /**
  79.      * Relations
  80.      * @var array
  81.      */
  82.     protected $arrRelations = array();
  83.     /**
  84.      * Related
  85.      * @var array
  86.      */
  87.     protected $arrRelated = array();
  88.     /**
  89.      * Prevent saving
  90.      * @var boolean
  91.      */
  92.     protected $blnPreventSaving false;
  93.     /**
  94.      * Load the relations and optionally process a result set
  95.      *
  96.      * @param Result|array $objResult An optional database result or array
  97.      */
  98.     public function __construct($objResult=null)
  99.     {
  100.         $this->arrModified = array();
  101.         $objDca DcaExtractor::getInstance(static::$strTable);
  102.         $this->arrRelations $objDca->getRelations();
  103.         if ($objResult !== null)
  104.         {
  105.             $arrRelated = array();
  106.             if ($objResult instanceof Result)
  107.             {
  108.                 $arrData $objResult->row();
  109.             }
  110.             else
  111.             {
  112.                 $arrData = (array) $objResult;
  113.             }
  114.             // Look for joined fields
  115.             foreach ($arrData as $k=>$v)
  116.             {
  117.                 if (strpos($k'__') !== false)
  118.                 {
  119.                     list($key$field) = explode('__'$k2);
  120.                     if (!isset($arrRelated[$key]))
  121.                     {
  122.                         $arrRelated[$key] = array();
  123.                     }
  124.                     $arrRelated[$key][$field] = $v;
  125.                     unset($arrData[$k]);
  126.                 }
  127.             }
  128.             $objRegistry Registry::getInstance();
  129.             $this->setRow($arrData); // see #5439
  130.             $objRegistry->register($this);
  131.             // Create the related models
  132.             foreach ($arrRelated as $key=>$row)
  133.             {
  134.                 $table $this->arrRelations[$key]['table'];
  135.                 /** @var static $strClass */
  136.                 $strClass = static::getClassFromTable($table);
  137.                 $intPk $strClass::getPk();
  138.                 // If the primary key is empty, set null (see #5356)
  139.                 if (!isset($row[$intPk]))
  140.                 {
  141.                     $this->arrRelated[$key] = null;
  142.                 }
  143.                 else
  144.                 {
  145.                     $objRelated $objRegistry->fetch($table$row[$intPk]);
  146.                     if ($objRelated !== null)
  147.                     {
  148.                         $objRelated->mergeRow($row);
  149.                     }
  150.                     else
  151.                     {
  152.                         /** @var static $objRelated */
  153.                         $objRelated = new $strClass();
  154.                         $objRelated->setRow($row);
  155.                         $objRegistry->register($objRelated);
  156.                     }
  157.                     $this->arrRelated[$key] = $objRelated;
  158.                 }
  159.             }
  160.         }
  161.     }
  162.     /**
  163.      * Unset the primary key when cloning an object
  164.      */
  165.     public function __clone()
  166.     {
  167.         $this->arrModified = array();
  168.         $this->blnPreventSaving false;
  169.         unset($this->arrData[static::$strPk]);
  170.     }
  171.     /**
  172.      * Clone a model with its original values
  173.      *
  174.      * @return static The model
  175.      */
  176.     public function cloneOriginal()
  177.     {
  178.         $clone = clone $this;
  179.         $clone->setRow($this->originalRow());
  180.         return $clone;
  181.     }
  182.     /**
  183.      * Set an object property
  184.      *
  185.      * @param string $strKey   The property name
  186.      * @param mixed  $varValue The property value
  187.      */
  188.     public function __set($strKey$varValue)
  189.     {
  190.         if (isset($this->arrData[$strKey]) && $this->arrData[$strKey] === $varValue)
  191.         {
  192.             return;
  193.         }
  194.         $this->markModified($strKey);
  195.         $this->arrData[$strKey] = $varValue;
  196.         unset($this->arrRelated[$strKey]);
  197.     }
  198.     /**
  199.      * Return an object property
  200.      *
  201.      * @param string $strKey The property key
  202.      *
  203.      * @return mixed|null The property value or null
  204.      */
  205.     public function __get($strKey)
  206.     {
  207.         return $this->arrData[$strKey] ?? null;
  208.     }
  209.     /**
  210.      * Check whether a property is set
  211.      *
  212.      * @param string $strKey The property key
  213.      *
  214.      * @return boolean True if the property is set
  215.      */
  216.     public function __isset($strKey)
  217.     {
  218.         return isset($this->arrData[$strKey]);
  219.     }
  220.     /**
  221.      * Return the name of the primary key
  222.      *
  223.      * @return string The primary key
  224.      */
  225.     public static function getPk()
  226.     {
  227.         return static::$strPk;
  228.     }
  229.     /**
  230.      * Return an array of unique field/column names (without the PK)
  231.      *
  232.      * @return array
  233.      */
  234.     public static function getUniqueFields()
  235.     {
  236.         $objDca DcaExtractor::getInstance(static::getTable());
  237.         return $objDca->getUniqueFields();
  238.     }
  239.     /**
  240.      * Return the name of the related table
  241.      *
  242.      * @return string The table name
  243.      */
  244.     public static function getTable()
  245.     {
  246.         return static::$strTable;
  247.     }
  248.     /**
  249.      * Return the current record as associative array
  250.      *
  251.      * @return array The data record
  252.      */
  253.     public function row()
  254.     {
  255.         return $this->arrData;
  256.     }
  257.     /**
  258.      * Return the original values as associative array
  259.      *
  260.      * @return array The original data
  261.      */
  262.     public function originalRow()
  263.     {
  264.         $row $this->row();
  265.         if (!$this->isModified())
  266.         {
  267.             return $row;
  268.         }
  269.         $originalRow = array();
  270.         foreach ($row as $k=>$v)
  271.         {
  272.             $originalRow[$k] = $this->arrModified[$k] ?? $v;
  273.         }
  274.         return $originalRow;
  275.     }
  276.     /**
  277.      * Return true if the model has been modified
  278.      *
  279.      * @return boolean True if the model has been modified
  280.      */
  281.     public function isModified()
  282.     {
  283.         return !empty($this->arrModified);
  284.     }
  285.     /**
  286.      * Set the current record from an array
  287.      *
  288.      * @param array $arrData The data record
  289.      *
  290.      * @return static The model object
  291.      */
  292.     public function setRow(array $arrData)
  293.     {
  294.         foreach ($arrData as $k=>$v)
  295.         {
  296.             if (strpos($k'__') !== false)
  297.             {
  298.                 unset($arrData[$k]);
  299.             }
  300.         }
  301.         $this->arrData $arrData;
  302.         return $this;
  303.     }
  304.     /**
  305.      * Set the current record from an array preserving modified but unsaved fields
  306.      *
  307.      * @param array $arrData The data record
  308.      *
  309.      * @return static The model object
  310.      */
  311.     public function mergeRow(array $arrData)
  312.     {
  313.         foreach ($arrData as $k=>$v)
  314.         {
  315.             if (strpos($k'__') !== false)
  316.             {
  317.                 continue;
  318.             }
  319.             if (!isset($this->arrModified[$k]))
  320.             {
  321.                 $this->arrData[$k] = $v;
  322.             }
  323.         }
  324.         return $this;
  325.     }
  326.     /**
  327.      * Mark a field as modified
  328.      *
  329.      * @param string $strKey The field key
  330.      */
  331.     public function markModified($strKey)
  332.     {
  333.         if (!isset($this->arrModified[$strKey]))
  334.         {
  335.             $this->arrModified[$strKey] = $this->arrData[$strKey] ?? null;
  336.         }
  337.     }
  338.     /**
  339.      * Return the object instance
  340.      *
  341.      * @return static The model object
  342.      */
  343.     public function current()
  344.     {
  345.         return $this;
  346.     }
  347.     /**
  348.      * Save the current record
  349.      *
  350.      * @return static The model object
  351.      *
  352.      * @throws \InvalidArgumentException If an argument is passed
  353.      * @throws \RuntimeException         If the model cannot be saved
  354.      */
  355.     public function save()
  356.     {
  357.         // Deprecated call
  358.         if (\func_num_args() > 0)
  359.         {
  360.             throw new \InvalidArgumentException('The $blnForceInsert argument has been removed (see system/docs/UPGRADE.md)');
  361.         }
  362.         // The instance cannot be saved
  363.         if ($this->blnPreventSaving)
  364.         {
  365.             throw new \RuntimeException('The model instance has been detached and cannot be saved');
  366.         }
  367.         $objDatabase Database::getInstance();
  368.         $arrFields $objDatabase->getFieldNames(static::$strTable);
  369.         // The model is in the registry
  370.         if (Registry::getInstance()->isRegistered($this))
  371.         {
  372.             $arrSet = array();
  373.             $arrRow $this->row();
  374.             // Only update modified fields
  375.             foreach ($this->arrModified as $k=>$v)
  376.             {
  377.                 // Only set fields that exist in the DB
  378.                 if (\in_array($k$arrFields))
  379.                 {
  380.                     $arrSet[$k] = $arrRow[$k];
  381.                 }
  382.             }
  383.             $arrSet $this->preSave($arrSet);
  384.             // No modified fiels
  385.             if (empty($arrSet))
  386.             {
  387.                 return $this;
  388.             }
  389.             // Track primary key changes
  390.             $intPk $this->arrModified[static::$strPk] ?? $this->{static::$strPk};
  391.             if ($intPk === null)
  392.             {
  393.                 throw new \RuntimeException('The primary key has not been set');
  394.             }
  395.             // Update the row
  396.             $objDatabase->prepare("UPDATE " . static::$strTable " %s WHERE " Database::quoteIdentifier(static::$strPk) . "=?")
  397.                         ->set($arrSet)
  398.                         ->execute($intPk);
  399.             $this->postSave(self::UPDATE);
  400.             $this->arrModified = array(); // reset after postSave()
  401.         }
  402.         // The model is not yet in the registry
  403.         else
  404.         {
  405.             $arrSet $this->row();
  406.             // Remove fields that do not exist in the DB
  407.             foreach ($arrSet as $k=>$v)
  408.             {
  409.                 if (!\in_array($k$arrFields))
  410.                 {
  411.                     unset($arrSet[$k]);
  412.                 }
  413.             }
  414.             $arrSet $this->preSave($arrSet);
  415.             // No modified fiels
  416.             if (empty($arrSet))
  417.             {
  418.                 return $this;
  419.             }
  420.             // Insert a new row
  421.             $stmt $objDatabase->prepare("INSERT INTO " . static::$strTable " %s")
  422.                                 ->set($arrSet)
  423.                                 ->execute();
  424.             if (static::$strPk == 'id')
  425.             {
  426.                 $this->id $stmt->insertId;
  427.             }
  428.             $this->postSave(self::INSERT);
  429.             $this->arrModified = array(); // reset after postSave()
  430.             Registry::getInstance()->register($this);
  431.         }
  432.         return $this;
  433.     }
  434.     /**
  435.      * Modify the current row before it is stored in the database
  436.      *
  437.      * @param array $arrSet The data array
  438.      *
  439.      * @return array The modified data array
  440.      */
  441.     protected function preSave(array $arrSet)
  442.     {
  443.         return $arrSet;
  444.     }
  445.     /**
  446.      * Modify the current row after it has been stored in the database
  447.      *
  448.      * @param integer $intType The query type (Model::INSERT or Model::UPDATE)
  449.      */
  450.     protected function postSave($intType)
  451.     {
  452.         if ($intType == self::INSERT)
  453.         {
  454.             $this->refresh(); // might have been modified by default values or triggers
  455.         }
  456.     }
  457.     /**
  458.      * Delete the current record and return the number of affected rows
  459.      *
  460.      * @return integer The number of affected rows
  461.      */
  462.     public function delete()
  463.     {
  464.         // Track primary key changes
  465.         $intPk $this->arrModified[static::$strPk] ?? $this->{static::$strPk};
  466.         // Delete the row
  467.         $intAffected Database::getInstance()->prepare("DELETE FROM " . static::$strTable " WHERE " Database::quoteIdentifier(static::$strPk) . "=?")
  468.                                                ->execute($intPk)
  469.                                                ->affectedRows;
  470.         if ($intAffected)
  471.         {
  472.             // Unregister the model
  473.             Registry::getInstance()->unregister($this);
  474.             // Remove the primary key (see #6162)
  475.             $this->arrData[static::$strPk] = null;
  476.         }
  477.         return $intAffected;
  478.     }
  479.     /**
  480.      * Lazy load related records
  481.      *
  482.      * @param string $strKey     The property name
  483.      * @param array  $arrOptions An optional options array
  484.      *
  485.      * @return static|Collection|null The model or a model collection if there are multiple rows
  486.      *
  487.      * @throws \Exception If $strKey is not a related field
  488.      */
  489.     public function getRelated($strKey, array $arrOptions=array())
  490.     {
  491.         // The related model has been loaded before
  492.         if (\array_key_exists($strKey$this->arrRelated))
  493.         {
  494.             return $this->arrRelated[$strKey];
  495.         }
  496.         // The relation does not exist
  497.         if (!isset($this->arrRelations[$strKey]))
  498.         {
  499.             $table = static::getTable();
  500.             throw new \Exception("Field $table.$strKey does not seem to be related");
  501.         }
  502.         // The relation exists but there is no reference yet (see #6161 and #458)
  503.         if (empty($this->$strKey))
  504.         {
  505.             return null;
  506.         }
  507.         $arrRelation $this->arrRelations[$strKey];
  508.         /** @var static $strClass */
  509.         $strClass = static::getClassFromTable($arrRelation['table']);
  510.         // Load the related record(s)
  511.         if ($arrRelation['type'] == 'hasOne' || $arrRelation['type'] == 'belongsTo')
  512.         {
  513.             $this->arrRelated[$strKey] = $strClass::findOneBy($arrRelation['field'], $this->$strKey$arrOptions);
  514.         }
  515.         elseif ($arrRelation['type'] == 'hasMany' || $arrRelation['type'] == 'belongsToMany')
  516.         {
  517.             if (isset($arrRelation['delimiter']))
  518.             {
  519.                 $arrValues StringUtil::trimsplit($arrRelation['delimiter'], $this->$strKey);
  520.             }
  521.             else
  522.             {
  523.                 $arrValues StringUtil::deserialize($this->$strKeytrue);
  524.             }
  525.             $objModel null;
  526.             if (\is_array($arrValues))
  527.             {
  528.                 // Handle UUIDs (see #6525 and #8850)
  529.                 if ($arrRelation['table'] == 'tl_files' && $arrRelation['field'] == 'uuid')
  530.                 {
  531.                     /** @var FilesModel $strClass */
  532.                     $objModel $strClass::findMultipleByUuids($arrValues$arrOptions);
  533.                 }
  534.                 else
  535.                 {
  536.                     $strField $arrRelation['table'] . '.' Database::quoteIdentifier($arrRelation['field']);
  537.                     $arrOptions array_merge
  538.                     (
  539.                         array
  540.                         (
  541.                             'order' => Database::getInstance()->findInSet($strField$arrValues)
  542.                         ),
  543.                         $arrOptions
  544.                     );
  545.                     $objModel $strClass::findBy(array($strField " IN('" implode("','"$arrValues) . "')"), null$arrOptions);
  546.                 }
  547.             }
  548.             $this->arrRelated[$strKey] = $objModel;
  549.         }
  550.         return $this->arrRelated[$strKey];
  551.     }
  552.     /**
  553.      * Reload the data from the database discarding all modifications
  554.      */
  555.     public function refresh()
  556.     {
  557.         // Track primary key changes
  558.         $intPk $this->arrModified[static::$strPk] ?? $this->{static::$strPk};
  559.         // Reload the database record
  560.         $res Database::getInstance()->prepare("SELECT * FROM " . static::$strTable " WHERE " Database::quoteIdentifier(static::$strPk) . "=?")
  561.                                        ->execute($intPk);
  562.         $this->setRow($res->row());
  563.     }
  564.     /**
  565.      * Detach the model from the registry
  566.      *
  567.      * @param boolean $blnKeepClone Keeps a clone of the model in the registry
  568.      */
  569.     public function detach($blnKeepClone=true)
  570.     {
  571.         $registry Registry::getInstance();
  572.         if (!$registry->isRegistered($this))
  573.         {
  574.             return;
  575.         }
  576.         $registry->unregister($this);
  577.         if ($blnKeepClone)
  578.         {
  579.             $this->cloneOriginal()->attach();
  580.         }
  581.     }
  582.     /**
  583.      * Attach the model to the registry
  584.      */
  585.     public function attach()
  586.     {
  587.         Registry::getInstance()->register($this);
  588.     }
  589.     /**
  590.      * Called when the model is attached to the model registry
  591.      *
  592.      * @param Registry $registry The model registry
  593.      */
  594.     public function onRegister(Registry $registry)
  595.     {
  596.         // Register aliases to unique fields
  597.         foreach (static::getUniqueFields() as $strColumn)
  598.         {
  599.             $varAliasValue $this->{$strColumn};
  600.             if (!$registry->isRegisteredAlias($this$strColumn$varAliasValue))
  601.             {
  602.                 $registry->registerAlias($this$strColumn$varAliasValue);
  603.             }
  604.         }
  605.     }
  606.     /**
  607.      * Called when the model is detached from the model registry
  608.      *
  609.      * @param Registry $registry The model registry
  610.      */
  611.     public function onUnregister(Registry $registry)
  612.     {
  613.         // Unregister aliases to unique fields
  614.         foreach (static::getUniqueFields() as $strColumn)
  615.         {
  616.             $varAliasValue $this->{$strColumn};
  617.             if ($registry->isRegisteredAlias($this$strColumn$varAliasValue))
  618.             {
  619.                 $registry->unregisterAlias($this$strColumn$varAliasValue);
  620.             }
  621.         }
  622.     }
  623.     /**
  624.      * Prevent saving the model
  625.      *
  626.      * @param boolean $blnKeepClone Keeps a clone of the model in the registry
  627.      */
  628.     public function preventSaving($blnKeepClone=true)
  629.     {
  630.         $this->detach($blnKeepClone);
  631.         $this->blnPreventSaving true;
  632.     }
  633.     /**
  634.      * Find a single record by its primary key
  635.      *
  636.      * @param mixed $varValue   The property value
  637.      * @param array $arrOptions An optional options array
  638.      *
  639.      * @return static The model or null if the result is empty
  640.      */
  641.     public static function findByPk($varValue, array $arrOptions=array())
  642.     {
  643.         // Try to load from the registry
  644.         if (empty($arrOptions))
  645.         {
  646.             $objModel Registry::getInstance()->fetch(static::$strTable$varValue);
  647.             if ($objModel !== null)
  648.             {
  649.                 return $objModel;
  650.             }
  651.         }
  652.         $arrOptions array_merge
  653.         (
  654.             array
  655.             (
  656.                 'limit'  => 1,
  657.                 'column' => static::$strPk,
  658.                 'value'  => $varValue,
  659.                 'return' => 'Model'
  660.             ),
  661.             $arrOptions
  662.         );
  663.         return static::find($arrOptions);
  664.     }
  665.     /**
  666.      * Find a single record by its ID or alias
  667.      *
  668.      * @param mixed $varId      The ID or alias
  669.      * @param array $arrOptions An optional options array
  670.      *
  671.      * @return static The model or null if the result is empty
  672.      */
  673.     public static function findByIdOrAlias($varId, array $arrOptions=array())
  674.     {
  675.         $isAlias = !preg_match('/^[1-9]\d*$/'$varId);
  676.         // Try to load from the registry
  677.         if (!$isAlias && empty($arrOptions))
  678.         {
  679.             $objModel Registry::getInstance()->fetch(static::$strTable$varId);
  680.             if ($objModel !== null)
  681.             {
  682.                 return $objModel;
  683.             }
  684.         }
  685.         $t = static::$strTable;
  686.         $arrOptions array_merge
  687.         (
  688.             array
  689.             (
  690.                 'limit'  => 1,
  691.                 'column' => $isAlias ? array("BINARY $t.alias=?") : array("$t.id=?"),
  692.                 'value'  => $varId,
  693.                 'return' => 'Model'
  694.             ),
  695.             $arrOptions
  696.         );
  697.         return static::find($arrOptions);
  698.     }
  699.     /**
  700.      * Find multiple records by their IDs
  701.      *
  702.      * @param array $arrIds     An array of IDs
  703.      * @param array $arrOptions An optional options array
  704.      *
  705.      * @return Collection|null The model collection or null if there are no records
  706.      */
  707.     public static function findMultipleByIds($arrIds, array $arrOptions=array())
  708.     {
  709.         if (empty($arrIds) || !\is_array($arrIds))
  710.         {
  711.             return null;
  712.         }
  713.         $arrRegistered = array();
  714.         $arrUnregistered = array();
  715.         // Search for registered models
  716.         foreach ($arrIds as $intId)
  717.         {
  718.             if (empty($arrOptions))
  719.             {
  720.                 $arrRegistered[$intId] = Registry::getInstance()->fetch(static::$strTable$intId);
  721.             }
  722.             if (!isset($arrRegistered[$intId]))
  723.             {
  724.                 $arrUnregistered[] = $intId;
  725.             }
  726.         }
  727.         // Fetch only the missing models from the database
  728.         if (!empty($arrUnregistered))
  729.         {
  730.             $t = static::$strTable;
  731.             $arrOptions array_merge
  732.             (
  733.                 array
  734.                 (
  735.                     'column' => array("$t.id IN(" implode(','array_map('\intval'$arrUnregistered)) . ")"),
  736.                     'value'  => null,
  737.                     'order'  => Database::getInstance()->findInSet("$t.id"$arrIds),
  738.                     'return' => 'Collection'
  739.                 ),
  740.                 $arrOptions
  741.             );
  742.             $objMissing = static::find($arrOptions);
  743.             if ($objMissing !== null)
  744.             {
  745.                 foreach ($objMissing as $objCurrent)
  746.                 {
  747.                     $intId $objCurrent->{static::$strPk};
  748.                     $arrRegistered[$intId] = $objCurrent;
  749.                 }
  750.             }
  751.         }
  752.         $arrRegistered array_filter(array_values($arrRegistered));
  753.         if (empty($arrRegistered))
  754.         {
  755.             return null;
  756.         }
  757.         return static::createCollection($arrRegistered, static::$strTable);
  758.     }
  759.     /**
  760.      * Find a single record by various criteria
  761.      *
  762.      * @param mixed $strColumn  The property name
  763.      * @param mixed $varValue   The property value
  764.      * @param array $arrOptions An optional options array
  765.      *
  766.      * @return static The model or null if the result is empty
  767.      */
  768.     public static function findOneBy($strColumn$varValue, array $arrOptions=array())
  769.     {
  770.         $arrOptions array_merge
  771.         (
  772.             array
  773.             (
  774.                 'limit'  => 1,
  775.                 'column' => $strColumn,
  776.                 'value'  => $varValue,
  777.                 'return' => 'Model'
  778.             ),
  779.             $arrOptions
  780.         );
  781.         return static::find($arrOptions);
  782.     }
  783.     /**
  784.      * Find records by various criteria
  785.      *
  786.      * @param mixed $strColumn  The property name
  787.      * @param mixed $varValue   The property value
  788.      * @param array $arrOptions An optional options array
  789.      *
  790.      * @return static|Collection|null A model, model collection or null if the result is empty
  791.      */
  792.     public static function findBy($strColumn$varValue, array $arrOptions=array())
  793.     {
  794.         $blnModel false;
  795.         $arrColumn = (array) $strColumn;
  796.         if (\count($arrColumn) == && ($arrColumn[0] === static::getPk() || \in_array($arrColumn[0], static::getUniqueFields())))
  797.         {
  798.             $blnModel true;
  799.         }
  800.         $arrOptions array_merge
  801.         (
  802.             array
  803.             (
  804.                 'column' => $strColumn,
  805.                 'value'  => $varValue,
  806.                 'return' => $blnModel 'Model' 'Collection'
  807.             ),
  808.             $arrOptions
  809.         );
  810.         return static::find($arrOptions);
  811.     }
  812.     /**
  813.      * Find all records
  814.      *
  815.      * @param array $arrOptions An optional options array
  816.      *
  817.      * @return Collection|null The model collection or null if the result is empty
  818.      */
  819.     public static function findAll(array $arrOptions=array())
  820.     {
  821.         $arrOptions array_merge
  822.         (
  823.             array
  824.             (
  825.                 'return' => 'Collection'
  826.             ),
  827.             $arrOptions
  828.         );
  829.         return static::find($arrOptions);
  830.     }
  831.     /**
  832.      * Magic method to map Model::findByName() to Model::findBy('name')
  833.      *
  834.      * @param string $name The method name
  835.      * @param array  $args The passed arguments
  836.      *
  837.      * @return static|Collection|integer|null A model or model collection
  838.      *
  839.      * @throws \Exception If the method name is invalid
  840.      */
  841.     public static function __callStatic($name$args)
  842.     {
  843.         if (strncmp($name'findBy'6) === 0)
  844.         {
  845.             array_unshift($argslcfirst(substr($name6)));
  846.             return static::findBy(...$args);
  847.         }
  848.         if (strncmp($name'findOneBy'9) === 0)
  849.         {
  850.             array_unshift($argslcfirst(substr($name9)));
  851.             return static::findOneBy(...$args);
  852.         }
  853.         if (strncmp($name'countBy'7) === 0)
  854.         {
  855.             array_unshift($argslcfirst(substr($name7)));
  856.             return static::countBy(...$args);
  857.         }
  858.         throw new \Exception("Unknown method $name");
  859.     }
  860.     /**
  861.      * Find records and return the model or model collection
  862.      *
  863.      * Supported options:
  864.      *
  865.      * * column: the field name
  866.      * * value:  the field value
  867.      * * limit:  the maximum number of rows
  868.      * * offset: the number of rows to skip
  869.      * * order:  the sorting order
  870.      * * eager:  load all related records eagerly
  871.      *
  872.      * @param array $arrOptions The options array
  873.      *
  874.      * @return Model|Model[]|Collection|null A model, model collection or null if the result is empty
  875.      */
  876.     protected static function find(array $arrOptions)
  877.     {
  878.         if (!static::$strTable)
  879.         {
  880.             return null;
  881.         }
  882.         // Try to load from the registry
  883.         if (($arrOptions['return'] ?? null) == 'Model')
  884.         {
  885.             $arrColumn = (array) $arrOptions['column'];
  886.             if (\count($arrColumn) == 1)
  887.             {
  888.                 // Support table prefixes
  889.                 $arrColumn[0] = preg_replace('/^' preg_quote(static::getTable(), '/') . '\./'''$arrColumn[0]);
  890.                 if ($arrColumn[0] == static::$strPk || \in_array($arrColumn[0], static::getUniqueFields()))
  891.                 {
  892.                     $varKey = \is_array($arrOptions['value']) ? $arrOptions['value'][0] : $arrOptions['value'];
  893.                     $objModel Registry::getInstance()->fetch(static::$strTable$varKey$arrColumn[0]);
  894.                     if ($objModel !== null)
  895.                     {
  896.                         return $objModel;
  897.                     }
  898.                 }
  899.             }
  900.         }
  901.         $arrOptions['table'] = static::$strTable;
  902.         $strQuery = static::buildFindQuery($arrOptions);
  903.         $objStatement Database::getInstance()->prepare($strQuery);
  904.         // Defaults for limit and offset
  905.         if (!isset($arrOptions['limit']))
  906.         {
  907.             $arrOptions['limit'] = 0;
  908.         }
  909.         if (!isset($arrOptions['offset']))
  910.         {
  911.             $arrOptions['offset'] = 0;
  912.         }
  913.         // Limit
  914.         if ($arrOptions['limit'] > || $arrOptions['offset'] > 0)
  915.         {
  916.             $objStatement->limit($arrOptions['limit'], $arrOptions['offset']);
  917.         }
  918.         $objStatement = static::preFind($objStatement);
  919.         $objResult $objStatement->execute($arrOptions['value'] ?? null);
  920.         if ($objResult->numRows 1)
  921.         {
  922.             return ($arrOptions['return'] ?? null) == 'Array' ? array() : null;
  923.         }
  924.         $objResult = static::postFind($objResult);
  925.         // Try to load from the registry
  926.         if (($arrOptions['return'] ?? null) == 'Model')
  927.         {
  928.             $objModel Registry::getInstance()->fetch(static::$strTable$objResult->{static::$strPk});
  929.             if ($objModel !== null)
  930.             {
  931.                 return $objModel->mergeRow($objResult->row());
  932.             }
  933.             return static::createModelFromDbResult($objResult);
  934.         }
  935.         if (($arrOptions['return'] ?? null) == 'Array')
  936.         {
  937.             return static::createCollectionFromDbResult($objResult, static::$strTable)->getModels();
  938.         }
  939.         return static::createCollectionFromDbResult($objResult, static::$strTable);
  940.     }
  941.     /**
  942.      * Modify the database statement before it is executed
  943.      *
  944.      * @param Statement $objStatement The database statement object
  945.      *
  946.      * @return Statement The database statement object
  947.      */
  948.     protected static function preFind(Statement $objStatement)
  949.     {
  950.         return $objStatement;
  951.     }
  952.     /**
  953.      * Modify the database result before the model is created
  954.      *
  955.      * @param Result $objResult The database result object
  956.      *
  957.      * @return Result The database result object
  958.      */
  959.     protected static function postFind(Result $objResult)
  960.     {
  961.         return $objResult;
  962.     }
  963.     /**
  964.      * Return the number of records matching certain criteria
  965.      *
  966.      * @param mixed $strColumn  An optional property name
  967.      * @param mixed $varValue   An optional property value
  968.      * @param array $arrOptions An optional options array
  969.      *
  970.      * @return integer The number of matching rows
  971.      */
  972.     public static function countBy($strColumn=null$varValue=null, array $arrOptions=array())
  973.     {
  974.         if (!static::$strTable)
  975.         {
  976.             return 0;
  977.         }
  978.         $arrOptions array_merge
  979.         (
  980.             array
  981.             (
  982.                 'table'  => static::$strTable,
  983.                 'column' => $strColumn,
  984.                 'value'  => $varValue
  985.             ),
  986.             $arrOptions
  987.         );
  988.         $strQuery = static::buildCountQuery($arrOptions);
  989.         return (int) Database::getInstance()->prepare($strQuery)->execute($arrOptions['value'])->count;
  990.     }
  991.     /**
  992.      * Return the total number of rows
  993.      *
  994.      * @return integer The total number of rows
  995.      */
  996.     public static function countAll()
  997.     {
  998.         return static::countBy();
  999.     }
  1000.     /**
  1001.      * Compile a Model class name from a table name (e.g. tl_form_field becomes FormFieldModel)
  1002.      *
  1003.      * @param string $strTable The table name
  1004.      *
  1005.      * @return string The model class name
  1006.      */
  1007.     public static function getClassFromTable($strTable)
  1008.     {
  1009.         if (isset(static::$arrClassNames[$strTable]))
  1010.         {
  1011.             return static::$arrClassNames[$strTable];
  1012.         }
  1013.         if (isset($GLOBALS['TL_MODELS'][$strTable]))
  1014.         {
  1015.             static::$arrClassNames[$strTable] = $GLOBALS['TL_MODELS'][$strTable]; // see 4796
  1016.             return static::$arrClassNames[$strTable];
  1017.         }
  1018.         trigger_deprecation('contao/core-bundle''4.10'sprintf('Not registering table "%s" in $GLOBALS[\'TL_MODELS\'] has been deprecated and will no longer work in Contao 5.0.'$strTable));
  1019.         $arrChunks explode('_'$strTable);
  1020.         if ($arrChunks[0] == 'tl')
  1021.         {
  1022.             array_shift($arrChunks);
  1023.         }
  1024.         static::$arrClassNames[$strTable] = implode(''array_map('ucfirst'$arrChunks)) . 'Model';
  1025.         return static::$arrClassNames[$strTable];
  1026.     }
  1027.     /**
  1028.      * Build a query based on the given options
  1029.      *
  1030.      * @param array $arrOptions The options array
  1031.      *
  1032.      * @return string The query string
  1033.      */
  1034.     protected static function buildFindQuery(array $arrOptions)
  1035.     {
  1036.         return QueryBuilder::find($arrOptions);
  1037.     }
  1038.     /**
  1039.      * Build a query based on the given options to count the number of records
  1040.      *
  1041.      * @param array $arrOptions The options array
  1042.      *
  1043.      * @return string The query string
  1044.      */
  1045.     protected static function buildCountQuery(array $arrOptions)
  1046.     {
  1047.         return QueryBuilder::count($arrOptions);
  1048.     }
  1049.     /**
  1050.      * Create a model from a database result
  1051.      *
  1052.      * @param Result $objResult The database result object
  1053.      *
  1054.      * @return static The model
  1055.      */
  1056.     protected static function createModelFromDbResult(Result $objResult)
  1057.     {
  1058.         /**
  1059.          * @var static               $strClass
  1060.          * @var class-string<static> $strClass
  1061.          */
  1062.         $strClass = static::getClassFromTable(static::$strTable);
  1063.         return new $strClass($objResult);
  1064.     }
  1065.     /**
  1066.      * Create a Collection object
  1067.      *
  1068.      * @param array  $arrModels An array of models
  1069.      * @param string $strTable  The table name
  1070.      *
  1071.      * @return Collection The Collection object
  1072.      */
  1073.     protected static function createCollection(array $arrModels$strTable)
  1074.     {
  1075.         return new Collection($arrModels$strTable);
  1076.     }
  1077.     /**
  1078.      * Create a new collection from a database result
  1079.      *
  1080.      * @param Result $objResult The database result object
  1081.      * @param string $strTable  The table name
  1082.      *
  1083.      * @return Collection The model collection
  1084.      */
  1085.     protected static function createCollectionFromDbResult(Result $objResult$strTable)
  1086.     {
  1087.         return Collection::createFromDbResult($objResult$strTable);
  1088.     }
  1089.     /**
  1090.      * Check if the preview mode is enabled
  1091.      *
  1092.      * @param array $arrOptions The options array
  1093.      *
  1094.      * @return boolean
  1095.      */
  1096.     protected static function isPreviewMode(array $arrOptions)
  1097.     {
  1098.         if (isset($arrOptions['ignoreFePreview']))
  1099.         {
  1100.             return false;
  1101.         }
  1102.         return \defined('BE_USER_LOGGED_IN') && BE_USER_LOGGED_IN === true;
  1103.     }
  1104. }
  1105. class_alias(Model::class, 'Model');