PHP'nin sihirli yöntemleri bağlamında "trigger_error` ve" Exception Exception "


13

Bir meslektaşımla sihirli yöntemlertrigger_error bağlamında (varsa) doğru kullanımı konusunda bir tartışma yaşıyorum . İlk olarak, bu tek durum dışında bundan kaçınılması gerektiğini düşünüyorum .trigger_error

Diyelim ki tek yöntemli bir sınıfımız var foo()

class A {
    public function foo() {
        echo 'bar';
    }
}

Şimdi aynı arayüzü sağlamak istediğimizi, ancak tüm yöntem çağrılarını yakalamak için sihirli bir yöntem kullandığımızı söyleyin

class B {
    public function __call($method, $args) {
        switch (strtolower($method)) {
        case 'foo':
            echo 'bar';
            break;
        }
    }
}

$a = new A;
$b = new B;

$a->foo(); //bar
$b->foo(); //bar

Her iki sınıf da yanıt verme biçimleriyle aynıdır, foo()ancak geçersiz bir yöntem çağrılırken farklılık gösterir.

$a->doesntexist(); //Error
$b->doesntexist(); //Does nothing

Benim iddiam, trigger_errorbilinmeyen bir yöntem yakalandığında sihirli yöntemlerin çağırması gerektiğidir

class B {
    public function __call($method, $args) {
        switch (strtolower($method)) {
        case 'foo':
            echo 'bar';
            break;
        default:
            $class = get_class($this);
            $trace = debug_backtrace();
            $file = $trace[0]['file'];
            $line = $trace[0]['line'];
            trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);
            break;
        }
    }
}

Böylece her iki sınıf da (neredeyse) aynı şekilde davranır

$a->badMethod(); //Call to undefined method A::badMethod() in [..] on line 28
$b->badMethod(); //Call to undefined method B::badMethod() in [..] on line 32

Kullanım durumum bir ActiveRecord uygulamasıdır. Kullandığım __callaslında aynı şeyi yapmak ama da değiştirici sahip yakalamak ve sap yöntemlerine Distinctya Ignore, mesela

selectDistinct()
selectDistinctColumn($column, ..)
selectAll()
selectOne()
select()

veya

insert()
replace()
insertIgnore()
replaceIgnore()

Gibi yöntemler where(), from(), groupBy()vb sabit kodludur.

Yanlışlıkla aradığınızda argümanım vurgulanır insret(). Benim aktif kayıt uygulama tüm yöntemleri kodladı, o zaman bir hata olurdu.

Herhangi bir iyi soyutlamada olduğu gibi, kullanıcı uygulama detaylarından habersiz olmalı ve sadece arayüze güvenmelidir. Sihirli yöntemleri kullanan uygulama neden farklı davranmalı? Her ikisi de bir hata olmalıdır.

Yanıtlar:


7

Aynı ActiveRecord arayüzü iki uygulamaları al ( select(), where()vs.)

class ActiveRecord1 {
    //Hardcodes all methods
}

class ActiveRecord2 {
    //Uses __call to handle some methods, hardcodes the rest
}

Birinci sınıfta geçersiz bir yöntem çağırırsanız, örneğin ActiveRecord1::insret(), varsayılan PHP davranışı bir hatayı tetiklemektir . Geçersiz bir işlev / yöntem çağrısı, makul bir uygulamanın yakalamak ve işlemek isteyeceği bir koşul değildir. Elbette, bir hatanın bir istisna olduğu Ruby veya Python gibi dillerde yakalayabilirsiniz , ancak diğerleri (JavaScript / herhangi bir statik dil / daha fazlası?) Başarısız olacaktır.

PHP'ye dön - eğer her iki sınıf da aynı arabirimi uygularsa, neden aynı davranışı sergilemesinler?

Geçersiz bir yöntem varsa __callveya __callStaticalgılarsa, dilin varsayılan davranışını taklit etmek için bir hata tetiklemelidir

$class = get_class($this);
$trace = debug_backtrace();
$file = $trace[0]['file'];
$line = $trace[0]['line'];
trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);

Hatalar istisnalar (% 100 olmamalıdır) üzerinde kullanılması gerektiğini tartışmıyorum, ancak ben PHP'nin sihirli yöntemlerinin bir istisna - pun amaçlı :) - bu bağlamda dil bağlamında inanıyorum


1
Üzgünüm, ama satın almıyorum. Neden sınıflar ilk PHP uygulanan edildiğinde istisnalar on yıl önce PHP yoktu çünkü koyun olmalı 4.something?
Matthew Scharley

@ Tutarlılık için dikkat edin. Hatalara karşı istisnalar tartışmıyorum (tartışma yok) veya PHP'nin tasarımda başarısız olup olmadığı (yok), bu çok benzersiz durumda, tutarlılık için , dilin davranışını taklit etmek en iyisi
chriso

Tutarlılık her zaman iyi bir şey değildir. Tutarlı ama zayıf bir tasarım, günün sonunda kullanmak için acı veren zayıf bir tasarımdır.
Matthew Scharley

@Matthew true, ancak IMO geçersiz bir yöntem çağrısında bir hatayı tetiklemek kötü tasarım değil 1) birçok (en çok?) Dilde yerleşik ve 2) İstediğiniz tek bir durumu düşünemiyorum için yakalamak geçersiz yöntem çağrısı ve idare?
chriso

@chriso: Evren, sizin veya benim bunun olmasını hayal edemeyeceğimiz kullanım örnekleri olduğu kadar büyük. __call()Dinamik yönlendirme yapmak için kullanıyorsunuz , pistin aşağısında birisinin bunun başarısız olduğu durumu ele almak isteyebileceğini düşünmek gerçekten mantıksız mı? Ne olursa olsun, bu daireler çiziyor, bu yüzden bu benim son yorumum olacak. Ne yapacağınızı yapın, günün sonunda bu bir karar çağrısına gelir: Daha iyi destek ve tutarlılık. Her iki yöntem de özel işlem yapılmaması durumunda uygulama üzerinde aynı etkiye neden olacaktır.
Matthew Scharley

3

Görüşlü düşüncemi oraya atacağım, ancak trigger_errorherhangi bir yerde kullanırsanız , yanlış bir şey yapıyorsunuz. İstisnalar gitmenin yoludur.

İstisnaların avantajları:

  • Yakalanabilirler. Bu büyük bir avantaj ve ihtiyacınız olan tek şey olmalı. İnsanlar aslında bir şeylerin yanlış gitme şansı beklediklerinde farklı bir şey deneyebilirler. Hata size bu fırsatı vermiyor. Özel bir hata işleyici kurmak bile bir istisnayı yakalamak için bir mum tutmaz. Sorunuzdaki yorumlarınızla ilgili olarak, 'makul' bir uygulamanın ne olduğu tamamen uygulamanın bağlamına bağlıdır. İnsanlar kendi durumlarında asla olmayacağını düşünüyorlarsa istisnaları yakalayamazlar. İnsanlara seçenek vermemek bir Bad Thing ™.
  • Yığın izleri. Bir şeyler ters giderse, sorunun nerede ve hangi bağlamda meydana geldiğini bilirsiniz . Hiç bazı temel yöntemlerden bir hatanın nereden geldiğini izlemeye çalıştınız mı? Çok az parametreli bir işlevi çağırırsanız, aradığınız yöntemin başlangıcını vurgulayan ve tamamen nereden çağıracağını belirten işe yaramaz bir hata alırsınız.
  • Açıklık. Yukarıdaki ikisini birleştirdiğinizde daha net bir kod elde edersiniz. Hataları işlemek için özel bir hata işleyici kullanmaya çalışırsanız (örn. Hatalar için yığın izleri oluşturmak için), tüm hata işlemeniz hataların gerçekte üretildiği yerde değil, tek bir işlevdedir.

Endişelerinizi gidermek, var olmayan bir yöntemi aramak geçerli bir olasılık olabilir . Bu tamamen yazdığınız kodun içeriğine bağlıdır, ancak bunun olabileceği bazı durumlar vardır. Kullanım durumunuzu tam olarak ele alan bazı veritabanı sunucuları, başkalarının kullanmadığı bazı işlevlere izin verebilir. Kullanılması try/ catchve istisnalar __call()yetenekleri kontrol etmek için bir işleve vs tamamen farklı bir argümandır.

Ben kullanmak için düşünebildiğim tek kullanım durum trigger_erroriçindir E_USER_WARNINGveya daha düşüktür. Bir şeyi tetiklemek E_USER_ERRORbence her zaman bir hatadır.


Yanıtınız için teşekkürler :) - 1) İstisnaların neredeyse her zaman hatalar üzerinde kullanılması gerektiğini kabul ediyorum - tüm argümanlarınız geçerli noktalar. Ancak .. Ben bu bağlamda argüman başarısız olduğunu düşünüyorum ..
chriso

2
" Var olmayan bir yöntemi çağırmak geçerli bir olasılık olabilir " - Tamamen katılmıyorum. Her dilde (şimdiye kadar kullandığım), var olmayan bir işlevi / yöntemi çağırmak hataya neden olur. Öyle değil yakalanmış ve ele alınması gereken bir durumdur. Statik diller, geçersiz bir yöntem çağrısı ile derlemenize izin vermez ve dinamik diller çağrıya ulaştığında başarısız olur.
chriso

İkinci düzenlememi okursanız kullanım durumumu görürsünüz. Aynı sınıftan biri __call, diğeri sabit kodlu yöntemler kullanarak iki uygulamanız olduğunu varsayalım. Uygulama ayrıntılarını göz ardı ederek, her iki sınıf da aynı arabirimi uygularken neden farklı davranmalıdır? Sabit kodlu yöntemlere sahip sınıfla geçersiz bir yöntem çağırırsanız PHP bir hata tetikler . Kullanılması trigger_error__call veya __callStatic mimikler dilin varsayılan davranışı bağlamında
chriso

@chriso: PHP olmayan dinamik diller, yakalanabilecek bir istisna dışında başarısız olur . Örneğin Ruby, NoMethodErrorarzu ederseniz yakalayabileceğiniz bir atar . Hatalar benim düşünceme göre PHP'de dev bir hatadır. Çekirdeğin hataları bildirmek için bozuk bir yöntem kullanması kendi kodunuzun olması gerektiği anlamına gelmez.
Matthew Scharley

@chriso Çekirdeğin hala hataları kullanmasının tek sebebinin geriye dönük uyumluluk olduğuna inanmak istiyorum. Umarım PHP6, PHP5 gibi bir adım daha ileri gidecek ve hataları tamamen bırakacaktır. Günün sonunda, hem hatalar hem de istisnalar aynı sonucu doğurur: kod yürütmenin derhal sonlandırılması. Bir istisna dışında, neden ve nerede bu kadar kolay olduğunu teşhis edebilirsiniz.
Matthew Scharley

3

Standart PHP hataları eskimiş kabul edilmelidir. PHP, hataları, uyarıları ve bildirimleri tam bir yığın izlemesiyle istisnalara dönüştürmek için yerleşik bir sınıf ErrorException sağlar. Bu şekilde kullanırsınız:

function errorToExceptionHandler($errNo, $errStr, $errFile, $errLine, $errContext)
{
if (error_reporting() == 0) return;
throw new ErrorException($errStr, 0, $errNo, $errFile, $errLine);
}
set_error_handler('errorToExceptionHandler');

Bunu kullanarak, bu soru tartışmalı hale gelir. Yerleşik hatalar artık istisnalar yaratmaktadır ve bu nedenle kendi kodunuz da olmalıdır.


1
Bunu kullanırsanız, E_NOTICEistisnalara dönüşmemek için hatanın türünü kontrol etmeniz gerekir . Bu kötü olur.
Matthew Scharley

1
Hayır, E_NOTICES öğesini Özel Durumlara dönüştürmek iyi! Tüm bildirimler hata olarak kabul edilmelidir; bunları istisnalara dönüştürüp dönüştürmemeniz iyi bir uygulamadır.
Wayne

2
Normalde size katılıyorum, ancak herhangi bir üçüncü taraf kodunu kullanmaya başladığınızda, bu hızla yüzüne düşme eğilimindedir.
Matthew Scharley

Hemen hemen tüm 3. parti kütüphaneler E_STRICT | E_ALL güvenli. Eğer olmayan bir kod kullanıyorsam, içeri girip düzeltirim. Yıllarca sorunsuz çalıştım .
Wayne

Açıkçası daha önce Dual kullanmadınız. Çekirdek böyle gerçekten çok iyi, ancak birçok üçüncü parti modülü değil
Matthew Scharley

0

IMO, bu aşağıdakiler için tamamen geçerli bir kullanım örneğidir trigger_error:

function handleError($errno, $errstring, $errfile, $errline, $errcontext) {
    if (error_reporting() & $errno) {
        // only process when included in error_reporting
        return handleException(new \Exception($errstring, $errno));
    }
    return true;
}

function handleException($exception){
    // Here, you do whatever you want with the generated
    // exceptions. You can store them in a file or database,
    // output them in a debug section of your page or do
    // pretty much anything else with it, as if it's a
    // normal variable

    switch ($code) {
        case E_ERROR:
        case E_CORE_ERROR:
        case E_USER_ERROR:
            // Make sure script exits here
            exit(1);
        default:
            // Let script continue
            return true;
    }
}

// Set error handler to your custom handler
set_error_handler('handleError');
// Set exception handler to your custom handler
set_exception_handler('handleException');


// ---------------------------------- //

// Generate warning
trigger_error('This went wrong, but we can continue', E_USER_WARNING);

// Generate fatal error :
trigger_error('This went horrible wrong', E_USER_ERROR);

Bu stratejiyi kullanarak , işlev içinde $errcontextyaparsanız parametreyi alırsınız . Bu, bazı hata ayıklama amaçları için çok yararlıdır.$exception->getTrace()handleException

Ne yazık ki, bu yalnızca trigger_errordoğrudan içeriğinizden yararlanırsanız çalışır; bu , trigger_errorişlevi takma olarak sarmak için bir işlev / yöntem kullanamayacağınız anlamına gelir (böylece function debug($code, $message) { return trigger_error($message, $code); }bağlam verilerinin izinizde olmasını istiyorsanız bir şey yapamazsınız ).

Daha iyi bir alternatif arıyordum, ancak şu ana kadar hiç bulamadım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.