Symfony 앱 - Propel 객체에 계산된 필드를 추가하는 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/246400

  •  05-07-2019
  •  | 
  •  

문제

Propel 개체의 계산된 필드를 사용하는 가장 좋은 방법은 무엇입니까?

"고객" 테이블이 있고 각 열이 내 개체의 속성에 해당하는 "고객" 개체가 있다고 가정해 보겠습니다.내가 하고 싶은 것은:보기 A에서는 사용할 때 계산된 속성 "완료된 주문 수"를 내 개체에 추가하지만 보기 B 및 C에서는 사용할 수 없습니다.

계산된 속성은 ID를 통해 내 "고객" 개체에 연결된 "주문" 개체의 COUNT()입니다.

지금 할 수 있는 일은 먼저 모든 Customer 개체를 선택한 다음 모든 개체에 대해 반복적으로 Orders를 계산하는 것입니다. 그러나 단일 쿼리에서 수행하면 성능이 향상될 것이라고 생각합니다.그러나 Propel 개체에 계산된 필드의 정의가 포함되어 있지 않기 때문에 제대로 "수화"할 수 없습니다.

어떻게 접근하시겠습니까?

도움이 되었습니까?

해결책

몇 가지 선택이 있습니다.첫째, 내 대답과 비슷하게 DB에 카운트를 수행할 뷰를 만드는 것입니다. 여기.나는 주어진 테이블에 대한 읽기 전용 속성이 실제로 테이블 자체보다 훨씬 더 넓은 현재 작업 중인 Symfony 프로젝트에 대해 이 작업을 수행합니다.어쨌든 그룹화 열(max(), count() 등)은 읽기 전용이므로 이것이 권장 사항입니다.

다른 옵션은 실제로 이 기능을 모델에 구축하는 것입니다.이 수분 공급은 스스로 할 수 있지만 조금 복잡합니다.대략적인 단계는 다음과 같습니다.

  1. 열을 테이블 보호된 데이터 멤버로 클래스.
  2. 이 열에 적합한 getter 및 setter를 작성합니다.
  3. 수화물 방법을 재정의하고 다른 쿼리의 데이터로 새 열을 채웁니다.첫 번째 줄로 parent::hydrate()를 호출해야 합니다.

그러나 이것은 이미 당신이 말하는 것보다 훨씬 낫지 않습니다.당신은 여전히 ​​​​필요할 것입니다 N + 단일 레코드 세트를 검색하는 쿼리 1개.하지만 3단계에서는 창의력을 발휘하여 N 반환된 행 수가 아니라 계산된 열 수입니다.

또 다른 옵션은 테이블동료 수업.

  1. 위의 1단계와 2단계를 수행합니다.
  2. Propel::getConnection() 프로세스를 통해 수동으로 쿼리할 사용자 정의 SQL을 작성합니다.
  3. 결과 집합을 반복하여 데이터 집합을 수동으로 생성하고 doSelect 프로세스에서 사용할 때 수화를 중단하지 않도록 이 시점에서 사용자 지정 수화를 처리합니다.

이 접근 방식의 예는 다음과 같습니다.

<?php

class TablePeer extends BaseTablePeer
{
    public static function selectWithCalculatedColumns()
    {
        //  Do our custom selection, still using propel's column data constants
        $sql = "
            SELECT " . implode( ', ', self::getFieldNames( BasePeer::TYPE_COLNAME ) ) . "
                 , count(" . JoinedTablePeer::ID . ") AS calc_col
              FROM " . self::TABLE_NAME . "
              LEFT JOIN " . JoinedTablePeer::TABLE_NAME . "
                ON " . JoinedTablePeer::ID . " = " . self::FKEY_COLUMN
        ;

        //  Get the result set
        $conn   = Propel::getConnection();
        $stmt   = $conn->prepareStatement( $sql );
        $rs = $stmt->executeQuery( array(), ResultSet::FETCHMODE_NUM );

        //  Create an empty rowset
        $rowset = array();

        //  Iterate over the result set
        while ( $rs->next() )
        {
            //  Create each row individually
            $row = new Table();
            $startcol = $row->hydrate( $rs );

            //  Use our custom setter to populate the new column
            $row->setCalcCol( $row->get( $startcol ) );
            $rowset[] = $row;
        }
        return $rowset;
    }
}

귀하의 문제에 대한 다른 해결책이 있을 수 있지만 그것은 제가 아는 범위를 벗어납니다.행운을 빌어 요!

다른 팁

PostGIS 필드에 액세스하기 위해 Hydrate () 및 Peer :: AddSelectColumns ()를 재정의하여 프로젝트 에서이 작업을 수행하고 있습니다.

// in peer
public static function locationAsEWKTColumnIndex()
{
    return GeographyPeer::NUM_COLUMNS - GeographyPeer::NUM_LAZY_LOAD_COLUMNS;
}

public static function polygonAsEWKTColumnIndex()
{
    return GeographyPeer::NUM_COLUMNS - GeographyPeer::NUM_LAZY_LOAD_COLUMNS + 1;
}

public static function addSelectColumns(Criteria $criteria)
{
    parent::addSelectColumns($criteria);
    $criteria->addAsColumn("locationAsEWKT", "AsEWKT(" . GeographyPeer::LOCATION . ")");
    $criteria->addAsColumn("polygonAsEWKT", "AsEWKT(" . GeographyPeer::POLYGON . ")");
}
// in object
public function hydrate($row, $startcol = 0, $rehydrate = false)
{
    $r = parent::hydrate($row, $startcol, $rehydrate);
    if ($row[GeographyPeer::locationAsEWKTColumnIndex()])   // load GIS info from DB IFF the location field is populated. NOTE: These fields are either both NULL or both NOT NULL, so this IF is OK
    {
        $this->location_ = GeoPoint::PointFromEWKT($row[GeographyPeer::locationAsEWKTColumnIndex()]); // load gis data from extra select columns See GeographyPeer::addSelectColumns().
        $this->polygon_ = GeoMultiPolygon::MultiPolygonFromEWKT($row[GeographyPeer::polygonAsEWKTColumnIndex()]); // load gis data from extra select columns See GeographyPeer::addSelectColumns().
    }   
    return $r;
}   

AddAscolumn ()에는 구피가 있지만 지금은 기억이 나지 않지만 이것은 효과가 있습니다. 당신은 할 수 있습니다 AddAscolumn () 문제에 대해 자세히 알아보십시오.

다음은 추가 쿼리없이 이것을 해결하기 위해 한 일입니다.

문제

Symfony Pager와 함께 사용되는 일반적인 결과 세트에 사용자 정의 카운트 필드를 추가해야합니다. 그러나 우리가 알고 있듯이 Propel은 이것을 상자에서 지원하지 않습니다. 쉬운 해결책은 템플릿에서 이와 같은 일을하는 것입니다.

foreach ($pager->getResults() as $project):

 echo $project->getName() . ' and ' . $project->getNumMembers()

endforeach;

어디에 getNumMembers() 각각에 대해 별도의 카운트 쿼리를 실행합니다 $project 물체. 물론, 우리는 원래 선택 쿼리에 열로 추가하여 즉시 쿼리를 저장하여 즉시 쿼리를 저장하여 즉시 계산을 할 수 있기 때문에 이것이 비효율적이라는 것을 알고 있습니다.

다른 기준을 사용 하여이 결과 세트를 표시하는 몇 가지 다른 페이지가있었습니다. 따라서 PDO로 직접 SQL 쿼리 문자열을 직접 작성하면 기준 객체에 들어가야하고 그 안에있는 모든 것을 기반으로 쿼리 문자열을 형성하려고 시도하는 데 엉망이 될 수 있습니다!

따라서 결국 내가 한 일은 Propel의 기본 코드가 기준과 함께 작동하고 평소와 같이 SQL을 생성하도록하는 모든 것을 피합니다.

1- 먼저 doselect ()에 의해 리턴되는 모델 객체에서 [get/set] nummembers () 동등한 액세서/뮤티터 메소드를 만듭니다. 액세서는 더 이상 카운트 쿼리를 수행하지 않고 그 가치 만 보유합니다.

2- 피어 클래스로 이동하여 부모 doselect () 메소드를 재정의하고 모든 코드를 그대로 정확하게 복사하십시오.

3- GetMixerPreselecthook이 기본 피어의 개인 메소드이기 때문에이 비트를 제거하십시오 (또는 필요한 경우 피어에 복사) :

// symfony_behaviors behavior
foreach (sfMixer::getCallables(self::getMixerPreSelectHook(__FUNCTION__)) as $sf_hook)
{
  call_user_func($sf_hook, 'BaseTsProjectPeer', $criteria, $con);
}

4- 이제 피어 클래스의 DoSelect 메소드에 사용자 정의 카운트 필드를 추가하십시오.

// copied into ProjectPeer - overrides BaseProjectPeer::doSelectJoinUser()
public static function doSelectJoinUser(Criteria $criteria, ...)
{
   // copied from parent method, along with everything else
   ProjectPeer::addSelectColumns($criteria);
   $startcol = (ProjectPeer::NUM_COLUMNS - ProjectPeer::NUM_LAZY_LOAD_COLUMNS);
   UserPeer::addSelectColumns($criteria);

   // now add our custom COUNT column after all other columns have been added
   // so as to not screw up Propel's position matching system when hydrating
   // the Project and User objects.
   $criteria->addSelectColumn('COUNT(' . ProjectMemberPeer::ID . ')');

   // now add the GROUP BY clause to count members by project
   $criteria->addGroupByColumn(self::ID);

   // more parent code

   ...

   // until we get to this bit inside the hydrating loop:

   $obj1 = new $cls();
   $obj1->hydrate($row);

   // AND...hydrate our custom COUNT property (the last column)
   $obj1->setNumMembers($row[count($row) - 1]);

   // more code copied from parent

   ...

   return $results;         
}

그게 다야. 이제 결과를 뱉을 때 별도의 쿼리를 수행하지 않고 추가 카운트 필드를 객체에 추가했습니다. 이 솔루션의 유일한 단점은 중간에 비트를 추가해야하기 때문에 모든 상위 코드를 복사해야한다는 것입니다. 그러나 내 상황에서 이것은 모든 쿼리를 저장하고 내 자신의 SQL 쿼리 문자열을 작성하지 않는 작은 타협처럼 보였습니다.

고객에게 "Orders_Count"속성을 추가 한 다음 다음과 같은 내용을 작성하십시오.

class Order {
...
  public function save($conn = null) {
    $customer = $this->getCustomer();
    $customer->setOrdersCount($customer->getOrdersCount() + 1);
    $custoner->save();
    parent::save();
  }
...
}

"저장"방법뿐만 아니라 아이디어는 동일하게 사용될 수 있습니다. 불행히도 Propel은 그러한 필드에 대해 "마법"을 지원하지 않습니다.

Propel은 실제로 연결된 필드의 이름을 기반으로 자동 기능을 작성합니다. 다음과 같은 스키마가 있다고 가정 해 봅시다.

customer:
  id:
  name:
  ...

order:
  id:
  customer_id: # links to customer table automagically
  completed: { type: boolean, default false }
  ...

모델을 구축 할 때 고객 객체에는 해당 고객과 관련된 모든 주문을 검색하는 메소드 getorders ()가 있습니다. 그런 다음 Count ($ Customer-> GetOrders ())를 사용하여 해당 고객의 주문 수를 얻을 수 있습니다.

단점은 이것이 해당 순서 대상을 가져오고 수분을 공급할 것입니다. 대부분의 RDBMS에서 레코드를 당기거나 count ()를 사용하는 것 사이의 유일한 성능 차이는 결과 세트를 반환하는 데 사용되는 대역폭입니다. 해당 대역폭이 애플리케이션에 중요하다면 Creole을 사용하여 COUNT () 쿼리를 수동으로 작성하는 고객 객체에서 메소드를 만들 수 있습니다.

  // in lib/model/Customer.php
  class Customer extends BaseCustomer
  {
    public function CountOrders()
    {
      $connection = Propel::getConnection();
      $query = "SELECT COUNT(*) AS count FROM %s WHERE customer_id='%s'";
      $statement = $connection->prepareStatement(sprintf($query, CustomerPeer::TABLE_NAME, $this->getId());
      $resultset = $statement->executeQuery();
      $resultset->next();
      return $resultset->getInt('count');
    }
    ...
  }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top