2024 年 PHP 日本研討會

Final 關鍵字

final 關鍵字可以防止子類別覆寫方法、屬性或常數,方法是在定義前面加上 final。如果類別本身被定義為 final,則它不能被繼承。

範例 #1 Final 方法範例

<?php
class BaseClass {
public function
test() {
echo
"BaseClass::test() called\n";
}

final public function
moreTesting() {
echo
"BaseClass::moreTesting() called\n";
}
}

class
ChildClass extends BaseClass {
public function
moreTesting() {
echo
"ChildClass::moreTesting() called\n";
}
}
// 造成致命錯誤:無法覆寫 final 方法 BaseClass::moreTesting()
?>

範例 #2 Final 類別範例

<?php
final class BaseClass {
public function
test() {
echo
"BaseClass::test() called\n";
}

// 由於類別已經是 final,final 關鍵字是多餘的
final public function moreTesting() {
echo
"BaseClass::moreTesting() called\n";
}
}

class
ChildClass extends BaseClass {
}
// 造成致命錯誤:類別 ChildClass 不可繼承自 final 類別 (BaseClass)
?>

範例 #3 Final 屬性範例,自 PHP 8.4.0 起

<?php
class BaseClass {
final protected
string $test;
}

class
ChildClass extends BaseClass {
public
string $test;
}
// 造成致命錯誤:無法覆寫 final 屬性 BaseClass::$test
?>

範例 #4 Final 常數範例,自 PHP 8.1.0 起

<?php
class Foo
{
final public const
X = "foo";
}

class
Bar extends Foo
{
public const
X = "bar";
}

// 致命錯誤:Bar::X 無法覆寫 final 常數 Foo::X
?>

注意自 PHP 8.0.0 起,除了建構子之外,私有方法不可宣告為 final。

注意宣告為 private(set) 的屬性會隱含地視為 final

新增備註

使用者貢獻的備註 11 則備註

jriddy at gmail dot com
15 年前
給 Java 開發者的注意事項:PHP 中的類別常數不使用 'final' 關鍵字。我們使用 'const' 關鍵字。

https://php.dev.org.tw/manual/en/language.oop5.constants.php
penartur at yandex dot ru
17 年前
請注意,即使 final 方法在父類中被定義為 private,您也無法覆寫它們。
因此,以下範例
<?php
class parentClass {
final private function
someMethod() { }
}
class
childClass extends parentClass {
private function
someMethod() { }
}
?>
會因錯誤「Fatal error: Cannot override final method parentClass::someMethod() in ***.php on line 7」而終止執行。

這種行為看起來有點出乎意料,因為在子類別中,我們無法知道父類別中存在哪些 private 方法,反之亦然。

因此,請記住,如果您定義了一個 private final 方法,則不能在子類別中放置同名的方法。
Rumour
1 年前
自 PHP 8.1 起,類別常數可以被「最終化」。與最熱門的使用者貢獻(很久以前寫的)部分矛盾的是,他們仍然是絕對正確的。
someone dot else at elsewhere dot net
10 年前
@thomas at somewhere dot com

'final' 關鍵字非常有用。繼承也很有用,但可能會被濫用,並在大型應用程式中造成問題。如果您遇到想要擴充的最終類別或方法,請改寫一個裝飾器。

<?php
final class Foo
{
public
method doFoo()
{
// 做一些有用的事情並返回結果
}
}

final class
FooDecorator
{
private
$foo;

public function
__construct(Foo $foo)
{
$this->foo = $foo;
}

public function
doFoo()
{
$result = $this->foo->doFoo();
// ... 自訂結果 ...
return $result;
}
}
?>
slorenzo at clug dot org dot ve
17 年前
<?php
class parentClass {
public function
someMethod() { }
}
class
childClass extends parentClass {
public final function
someMethod() { } //覆寫父類別函式
}

$class = new childClass;
$class->someMethod(); //呼叫子類別中覆寫的函式
?>
mattsch at gmail dot com
10 年前
您可以使用 final 方法來取代類別常數。這樣做的原因是您無法單獨對在另一個類別中使用的類別常數進行單元測試,因為您無法模擬常數。Final 方法允許您擁有與常數相同的功能,同時保持程式碼的低耦合性。

緊密耦合示例(不建議使用常數)

<?php
interface FooInterface
{
}

class
Foo implements FooInterface
{
const
BAR = 1;

public function
__construct()
{
}
}

interface
BazInterface
{
public function
getFooBar();
}

// 此類別無法單獨進行單元測試,因為也必須載入實際的 Foo 類別才能取得 Foo::BAR 的值
class Baz implements BazInterface
{
private
$foo;

public function
__construct(FooInterface $foo)
{
$this->foo = $foo;
}

public function
getFooBar()
{
return
Foo::BAR;
}

}

$foo = new Foo();
$baz = new Baz($foo);
$bar = $baz->getFooBar();
?>

低耦合示例(消除了常數的使用)

<?php
介面 FooInterface
{
public function
bar();
}

類別
Foo 實作 FooInterface
{
public function
__construct()
{
}

final public function
bar()
{
return
1;
}
}

介面
BazInterface
{
public function
getFooBar();
}

// 這個類別可以單獨進行單元測試,因為透過模擬 FooInterface 並呼叫 final 的 bar 方法,不需要載入 Foo 類別。
類別 Baz 實作 BazInterface
{
private
$foo;

public function
__construct(FooInterface $foo)
{
$this->foo = $foo;
}

public function
getFooBar()
{
return
$this->foo->bar();
}

}

$foo = new Foo();
$baz = new Baz($foo);
$bar = $baz->getFooBar();
?>
cottton at i-stats dot net
10 年前
我認為這值得了解
<?php
class BaseClass
{
protected static
$var = 'i belong to BaseClass';

public static function
test()
{
echo
'<hr>'.
'i am `'.__METHOD__.'()` and this is my var: `'.self::$var.'`<br>';
}
public static function
changeVar($val)
{
self::$var = $val;
echo
'<hr>'.
'i am `'.__METHOD__.'()` and i just changed my $var to: `'.self::$var.'`<br>';
}
final public static function
dontCopyMe($val)
{
self::$var = $val;
echo
'<hr>'.
'i am `'.__METHOD__.'()` and i just changed my $var to: `'.self::$var.'`<br>';
}
}

class
ChildClass extends BaseClass
{
protected static
$var = 'i belong to ChildClass';

public static function
test()
{
echo
'<hr>'.
'i am `'.__METHOD__.'()` and this is my var: `'.self::$var.'`<br>'.
'and this is my parent var: `'.parent::$var.'`';
}
public static function
changeVar($val)
{
self::$var = $val;
echo
'<hr>'.
'i am `'.__METHOD__.'()` and i just changed my $var to: `'.self::$var.'`<br>'.
'but the parent $var is still: `'.parent::$var.'`';
}
public static function
dontCopyMe($val) // Fatal error: Cannot override final method BaseClass::dontCopyMe() in ...
{
self::$var = $val;
echo
'<hr>'.
'i am `'.__METHOD__.'()` and i just changed my $var to: `'.self::$var.'`<br>';
}
}

BaseClass::test(); // i am `BaseClass::test()` and this is my var: `i belong to BaseClass`
ChildClass::test(); // i am `ChildClass::test()` and this is my var: `i belong to ChildClass`
// and this is my parent var: `i belong to BaseClass`
ChildClass::changeVar('something new'); // i am `ChildClass::changeVar()` and i just changed my $var to: `something new`
// but the parent $var is still: `i belong to BaseClass`
BaseClass::changeVar('something different'); // i am `BaseClass::changeVar()` and i just changed my $var to: `something different`
BaseClass::dontCopyMe('a text'); // i am `BaseClass::dontCopyMe()` and i just changed my $var to: `a text`
ChildClass::dontCopyMe('a text'); // Fatal error: Cannot override final method BaseClass::dontCopyMe() in ...
?>
santoshjoshi2003 at yahoo dot co dot in
16 年前
final 關鍵字的用法就像在 Java 中一樣
在 Java 中,final 有三種用法
1) 防止類別繼承
2) 防止方法覆寫或在子類別中重新定義方法

3) 宣告常數
但第三點似乎在 PHP 中沒有
我猜,因為我是一個 Java 開發者,目前正在學習 PHP
匿名
14 年前
FINAL 的行為不像你想的那麼嚴格。一個小例子
<?php
類別 A {
final private function
method(){}
}

類別
B 繼承 A {
private function
method(){}
}
?>

通常你會預期會發生以下情況
- 錯誤:final 和 private 關鍵字不能一起使用
- 沒有錯誤,因為 private 的可見性表示方法/變數/等等只在同一個類別中可見

但 PHP 的情況有點奇怪:「無法覆寫 final 方法 A::method()」

所以可以拒絕子類別中的方法名稱!不知道這是不是一個好的行為,但或許對你的目的有用。
匿名
3 個月前
當需要特殊的類別結構時,將魔術方法設為 final 可能會有幫助。

<?php

abstract class A {
final public function
__construnct(){ echo "A"; }
}

class
B extends A {
public function
__construct(){ echo "B"; }
}

$b = new B(); // 輸出:PHP 致命錯誤:無法覆寫最終方法 a\A::__construct()

?>
Baldurien
14 年前
「給 Java 開發者的注意事項:在 PHP 中,『final』關鍵字不用於類別常數。我們使用『const』關鍵字。」

https://php.dev.org.tw/manual/en/language.oop5.constants.php

無論 PHP 中的常數(無論是否在類別層級定義)都只能是純量值(整數、字串等),而 Java 中的常數可以是純物件(例如:java.awat.Color.BLACK),這點大致上是正確的。 要擁有這種類型常數的唯一可能解法是:

<?php
class Bar {...}
class
Foo {
public static
$FOOBAR;

static function
__init() {
static
$init = false;
if (
$init) throw new Exception('常數已被初始化');
self::$FOOBAR = new Bar();
$init = true;
}
}
Foo::__init();
?>
話雖如此,除非 PHP 自動呼叫 __init() 方法,否則這可能沒有用。

然而,在某些情況下可以使用的替代方案是:

<?php
function __autoload($className) {
... 載入類別所在的檔案 ...
if (
interface_exists($className, false)) return;
if (
class_exists($className, false)) {
$rc = new ReflectionClass($className);
if (!
$rc->hasMethod('__init')) return;
$m = $rc->getMethod('__init');
if (!(
$m->isStatic() && $m->isPrivate())) {
throw new
Exception($className . ' __init() 方法必須是私有且靜態的!');
}
$m->invoke(null);
return;
}
throw new
Exception('找不到類別或介面 ' . $className);
}
?>

這只有在每個檔案定義一個類別時才有效,因為我們可以確定 __autoload() 會被呼叫來載入包含該類別的檔案。

例如:

test2.php
<?php
class B {
public static
$X;
private static function
__init() {
echo
'B', "\n";
self::$X = array(1, 2);
}
}
class
A {
public static
$Y;
private static function
__init() {
echo
'A', "\n";
self::$Y = array(3, 4);
}
}
?>
test.php
<?php
function __autoload($n) {
if (
$n == 'A' || $n == 'B') require 'test2.php';
... 執行我們的 __init() 技巧 ...
}
var_dump(B::$X); // 顯示 B,然後 array(2) (1, 2)
var_dump(A::$Y); // 顯示 NULL。
?>
To Top