symfonyアプリ-計算フィールドをPropelオブジェクトに追加する方法は?
質問
Propelオブジェクトの計算フィールドを操作する最良の方法は何ですか?
「顧客」というオブジェクトがあるとします。対応するテーブル「顧客」があります。各列はオブジェクトの属性に対応しています。私がやりたいことは、計算された属性「完了した注文の数」を追加することです。ビューAおよびビューBおよびCでは使用しない場合、オブジェクトに追加します。
計算された属性は、" Order"のCOUNT()です。私の「顧客」にリンクされたオブジェクトIDを介したオブジェクト。
今できることは、最初にすべてのCustomerオブジェクトを選択してから、それらすべてのOrderを繰り返しカウントすることですが、1つのクエリでそれを行うとパフォーマンスが向上すると思います。しかし、私は適切に「水分補給」できません。計算フィールドの定義が含まれていないため、Propelオブジェクト。
どのようにアプローチしますか?
解決
いくつかの選択肢があります。まず、私の回答こちらと同様に、DBにカウントを行うビューを作成します。これは、特定のテーブルの読み取り専用属性が実際にはるかに広く、テーブル自体よりもはるかに広い現在作業中のSymfonyプロジェクトで実行します。グループ化列(max()、count()など)はとにかく読み取り専用なので、これは私の推奨事項です。
他のオプションは、この機能を実際にモデルに組み込むことです。あなたは絶対にこの水分補給を自分で行うことができますが、それは少し複雑です。大まかな手順は次のとおりです
- 保護されたデータメンバーとして列を Table クラスに追加します。
- これらの列に適切なゲッターとセッターを書く
- ハイドレートメソッドをオーバーライドして、新しい列に他のクエリからのデータを入力します。必ず最初の行として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;
}
}
あなたの問題に対する他の解決策があるかもしれませんが、それらは私の知る範囲を超えています。幸運を祈ります!
他のヒント
私は今、プロジェクトでhydr()とPeer :: addSelectColumns()をオーバーライドしてpostgisフィールドにアクセスすることでこれを行っています:
// 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()の問題の詳細を参照できます。
追加のクエリなしでこれを解決するために私がしたことは次のとおりです:
問題
カスタムCOUNTフィールドをSymfony Pagerで使用される一般的な結果セットに追加する必要があります。しかし、私たちが知っているように、Propelはそのままではこれをサポートしていません。そのため、簡単な解決策は、テンプレートで次のようにするだけです:
foreach ($pager->getResults() as $project):
echo $project->getName() . ' and ' . $project->getNumMembers()
endforeach;
getNumMembers()
は、各 $ project
オブジェクトに対して個別のCOUNTクエリを実行します。もちろん、COUNTを元のSELECTクエリに列として追加し、表示される各結果のクエリを保存することで、COUNTをその場で実行できるため、これは非常に非効率的であることがわかります。
この結果セットを表示するいくつかの異なるページがあり、すべて異なる基準を使用していました。そのため、PDOを使用して独自のSQLクエリ文字列を直接記述すると、Criteriaオブジェクトに入り、その中にあるものに基づいてクエリ文字列を作成しようとするのが面倒になるため、非常に面倒です!
だから、私が最後にやったことはそれをすべて避けて、Propelのネイティブコードを基準で動作させ、通常通りSQLを作成します。
1-最初に、doSelect()によって返されるモデルオブジェクトに[get / set] NumMembers()同等のアクセサー/ミューテーターメソッドを作成します。アクセサはもうCOUNTクエリを実行せず、その値を保持するだけです。
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-カスタムCOUNTフィールドをピアクラスの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;
}
それだけです。これで、別のクエリを実行せずにオブジェクトに追加のCOUNTフィールドを追加して、結果を吐き出すときにオブジェクトを取得できます。このソリューションの唯一の欠点は、すべての親コードをコピーする必要があったことです。なぜなら、その途中でビットを追加する必要があるからです。しかし、私の状況では、これはすべてのクエリを保存し、独自のSQLクエリ文字列を記述しないという小さな妥協のように思えました。
属性&quot; orders_count&quot;を追加します顧客に送信し、次のように記述します:
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 }
...
モデルを構築すると、Customerオブジェクトには、その顧客に関連付けられているすべての注文を取得するgetOrders()メソッドが含まれます。次に、count($ customer-&gt; getOrders())を使用して、その顧客の注文数を取得できます。
欠点は、これらのOrderオブジェクトもフェッチしてハイドレートすることです。ほとんどのRDBMSでは、レコードのプルとCOUNT()の使用の唯一のパフォーマンスの違いは、結果セットを返すために使用される帯域幅です。その帯域幅がアプリケーションにとって重要な場合は、Creoleを使用してCOUNT()クエリを手動で作成するCustomerオブジェクトにメソッドを作成できます。
// 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');
}
...
}