Symfony 앱 - Propel 객체에 계산된 필드를 추가하는 방법은 무엇입니까?
문제
Propel 개체의 계산된 필드를 사용하는 가장 좋은 방법은 무엇입니까?
"고객" 테이블이 있고 각 열이 내 개체의 속성에 해당하는 "고객" 개체가 있다고 가정해 보겠습니다.내가 하고 싶은 것은:보기 A에서는 사용할 때 계산된 속성 "완료된 주문 수"를 내 개체에 추가하지만 보기 B 및 C에서는 사용할 수 없습니다.
계산된 속성은 ID를 통해 내 "고객" 개체에 연결된 "주문" 개체의 COUNT()입니다.
지금 할 수 있는 일은 먼저 모든 Customer 개체를 선택한 다음 모든 개체에 대해 반복적으로 Orders를 계산하는 것입니다. 그러나 단일 쿼리에서 수행하면 성능이 향상될 것이라고 생각합니다.그러나 Propel 개체에 계산된 필드의 정의가 포함되어 있지 않기 때문에 제대로 "수화"할 수 없습니다.
어떻게 접근하시겠습니까?
해결책
몇 가지 선택이 있습니다.첫째, 내 대답과 비슷하게 DB에 카운트를 수행할 뷰를 만드는 것입니다. 여기.나는 주어진 테이블에 대한 읽기 전용 속성이 실제로 테이블 자체보다 훨씬 더 넓은 현재 작업 중인 Symfony 프로젝트에 대해 이 작업을 수행합니다.어쨌든 그룹화 열(max(), count() 등)은 읽기 전용이므로 이것이 권장 사항입니다.
다른 옵션은 실제로 이 기능을 모델에 구축하는 것입니다.이 수분 공급은 스스로 할 수 있지만 조금 복잡합니다.대략적인 단계는 다음과 같습니다.
- 열을 테이블 보호된 데이터 멤버로 클래스.
- 이 열에 적합한 getter 및 setter를 작성합니다.
- 수화물 방법을 재정의하고 다른 쿼리의 데이터로 새 열을 채웁니다.첫 번째 줄로 parent::hydrate()를 호출해야 합니다.
그러나 이것은 이미 당신이 말하는 것보다 훨씬 낫지 않습니다.당신은 여전히 필요할 것입니다 N + 단일 레코드 세트를 검색하는 쿼리 1개.하지만 3단계에서는 창의력을 발휘하여 N 반환된 행 수가 아니라 계산된 열 수입니다.
또 다른 옵션은 테이블동료 수업.
- 위의 1단계와 2단계를 수행합니다.
- Propel::getConnection() 프로세스를 통해 수동으로 쿼리할 사용자 정의 SQL을 작성합니다.
- 결과 집합을 반복하여 데이터 집합을 수동으로 생성하고 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');
}
...
}