Symfony uygulaması - nasıl nesneleri uskur için hesaplanan alanlar eklemek için?

5 Cevap php

Tahrik nesnelerin hesaplanan alanlar ile çalışan en iyi yolu nedir?

Ben bir tablonun "müşteri" vardır ve her kolon benim nesnenin bir niteliği karşılık bir nesne "Müşteri" söylüyorlar. Ne yapmak istiyorum: ama değil İzlenme B ve C görüntüle A kullanırken benim nesnesi için hesaplanmış nitelik "tamamlanmış siparişlerin sayısı" add

Hesaplanan öznitelik kimliği ile benim "Müşteri" nesnesine bağlı "Sipariş" nesneleri bir COUNT () 'dir.

Ben şimdi ne İlk, tüm Müşteri nesneleri seçmek sonra iteratif hepsi Siparişleri saymak, ama ben tek bir sorguda bunu yapıyor performansını artırmak düşünürdüm etmektir. Ama olamaz düzgün "hidrat" it hesaplanan alan (lar) tanımını içermiyor beri benim uskur nesne.

Bunu nasıl yaklaşım?

5 Cevap

Çeşitli seçenekler vardır. Birincisi benim cevap benzer sizin için sayımları yapacak DB bir görünüm oluşturmak için here. Ben salt okunur belirli bir tablo için niteliklerini nerede çalışmak bir akım Symfony proje için bunu çok aslında tablonun kendisini çok daha geniştir. Bu sütunları (max (), count (), vb) gruplama beri benim tavsiye salt okunur zaten.

Diğer seçenekler aslında modeline bu işlevselliği inşa etmek. Kesinlikle bu hidrasyon kendiniz yapmak CAN, ama biraz karışık. Burada kaba adımlar var

  1. Korumalı veri üyeleri olarak Table sınıfına sütun ekleyin.
  2. Bu sütunlar için uygun alıcı ve ayarlayıcıları yazın
  3. Hidrat yöntemini geçersiz kılar ve içinde, diğer sorguları gelen verilerle birlikte yeni sütunları doldurmak. Emin ilk satır olarak parent :: hidrat () çağrısı emin olun

Ancak, bu zaten bahsettiğini daha iyi değildir. Hala N + 1 sorguları tek bir kayıt kümesi almak gerekir. Ancak, böylece N hesaplanan sütun sayısı değil, satır numarası döndürülür olduğunu adım # 3 yaratıcı alabilirsiniz.

Başka bir seçenek Table Akran sınıf bir özel seçim yöntem oluşturmaktır.

  1. Yukarıdaki 1. ve 2. adımları yapın.
  2. Eğer Propel :: getConnection () işlemi ile manuel sorgulamak özel SQL yazın.
  3. Sonuç kümesi üzerinde yineleme tarafından elle veri kümesi oluşturmak ve zaman hidrasyon kırmak değil bu noktada özel hidrasyon işlemek doSelect süreçleri tarafından kullanılması.

İşte bu yaklaşımın bir örneği

<?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;
    }
}

Orada sorununuza başka çözümler olabilir, ama onlar benim bilgim dışındadır olabilir. İyi şanslar!

Burada herhangi bir ek sorguları olmadan bu çözmek için yaptım:

Problem

Symfony Çağrı cihazı ile kullanılan tipik bir sonuç kümesiyle özel COUNT alanı eklemek gerekiyordu. Bildiğimiz gibi Ancak, uskur kutusunu bunu desteklemiyor. Yani kolay bir çözüm sadece şablonda böyle bir şey yapmak için:

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

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

endforeach;

getNumMembers() her $project nesne için ayrı bir COUNT sorguyu çalıştırır nerede. Tabii ki, biz size gösterilen her sonuç için bir sorgu tasarruf, orijinal SELECT sorgusu için bir sütun olarak ekleyerek anında SAYISININ yapabilirim çünkü bu ağır verimsiz olduğunu biliyorum.

Ben bu sonuç kümesi, tüm kullanarak farklı Kriterlerine gösteren birçok farklı sayfaları vardı. Ben onun içinde ne dayalı bir sorgu dizesi oluşturmak için çalışırken etrafında Kriterleri nesne ve karmaşa içine almak zorundayız şekilde PDO ile kendi SQL sorgu dizesini yazarken doğrudan yol çok fazla güçlük olurdu!

Peki, ben sonunda yaptım Kriterleri itmek yerli kod işini icar, bütün bu engeller ve her zamanki gibi SQL oluşturun.

1 - Öncelikle [/ set olsun] NumMembers (doSelect tarafından () dönen alır modeli nesne) eşdeğer accessor / mutator yöntemleri oluşturmak. Erişimci artık COUNT sorgu yapmaz, Unutmayın, sadece kendi değerini tutar.

2 - Akran sınıfa gidin ve ana doSelect () yöntemini geçersiz kılar ve tam olduğu gibi ondan tüm kodu kopyalayın

3 - getMixerPreSelectHook baz eş özel bir yöntemdir, çünkü bu biraz kaldırın (veya ihtiyacınız eğer eş içine kopyalayın):

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

4 - Şimdi akran sınıfında doSelect yönteme özel COUNT alanı ekleyebilirsiniz:

// 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;         
}

İşte bu. Şimdi ek COUNT alanı, sonuçlarını tükürmek olarak almak için ayrı bir sorgu yapmadan nesne ekledi. Bu çözüm tek dezavantajı doğru bunun ortasında bit eklemek gerekir çünkü tüm üst kodu kopyalayın yaşadım olmasıdır. Ama benim durumda, bu tüm bu sorguları kaydetmek ve kendi SQL sorgu dizesini yazmak değil, küçük bir uzlaşma gibi görünüyordu.

Bir Müşteriye bir nitelik "orders_count" ekleyin ve sonra bu gibi bir şey yazmak:

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

Sen "save" yöntemi değil, sadece kullanabilirsiniz, ama fikir aynı kalır. Ne yazık ki, uskur gibi alanlar için herhangi bir "sihirli" desteklemiyor.

Tahrik aslında bağlantılı alanın adına göre otomatik bir işlev oluşturur. Diyelim ki böyle bir şema var diyelim:

customer:
  id:
  name:
  ...

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

Eğer modeli oluştururken, Müşteri nesne, o müşteri ile ilişkili tüm emir almak bir yöntem GetOrders () sahip olacaktır. Daha sonra sadece o müşteri için sipariş numarasını almak için sayılmasını ($ müşteri-> GetOrders ()) kullanabilirsiniz.

Dezavantajı, bu da getir ve bu al nesneleri hidrat olacaktır. Çoğu RDBMS, kayıtları çekerek veya SAYISININ kullanarak arasındaki tek performans farkı () set sonuçları döndürmek için kullanılan bant genişliği. Bu bant genişliği uygulamanız için önemli olurdu, sen SAYISININ elle Creole () kullanarak sorgu oluşturur Müşteri nesnesinde bir yöntemi oluşturmak isteyebilirsiniz:

  // 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');
    }
    ...
  }

I hidrat () ve PostGIS alanları erişmek için Peer :: addSelectColumns () geçersiz kılarak şimdi bir projede bu yapıyorum:

// 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;
}

Orada AddAsColumn ile aptal bir şey () ama ben şu anda hatırlamıyorum, ama bu işi yapar. You can read more about the AddAsColumn() issues.