Laravel Eloquent案例敏感关系

时间:2016-02-09 09:34:01

标签: php sql sql-server laravel eloquent

嗨,我有一些Laravel Eloquent关系的真正问题,我只能猜测是由一个区分大小写的关系造成的,我希望有人在这里可以提供帮助!

以下是我遇到问题的模型:

class DeliveryManifestLines extends Eloquent
{
    protected $table = 'manifests';

    public function sapDelivery()
    {
        return $this->hasOne('Delivery', 'DocNum', 'sap_delivery');
    }

}


class Delivery extends Eloquent
{
    protected $connection = 'sap';
    protected $table = 'ODLN';
    protected $primaryKey = 'DocNum';

    public function deliveryManifest() {
      return $this->belongsTo('DeliveryManifest', 'DocNum', 'sap_delivery');
    }

    public function address()
    {
        return $this->hasOne('Address', 'Address', 'ShipToCode')->where('CardCode', $this->CardCode)->where('AdresType', 'S');
    }

    public function geolocation()
    {
        return $this->hasOne('GeoLocation', 'Address', 'ShipToCode')->where('CardCode', $this->CardCode)->where('AdresType', 'S')->where('Lat', '>', 0)->where('Lng', '>', 0);
    }
}

class Address extends Eloquent
{
    protected $connection = 'sap';
    protected $table = 'CRD1';
    protected $primaryKey = 'Address';

    public function delivery() {
      return $this->belongsTo('Delivery', 'Address', 'ShipToCode');
    }

}

这是我的控制器中的代码,它应该从数据库中获取上述某些模型。

$deliveries = DeliveryManifestLines::with('sapDelivery')->where('manifest_date', $date))->get();

foreach ($deliveries as $delivery) {
    $delivery->sapDelivery->load('address');
}

我正在使用" ->load('address)"无论我尝试了什么,我都无法加载到#34; sapDelivery.address"

在99%的情况下,地址是从数据库中成功加载的,但我遇到过一个案例,其中我遇到了一个我只能认为是由区分大小写引起的问题。

使用Laravel DebugBar我可以看到我的应用程序正在执行以下查询:

SELECT * FROM [CRD1] WHERE [CardCode] = 'P437' AND [AdresType] = 'S' AND [CRD1].[Address] IN ('The Pizza Factory (topping)')

当我在这种情况下转储$delivery->sapDelivery的内容时,地址关系为NULL,但是,当我将SQL语句粘贴到我的数据库控制台并手动执行时,我会返回预期的行。

我可以看到这个地址和成千上万的其他地址之间的唯一区别是地址字段之间存在差异:

在CRD1表中,受影响/预期行的地址字段是"比萨工厂(Topping)"但雄辩的关系是使用AND [CRD1]。[地址] IN(' The Pizza Factory(顶部)')试图找到它我意识到SQL不区分大小写是默认的但是,我无法想到为什么这一行的行为与其他行不同的任何其他原因。

是否有人对可能导致此问题的原因有任何其他想法,并建议任何可能的解决方案,或以任何方式确认我的案例敏感性理论是罪魁祸首。

非常感谢!

2 个答案:

答案 0 :(得分:2)

所以在过去几个月里对这个问题进行了一点思考后,我今天重新审视了这个问题,并在laravel.io找到了一些非常有用的代码,这些代码遇到了我遇到的同样问题。

我建立在MattApril的解决方案之上,提供了一种我能想到的最简单的方法来提供一种在laravel中提供不区分大小写的关系的方法。

要实现这一点,您需要添加一些新类,这些类利用strtolower()函数创建小写键,允许在关系中使用的isset()函数找到不同的套接字但匹配的键:

ModelCI.php (app \ Models \ Eloquent \ ModelCI.php)

<?php

namespace App\Models\Eloquent;

use App\Models\Eloquent\Relations\BelongsToCI;
use App\Models\Eloquent\Relations\HasManyCI;
use App\Models\Eloquent\Relations\HasOneCI;
use Illuminate\Database\Eloquent\Model;


abstract class ModelCI extends Model
{
    /**
     * Define a one-to-many relationship.
     *
     * @param string $related
     * @param string $foreignKey
     * @param string $localKey
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function hasManyCI($related, $foreignKey = null, $localKey = null)
    {
        $foreignKey = $foreignKey ?: $this->getForeignKey();

        $instance = new $related();

        $localKey = $localKey ?: $this->getKeyName();

        return new HasManyCI($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
    }

        /**
         * Define a one-to-one relationship.
         *
         * @param  string  $related
         * @param  string  $foreignKey
         * @param  string  $localKey
         * @return \Illuminate\Database\Eloquent\Relations\HasOne
         */
        public function hasOneCI($related, $foreignKey = null, $localKey = null)
        {
            $foreignKey = $foreignKey ?: $this->getForeignKey();

            $instance = new $related;

            $localKey = $localKey ?: $this->getKeyName();

            return new HasOneCI($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
        }

    /**
     * Define an inverse one-to-one or many relationship.
     *
     * @param  string  $related
     * @param  string  $foreignKey
     * @param  string  $otherKey
     * @param  string  $relation
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function belongsToCI($related, $foreignKey = null, $otherKey = null, $relation = null)
    {
      // If no relation name was given, we will use this debug backtrace to extract
      // the calling method's name and use that as the relationship name as most
      // of the time this will be what we desire to use for the relationships.
      if (is_null($relation))
      {
        list(, $caller) = debug_backtrace(false, 2);

        $relation = $caller['function'];
      }

      // If no foreign key was supplied, we can use a backtrace to guess the proper
      // foreign key name by using the name of the relationship function, which
      // when combined with an "_id" should conventionally match the columns.
      if (is_null($foreignKey))
      {
        $foreignKey = snake_case($relation).'_id';
      }

      $instance = new $related;

      // Once we have the foreign key names, we'll just create a new Eloquent query
      // for the related models and returns the relationship instance which will
      // actually be responsible for retrieving and hydrating every relations.
      $query = $instance->newQuery();

      $otherKey = $otherKey ?: $instance->getKeyName();

      return new BelongsToCI($query, $this, $foreignKey, $otherKey, $relation);
    }
}

BelongsToCI.php (app \ Models \ Eloquent \ Relations \ BelongsToCI.php)

<?php namespace App\Models\Eloquent\Relations;


use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class BelongsToCI extends BelongsTo {
    /**
     * Match the eagerly loaded results to their parents.
     *
     * @param  array   $models
     * @param  \Illuminate\Database\Eloquent\Collection  $results
     * @param  string  $relation
     * @return array
     */
    public function match(array $models, Collection $results, $relation)
    {
        $foreign = $this->foreignKey;

        $other = $this->otherKey;

        // First we will get to build a dictionary of the child models by their primary
        // key of the relationship, then we can easily match the children back onto
        // the parents using that dictionary and the primary key of the children.
        $dictionary = array();

        foreach ($results as $result)
        {
            $dictionary[strtolower($result->getAttribute($other))] = $result;
        }

        // Once we have the dictionary constructed, we can loop through all the parents
        // and match back onto their children using these keys of the dictionary and
        // the primary key of the children to map them onto the correct instances.
        foreach ($models as $model)
        {
            if (isset($dictionary[strtolower($model->$foreign)]))
            {
                $model->setRelation($relation, $dictionary[strtolower($model->$foreign)]);
            }
        }

        return $models;
    }

}

HasManyCI.php (app \ Models \ Eloquent \ Relations \ HasManyCI.php)

<?php namespace App\Models\Eloquent\Relations;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasMany;

class HasManyCI extends HasMany {
    /**
        * Build model dictionary keyed by the relation's foreign key.
        *
        * @param  \Illuminate\Database\Eloquent\Collection  $results
        * @return array
        */
     protected function buildDictionary(Collection $results)
     {
             $dictionary = array();

             $foreign = $this->getPlainForeignKey();

             // First we will create a dictionary of models keyed by the foreign key of the
             // relationship as this will allow us to quickly access all of the related
             // models without having to do nested looping which will be quite slow.
             foreach ($results as $result)
             {
                     $dictionary[strtolower($result->{$foreign})][] = $result;
             }

             return $dictionary;
     }

    /**
        * Match the eagerly loaded results to their many parents.
        *
        * @param  array   $models
        * @param  \Illuminate\Database\Eloquent\Collection  $results
        * @param  string  $relation
        * @param  string  $type
        * @return array
        */
     protected function matchOneOrMany(array $models, Collection $results, $relation, $type)
     {
             $dictionary = $this->buildDictionary($results);


             // Once we have the dictionary we can simply spin through the parent models to
             // link them up with their children using the keyed dictionary to make the
             // matching very convenient and easy work. Then we'll just return them.
             foreach ($models as $model)
             {
                     $key = strtolower( $model->getAttribute($this->localKey) );
                     if (isset($dictionary[$key]))
                     {
                             $value = $this->getRelationValue($dictionary, $key, $type);
                             $model->setRelation($relation, $value);
                     }
             }

             return $models;
     }

}

HasOneCI.php (app \ Models \ Eloquent \ Relations \ HasOneCI.php)

<?php namespace App\Models\Eloquent\Relations;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasOne;

class HasOneCI extends HasOne {

    /**
     * Match the eagerly loaded results to their many parents.
     *
     * @param  array   $models
     * @param  \Illuminate\Database\Eloquent\Collection  $results
     * @param  string  $relation
     * @param  string  $type
     * @return array
     */
    protected function matchOneOrMany(array $models, Collection $results, $relation, $type)
    {
        $dictionary = $this->buildDictionary($results);

        // Once we have the dictionary we can simply spin through the parent models to
        // link them up with their children using the keyed dictionary to make the
        // matching very convenient and easy work. Then we'll just return them.
        foreach ($models as $model)
        {
            $key = strtolower($model->getAttribute($this->localKey));

            if (isset($dictionary[$key]))
            {
                $value = $this->getRelationValue($dictionary, $key, $type);

                $model->setRelation($relation, $value);
            }
        }

        return $models;
    }

    /**
     * Build model dictionary keyed by the relation's foreign key.
     *
     * @param  \Illuminate\Database\Eloquent\Collection  $results
     * @return array
     */
    protected function buildDictionary(Collection $results)
    {
        $dictionary = array();

        $foreign = strtolower($this->getPlainForeignKey());

        // First we will create a dictionary of models keyed by the foreign key of the
        // relationship as this will allow us to quickly access all of the related
        // models without having to do nested looping which will be quite slow.
        foreach ($results as $result)
        {
            $dictionary[$result->{$foreign}][] = $result;
        }

        return $dictionary;
    }

}

要使用新类,您必须定义一个关系:

$this->belongsToCI('Model');

$this->hasManyCI('Model');

$this->hasOneCI('Model');

答案 1 :(得分:-1)

Eloquent使用内部关联数组将相关记录映射并链接到其父对象,并且在具有字符串外键且大小写不同的情况下,某些相关记录将不会被映射。

我最近遇到了同样的问题,决定将解决方案包装在composer软件包中。 https://github.com/TishoTM/eloquent-ci-relations

在安装composer软件包之后,您只需在雄辩的模型中使用特征即可,而无需更改模型中的关系方法上的任何其他内容。

use \TishoTM\Eloquent\Concerns\HasCiRelationships;