首页 前端知识 PHP 和 jQuery 高级教程(二)

PHP 和 jQuery 高级教程(二)

2024-08-10 22:08:13 前端知识 前端哥 566 549 我要收藏

原文:Pro PHP and jQuery

协议:CC BY-NC-SA 4.0

三、面向对象编程

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1230-1_​3) contains supplementary material, which is available to authorized users.

在本章中,你将学习面向对象编程(OOP)背后的概念,面向对象编程是一种编码风格,在这种风格中,相关的动作被分组到类中,以帮助创建更紧凑、更有效的代码。你将在本书中构建的项目的后端在很大程度上是基于 OOP 的,所以在你将要完成的其余练习中会经常引用到本章中的概念。

理解面向对象编程

如上所述,面向对象编程是一种编码风格,它允许开发人员将相似的任务分组到类中。这有助于保持您的代码易于维护,并符合“不要重复自己”(DRY)的租户。

Note

关于干式编程的进一步阅读,见http://en.wikipedia.org/wiki/Don’t_repeat_yourself .

干编程的主要好处之一是,如果程序中的一条信息发生变化,通常只需要一个变化就可以更新代码。对于开发人员来说,最大的噩梦之一就是维护代码,在代码中一遍又一遍地声明数据,这意味着对程序的任何更改都变成了一个极其令人沮丧的游戏,比如 Waldo 在哪里?因为他们寻找重复的数据和功能。

OOP 让许多开发人员望而生畏,因为它引入了新的语法,乍看之下,似乎比简单的过程化或内联代码复杂得多。然而,仔细观察一下,OOP 实际上是一种非常简单的编程方法。

了解对象和类

在我们深入 OOP 的细节之前,有必要对对象和类的组件有一个基本的了解。本节将介绍类的构造块、它们不同的功能以及它们的一些用途。

认识到对象和类之间的差异

很快,OOP 中出现了混乱:经验丰富的开发人员开始谈论对象和类,它们似乎是可互换的术语。然而,事实并非如此,尽管一开始你很难理解其中的区别。

一个班级就像一栋房子的蓝图。它在纸上定义了房子的形状,明确定义和规划了房子不同部分之间的关系,即使房子并不存在。

那么,一个物体就像是根据蓝图建造的真正的房子。存储在对象中的数据就像组成房子的木材、电线和混凝土:如果没有根据蓝图进行组装,它只是一堆东西。然而,当所有这些都聚集在一起时,它就变成了一个有组织的、有用的房子。

类构成了数据和动作的结构,并使用这些信息来构建对象。可以同时从同一个类中构建多个对象,每个对象都相互独立。继续我们的建筑类比,这类似于从相同的蓝图建造整个小区的方式:150 个不同的房子,它们看起来都一样,但内部有不同的家庭和装饰。

构建类

创建类的语法非常简单:使用class关键字声明一个类,后跟类名和一组花括号({}):

<?php

declare(strict_types=1);

class MyClass

{

// Class properties and methods go here

}

?>

Note

可选声明strict_types=1是 PHP 7 的新增功能,它强制对所有函数调用和返回语句中出现的标量类型声明进行严格的类型检查。请参阅附录,了解更多关于这个特性和您将使用的其他 PHP 7 特性的信息。

创建类后,可以使用new关键字实例化一个新类并存储在一个变量中:

$obj = new MyClass;

要查看类的内容,使用var_dump():

var_dump($obj);

通过将所有前面的代码放在testing文件夹中一个名为test.php的新文件中来尝试这个过程:

<?php

declare(strict_types=1);

class MyClass

{

// Class properties and methods go here

}

$obj = new MyClass;

var_dump($obj);

?>

http://localhost/testing/test.php将页面加载到浏览器中,应该会显示以下内容:

object(MyClass)#1 (0) { }

最简单的形式是,您已经完成了第一个 OOP 脚本。

定义类属性

要向类中添加数据,需要使用属性或特定于类的变量。这些变量的工作方式与常规变量完全一样,只是它们被绑定到对象上,因此只能使用对象来访问。

要将属性添加到MyClass,请将以下粗体代码添加到脚本中:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

}

$obj = new MyClass;

var_dump($obj);

?>

关键字public决定了属性的可见性,这一点你将在本章稍后了解。接下来,使用标准变量语法命名属性,并赋值(尽管类属性不需要初始值)。

若要读取此属性并将其输出到浏览器,请引用要从中读取的对象和要读取的属性:

echo $obj->prop1;

因为一个类可以存在多个实例,所以如果没有引用单个对象,脚本就无法确定要读取哪个对象。箭头(->)的使用是一个 OOP 结构,它访问给定对象包含的属性和方法。

修改test.php中的脚本以读出属性,而不是转储整个类,方法是修改粗体的行:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

}

$obj = new MyClass;

echo $obj->prop1;

?>

重新加载浏览器现在会输出以下内容:

I’m a class property!

定义类方法

方法是特定于类的函数。对象能够执行的单个操作在类中被定义为方法。

例如,要创建设置和获取类属性$prop1的值的方法,请在代码中添加以下粗体行:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

$obj = new MyClass;

echo $obj->prop1;

?>

Note

OOP 允许对象使用$this引用自己。当在一个方法中工作时,使用$this,就像在类外使用对象名一样。

要使用这些方法,就像调用常规函数一样调用它们,但是首先要引用它们所属的对象。从MyClass中读取属性,更改其值,并通过修改以粗体显示的内容再次读取:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

$obj = new MyClass;

echo $obj->getProperty(); // Get the property value

$obj->setProperty("I’m a new property value!"); // Set a new one

echo $obj->getProperty(); // Read it out again to show the change

?>

重新加载您的浏览器,您将看到以下内容:

I’m a class property!

I’m a new property value!

当您使用同一个类的多个实例时,OOP 的强大就变得显而易见了。向混合中添加一个额外的实例MyClass,并开始设置和获取属性:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

// Create two objects

$obj = new MyClass;

$obj2 = new MyClass;

// Get the value of $prop1 from both objects

echo $obj->getProperty();

echo $obj2->getProperty();

// Set new values for both objects

$obj->setProperty("I’m a new property value!");

$obj2->setProperty("I belong to the second instance!");

// Output both objects' $prop1 value

echo $obj->getProperty();

echo $obj2->getProperty();

?>

当您在浏览器中加载结果时,结果如下所示:

I’m a class property!

I’m a class property!

I’m a new property value!

I belong to the second instance!

正如你所看到的,OOP 将对象作为独立的实体,这使得将不同的代码分成小的、相关的包变得容易。

为了使对象的使用更容易,PHP 还提供了许多神奇的方法,或者当对象中发生某些常见动作时调用的特殊方法。这允许开发人员相对容易地执行许多有用的任务。

使用构造函数和析构函数

当一个对象被实例化时,通常需要立即设置一些东西。为了处理这个问题,PHP 提供了神奇的方法__construct(),每当创建一个新对象时,都会自动调用这个方法。

为了说明构造函数的概念,向MyClass添加一个构造函数,每当创建一个新的类实例时,它都会输出一条消息:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

// Create a new object

$obj = new MyClass;

// Get the value of $prop1

echo $obj->getProperty();

// Output a message at the end of the file

echo "End of file.<br />";

?>

Note

__CLASS__是所谓的魔法常数,在这种情况下,它返回调用它的类的名称。有几个可用的魔术常数;你可以在 http://us3.php.net/manual/en/language.constants.predefined.php 的 PHP 手册中读到更多关于它们的内容。

在浏览器中重新加载该文件将产生以下结果:

The class "MyClass" was initiated!

I’m a class property!

End of file.

要在对象被销毁时调用函数,可以使用__destruct()魔法方法。这对于类清理很有用(例如,关闭数据库连接)。

通过在MyClass中定义魔法方法__destruct(),当物体被破坏时输出一条信息:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function __destruct()

{

echo 'The class "', __CLASS__, '" was destroyed.<br />';

}

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

// Create a new object

$obj = new MyClass;

// Get the value of $prop1

echo $obj->getProperty();

// Output a message at the end of the file

echo "End of file.<br />";

?>

定义了析构函数后,重新加载测试文件会产生以下输出:

The class "MyClass" was initiated!

I’m a class property!

End of file.

The class "MyClass" was destroyed.

当到达一个文件的末尾时,PHP 自动释放文件中使用的所有资源以保持内存可用。这触发了MyClass对象的析构函数。

要显式触发析构函数,可以使用函数unset()销毁对象:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function __destruct()

{

echo 'The class "', __CLASS__, '" was destroyed.<br />';

}

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

// Create a new object

$obj = new MyClass;

// Get the value of $prop1

echo $obj->getProperty();

// Destroy the object

unset($obj);

// Output a message at the end of the file

echo "End of file.<br />";

?>

现在,在浏览器中加载时,结果会更改如下:

The class "MyClass" was initiated!

I’m a class property!

The class "MyClass" was destroyed.

End of file.

转换为字符串

为了避免脚本试图将MyClass输出为字符串时出现错误,使用了一种称为__toString()的神奇方法。

如果没有__toString(),试图将对象输出为字符串会导致致命错误。尝试使用echo输出对象,而不使用魔法方法,如下所示:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function __destruct()

{

echo 'The class "', __CLASS__, '" was destroyed.<br />';

}

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

// Create a new object

$obj = new MyClass;

// Output the object as a string

echo $obj;

// Destroy the object

unset($obj);

// Output a message at the end of the file

echo "End of file.<br />";

?>

这将导致以下结果:

The class "MyClass" was initiated!

Catchable fatal error``: Object of class MyClass could not be converted to string

in``C:\wamp\www\book\testing\tst01.php``on line

为了避免这个错误,添加一个__toString()方法:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function __destruct()

{

echo 'The class "', __CLASS__, '" was destroyed.<br />';

}

public function __toString()

{

echo "Using the toString method: ";

return $this->getProperty();

}

public function setProperty($newval)

{

$this->prop1 = $newval;

}

public function getProperty()

{

return $this->prop1 . "<br />";

}

}

// Create a new object

$obj = new MyClass;

// Output the object as a string

echo $obj;

// Destroy the object

unset($obj);

// Output a message at the end of the file

echo "End of file.<br />";

?>

在这种情况下,试图将对象转换为字符串会导致调用getProperty()方法。在您的浏览器中加载测试脚本以查看结果:

The class "MyClass" was initiated!

Using the toString method: I’m a class property!

The class "MyClass" was destroyed.

End of file.

Tip

除了本节讨论的神奇方法之外,还有其他几种方法可用。关于魔术方法的完整列表,请参见 PHP 手册页的 http://us2.php.net/manual/en/language.oop5.magic.php

使用类继承

使用extends关键字,类可以继承另一个类的方法和属性。例如,要创建扩展MyClass并添加一个方法的第二个类,您可以将以下内容添加到测试文件中:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function __destruct()

{

echo 'The class "', __CLASS__, '" was destroyed.<br />';

}

public function __toString()

{

echo "Using the toString method: ";

return $this->getProperty();

}

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

class MyOtherClass extends MyClass

{

public function newMethod(): string

{

return "From a new method in " . __CLASS__ . ".<br />";

}

}

// Create a new object

$newobj = new MyOtherClass;

// Output the object as a string

echo $newobj->newMethod();

// Use a method from the parent class

echo $newobj->getProperty();

?>

在浏览器中重新加载测试文件时,会输出以下内容:

The class "MyClass" was initiated!

From a new method in MyOtherClass.

I’m a class property!

The class "MyClass" was destroyed.

覆盖继承的属性和方法

要更改新类中现有属性或方法的行为,只需通过在新类中再次声明来覆盖它:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function __destruct()

{

echo 'The class "', __CLASS__, '" was destroyed.<br />';

}

public function __toString()

{

echo "Using the toString method: ";

return $this->getProperty();

}

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

class MyOtherClass extends MyClass

{

public function __construct()

{

echo "A new constructor in " . __CLASS__ . ".<br />";

}

public function newMethod(): string

{

return "From a new method in " . __CLASS__ . ".<br />";

}

}

// Create a new object

$newobj = new MyOtherClass;

// Output the object as a string

echo $newobj->newMethod();

// Use a method from the parent class

echo $newobj->getProperty();

?>

这会将浏览器中的输出更改为

A new constructor in MyOtherClass.

From a new method in MyOtherClass.

I’m a class property!

The class "MyClass" was destroyed.

覆盖方法时保留原始方法功能

要向继承的方法添加新功能,同时保持原始方法不变,请使用带有范围解析操作符(::)的parent关键字:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function __destruct()

{

echo 'The class "', __CLASS__, '" was destroyed.<br />';

}

public function __toString()

{

echo "Using the toString method: ";

return $this->getProperty();

}

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

class MyOtherClass extends MyClass

{

public function __construct()

{

parent::__construct(); // Call the parent class’s constructor

echo "A new constructor in " . __CLASS__ . ".<br />";

}

public function newMethod(): string

{

return "From a new method in " . __CLASS__ . ".<br />";

}

}

// Create a new object

$newobj = new MyOtherClass;

// Output the object as a string

echo $newobj->newMethod();

// Use a method from the parent class

echo $newobj->getProperty();

?>

这将输出父构造函数和新类的构造函数的结果:

The class "MyClass" was initiated!

A new constructor in MyOtherClass.

From a new method in MyOtherClass.

I’m a class property!

The class "MyClass" was destroyed.

分配属性和方法的可见性

为了增加对对象的控制,方法和属性被赋予可见性。这控制了如何以及从哪里访问属性和方法。可见性关键字有三个:publicprotectedprivate。除了它的可见性,一个方法或属性可以被声明为static,这允许它们在没有类的实例化的情况下被访问。

公共属性和方法

到目前为止,您使用的所有方法和属性都是public。这意味着可以在任何地方访问它们,包括在类内部和外部。

受保护的属性和方法

当一个属性或方法被声明为protected时,它只能在类本身或在子类(扩展包含受保护方法的类的类)中被访问。

MyClass中将getProperty()方法声明为protected,并尝试从类外部直接访问它,如下所示:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function __destruct()

{

echo 'The class "', __CLASS__, '" was destroyed.<br />';

}

public function __toString()

{

echo "Using the toString method: ";

return $this->getProperty();

}

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

protected function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

class MyOtherClass extends MyClass

{

public function __construct()

{

parent::__construct();

echo "A new constructor in " . __CLASS__ . ".<br />";

}

public function newMethod(): string

{

return "From a new method in " . __CLASS__ . ".<br />";

}

}

// Create a new object

$newobj = new MyOtherClass;

// Attempt to call a protected method

echo $newobj->getProperty();

?>

尝试运行该脚本时,会出现以下错误:

The class "MyClass" was initiated!

A new constructor in MyOtherClass.

Fatal error``: Uncaught Error: Call to protected method MyClass::getProperty() from

context '' in C:\wamp\www\book\testing\test.php:54 Stack trace: #0 {main} thrown 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

in``C:\wamp\www\book\testing\test.php``on line

现在,在MyOtherClass中创建一个新方法来调用getProperty()方法:

<?php

declare(strict_types=1);

class MyClass

{

public $prop1 = "I’m a class property!";

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function __destruct()

{

echo 'The class "', __CLASS__, '" was destroyed.<br />';

}

public function __toString()

{

echo "Using the toString method: ";

return $this->getProperty();

}

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

protected function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

class MyOtherClass extends MyClass

{

public function __construct()

{

parent::__construct();

echo "A new constructor in " . __CLASS__ . ".<br />";

}

public function newMethod(): string

{

return "From a new method in " . __CLASS__ . ".<br />";

}

public function callProtected(): string

{

return $this->getProperty();

}

}

// Create a new object

$newobj = new MyOtherClass;

// Call the protected method from within a public method

echo $newobj->callProtected();

?>

这将产生预期的结果:

The class "MyClass" was initiated!

A new constructor in MyOtherClass.

I’m a class property!

The class "MyClass" was destroyed.

私有属性和方法

声明为private的属性或方法只能从定义它的类中访问。该类之外的任何代码都不能直接访问该属性或方法。

为了演示这一点,最简单的方法是回到第一个例子,简单地在MyClass中将$prop1声明为private,然后尝试运行修改后的代码,如下所示:

<?php

declare(strict_types=1);

class MyClass

{

private $prop1 = "I’m a class property!";

}

$obj = new MyClass;

echo $obj->prop1;

?>

重新加载浏览器,出现以下错误:

Fatal error``: Uncaught Error: Cannot access private property MyClass::$prop1

in C:\wamp\www\book\testing\test.php:13 Stack trace: #0 {main} thrown 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

in``C:\wamp\www\book\testing\test.php``on line

通过保持成员属性private来限制对它们的访问通常被认为是良好的 OOP 实践。管理类属性的通常方法是提供public方法来设置和获取它们的值,正如您在上面看到的那样。

这里的区别在于,通过保留类属性private,您只通过方法强制访问。这种类属性的封装使您的代码更健壮,更易于维护。通过向客户端代码提供方法,您可以更好地控制类属性的完整性,并且可以在不破坏客户端代码的情况下自由地重新设计类的内部结构。

下面是恢复了访问属性的公共方法的示例:

<?php

declare(strict_types=1);

class MyClass

{

private $prop1 = "I’m a class property!";

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

public function getProperty(): string

{

return $this->prop1 . "<br />";

}

}

$obj = new MyClass;

echo $obj->getProperty();

$obj->setProperty("Now I am different!");

echo $obj->getProperty();

?>

这将生成以下输出:

I’m a class property!

Now I am different!

这段代码的工作方式和以前非常相似,但是增加了完全封装类属性的好处。类方法也可以被声明为private,就像我们偶尔对“helper”方法所做的那样,它的访问被限制在同一个类中的其他方法。但是private可见性更常用于类属性,如本例所示。我们通常会遵循这种模式前进。

静态属性和方法

声明为static的方法或属性无需首先实例化该类即可访问;您只需提供类名、范围解析操作符以及属性或方法名。

使用静态属性的一个主要好处是,它们在脚本运行期间保持其存储的值。这意味着,如果您修改了一个静态属性,并在稍后的脚本中访问它,修改后的值仍将被存储。

为了演示这一点,回到完整的示例,向MyClass添加一个名为$count的私有静态属性、一个名为getCount()的公共访问器和一个名为plusOne()的静态方法。然后设置一个do...while循环,只要值小于10就输出$count的递增值,像这样:

<?php

declare(strict_types=1);

class MyClass

{

private $prop1 = "I’m a class property!";

private static $count = 0;

public function __construct()

{

echo 'The class "', __CLASS__, '" was initiated!<br />';

}

public function __destruct()

{

echo 'The class "', __CLASS__, '" was destroyed.<br />';

}

public function __toString()

{

echo "Using the toString method: ";

return $this->getProperty();

}

public function setProperty(string $newval)

{

$this->prop1 = $newval;

}

protected function getProperty(): string

{

return $this->prop1 . "<br />";

}

public static function getCount(): int

{

return self::$count;

}

public static function plusOne()

{

echo "The count is " . ++self::$count . ".<br />";

}

}

class MyOtherClass extends MyClass

{

public function __construct()

{

parent::__construct();

echo "A new constructor in " . __CLASS__ . ".<br />";

}

public function newMethod(): string

{

return "From a new method in " . __CLASS__ . ".<br />";

}

public function callProtected()

{

return $this->getProperty();

}

}

do

{

// Call plusOne without instantiating MyClass

MyClass::plusOne();

} while ( MyClass::getCount() < 10 );

?>

Note

当访问静态属性时,美元符号($)跟在范围解析运算符之后。

当您在浏览器中加载此脚本时,会输出以下内容:

The count is 1.

The count is 2.

The count is 3.

The count is 4.

The count is 5.

The count is 6.

The count is 7.

The count is 8.

The count is 9.

The count is 10.

用文档块注释

虽然不是 OOP 的正式部分,但文档块注释风格是一种被广泛接受的记录类的方法。除了为开发人员提供一个编写代码时使用的标准,它还被许多最流行的 SDK(软件开发工具包)所采用,如 Eclipse(在 http://eclipse.org 可用)和 NetBeans(在 http://netbeans.org 可用),并将用于生成代码提示。

使用以附加星号开头的块注释来定义 DocBlock:

/**

* This is a very basic DocBlock

*/

DocBlocks 的真正强大之处在于它能够使用标记,标记以一个 at 符号(@)开始,紧接着是标记名和标记值。这些允许开发人员定义文件的作者、类的许可、属性或方法信息以及其他有用的信息。

最常见的标签如下:

  • @author:当前元素的作者(可能是一个类、文件、方法或任何一段代码)使用这个标签列出。如果不止一个作者被认可,则在同一个文档块中可以使用多个作者标签。作者姓名的格式为John Doe <john.doe@email.com>
  • @copyright:这表示当前元素的版权年份和版权所有者的名字。格式为2010 Copyright Holder
  • @license:链接到当前元素的许可。许可证信息的格式为http://www.example.com/path/to/license.txt License Name
  • @var:保存变量或类属性的类型和描述。格式为type element description
  • @param:该标签显示函数或方法参数的类型和描述。格式为type $element_name element description
  • @return:该标签提供了函数或方法返回值的类型和描述。格式为type return element description

用 DocBlocks 注释的示例类可能如下所示:

<?php

declare(strict_types=1);

/**

* A simple class

*

* This is the long description for this class,

* which can span as many lines as needed. It is

* not required, whereas the short description is

* necessary.

*

* It can also span multiple paragraphs if the

* description merits that much verbiage.

*

* @author Jason Lengstorf <jason.lengstorf@ennuidesign.com>

* @copyright 2010 Ennui Design

* @licensehttp://www.php.net/license/3_01.txt

*/

class SimpleClass

{

/**

* A private variable

*

* @var string stores data for the class

*/

private $foo;

/**

* Sets $foo to a new value upon class instantiation

*

* @param string $val a value required for the class

* @return void

*/

public function __construct(string $val)

{

$this->foo = $val;

}

/**

* Multiplies two integers

*

* Accepts a pair of integers and returns the

* product of the two.

*

* @param int $bat a number to be multiplied

* @param int $baz a number to be multiplied

* @return int the product of the two parameters

*/

public function bar(int $bat, int $baz): int

{

return $bat * $baz;

}

}

?>

一旦浏览了前面的类,DocBlock 的好处就显而易见了:所有的东西都被清晰地定义了,这样下一个开发人员就可以拿起代码,而不必担心一段代码是做什么的,或者它应该包含什么。

Note

有关文档块的更多信息,请参见 http://en.wikipedia.org/wiki/PHPDoc

比较面向对象代码和过程代码

写代码没有正确和错误的方式。也就是说,本节概述了在软件开发中采用面向对象方法的有力论据,尤其是在大型应用中。

易于实施

虽然一开始可能会令人望而生畏,但 OOP 实际上提供了一种更简单的处理数据的方法。因为对象可以在内部存储数据,所以变量不需要在函数之间传递才能正常工作。

此外,因为同一个类的多个实例可以同时存在,所以处理大型数据集要容易得多。例如,假设您正在处理一个文件中两个人的信息。他们需要姓名、职业和年龄。

程序方法

下面是这个例子的程序方法:

<?php

declare(strict_types=1);

function changeJob(array $person, string $newjob): array

{

$person['job'] = $newjob; // Change the person’s job

return $person;

}

function happyBirthday(array $person): array

{

++$person['age']; // Add 1 to the person’s age

return $person;

}

$person1 = array(

'name' => 'Tom',

'job' => 'Button-Pusher',

'age' => 34

);

$person2 = array(

'name' => 'John',

'job' => 'Lever-Puller',

'age' => 41

);

// Output the starting values for the people

echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";

echo "<pre>Person 2: ", print_r($person2, TRUE), "</pre>";

// Tom got a promotion and had a birthday

$person1 = changeJob($person1, 'Box-Mover');

$person1 = happyBirthday($person1);

// John just had a birthday

$person2 = happyBirthday($person2);

// Output the new values for the people

echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";

echo "<pre>Person 2: ", print_r($person2, TRUE), "</pre>";

?>

执行时,该代码输出以下内容:

Person 1: Array

(

[name] => Tom

[job] => Button-Pusher

[age] => 34

)

Person 2: Array

(

[name] => John

[job] => Lever-Puller

[age] => 41

)

Person 1: Array

(

[name] => Tom

[job] => Box-Mover

[age] => 35

)

Person 2: Array

(

[name] => John

[job] => Lever-Puller

[age] => 42

)

虽然这段代码不一定不好,但是在编码的时候有很多需要记住的地方。受影响人员的属性数组必须从每个函数调用中传递和返回,这就为错误留下了余地。

为了简化这个例子,最好是留给开发人员尽可能少的事情。只有当前操作绝对必要的信息才需要传递给函数。

这就是 OOP 介入并帮助你清理的地方。

面向对象的方法

下面是这个例子的 OOP 方法:

<?php

declare(strict_types=1);

class Person

{

private $_name;

private $_job;

private $_age;

public function __construct(string $name, string $job, int $age)

{

$this->_name = $name;

$this->_job = $job;

$this->_age = $age;

}

public function changeJob(string $newjob)

{

$this->_job = $newjob;

}

public function happyBirthday()

{

++$this->_age;

}

}

// Create two new people

$person1 = new Person("Tom", "Button-Pusher", 34);

$person2 = new Person("John", "Lever Puller", 41);

// Output their starting point

echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";

echo "<pre>Person 2: ", print_r($person2, TRUE), "</pre>";

// Give Tom a promotion and a birthday

$person1->changeJob("Box-Mover");

$person1->happyBirthday();

// John just gets a year older

$person2->happyBirthday();

// Output the ending values

echo "<pre>Person 1: ", print_r($person1, TRUE), "</pre>";

echo "<pre>Person 2: ", print_r($person2, TRUE), "</pre>";

?>

这将在浏览器中输出以下内容:

Person 1: Person Object

(

[_name:Person:private] => Tom

[_job:Person:private] => Button-Pusher

[_age:Person:private] => 34

)

Person 2: Person Object

(

[_name:Person:private] => John

[_job:Person:private] => Lever Puller

[_age:Person:private] => 41

)

Person 1: Person Object

(

[_name:Person:private] => Tom

[_job:Person:private] => Box-Mover

[_age:Person:private] => 35

)

Person 2: Person Object

(

[_name:Person:private] => John

[_job:Person:private] => Lever Puller

[_age:Person:private] => 42

)

要使这种方法面向对象,还需要一点点的设置,但是在定义了类之后,创建和修改人是轻而易举的事情;一个人的信息不需要从方法中传递或返回,只有绝对必要的信息才会传递给每个方法。

在小范围内,这种差异可能看起来不太大,但是随着您的应用规模的增长,如果实现得当,OOP 将显著减少您的工作量。

Tip

并非所有东西都需要面向对象。在应用内部的一个地方处理一些小事情的快速函数不一定需要包装在一个类中。当在面向对象和过程方法之间做出决定时,使用你的最佳判断。

更好的组织

OOP 的另一个好处是它非常容易打包和编目。每个类通常可以保存在自己单独的文件中,如果使用统一的命名约定,访问这些类就非常简单。

假设您有一个包含 150 个类的应用,这些类是通过应用文件系统根目录下的控制器文件动态调用的。所有 150 个类都遵循命名约定class. classname .inc.php,并驻留在应用的inc文件夹中。

控制器可以实现 PHP 的__autoload()函数,在调用时只动态地引入它需要的类,而不是将所有 150 个类都包含在控制器文件中以防万一,或者想出一些聪明的方法将这些文件包含在您自己的代码中:

<?php

function __autoload($class_name)

{

include_once 'inc/class.' . $class_name . '.inc.php';

}

?>

将每个类放在一个单独的文件中也使得代码更容易移植,更容易在新的应用中重用,而不需要大量的复制和粘贴。

更容易维护

由于 OOP 在正确执行时更紧凑的特性,代码中的变化通常比冗长的、杂乱无章的过程化实现更容易发现和做出。

如果一个特定的信息数组获得了一个新的属性,一个程序性的软件可能需要(在最坏的情况下)将新的属性添加到使用该数组的每个函数中。

一个 OOP 应用可以很容易地通过添加新的属性,然后添加处理该属性的方法来更新。

本节中提到的许多好处都是 OOP 与干编程实践相结合的产物。创建易于维护且不会引起噩梦的过程化代码是绝对可能的,创建糟糕的面向对象代码也是同样可能的。这本书将试图展示良好的编码习惯与 OOP 的结合,以生成易于阅读和维护的干净代码。

摘要

至此,您应该对面向对象的编程风格感到满意了。事件日历后端的整个核心将基于 OOP,所以任何目前看起来不清楚的概念都将被更彻底地检查,因为本章的概念将被放入一个实际的、真实世界的例子中。

在下一章中,您将开始构建事件日历的后端。

四、创建活动日历

Electronic supplementary material The online version of this chapter (doi:10.​1007/​978-1-4842-1230-1_​4) contains supplementary material, which is available to authorized users.

既然您已经熟悉了面向对象编程的概念,那么您可以开始从事本书的核心项目:事件日历。一切都从这里开始,随着本书的进展,您将使用 PHP 和 jQuery 添加越来越多的功能。

规划日历

因为您完全是从零开始,所以您需要花一分钟来规划应用。这个应用将是数据库驱动的(使用 MySQL),因此规划将分两个阶段进行:首先是数据库结构,然后是将访问和修改数据库的应用的基本图。

定义数据库结构

为了使构建应用变得更加容易,首先应该计划如何存储数据。这塑造了应用中的一切。

对于基本事件日历,您需要存储的所有信息如下:

  • event_id:自动递增的整数,唯一标识每个事件
  • event_title:事件的标题
  • 事件的完整描述
  • event_start:事件的开始时间(格式YYYY-MM-DD HH:MM:SS)
  • event_end:事件的结束时间(格式YYYY-MM-DD HH:MM:SS)

创建类别映射

下一步是布置主类,它将处理应用将执行的与日历事件相关的所有操作。这个类将被称为Calendar;这些方法和属性将如下所示:

  • 构建构造函数。
  • 请确保数据库连接存在或创建一个。
  • 设置以下基本属性:
    • 数据库对象
    • 要使用的日期
    • 正在查看的月份
    • 要查看的年份
    • 一个月中的天数
    • 一个月开始的工作日
  • 生成 HTML 来构建事件表单。
    • 检查事件是否正在被编辑或创建。
    • 如果需要编辑,将事件信息加载到表单中。
  • 在数据库中保存新事件并整理输入。
  • 从数据库中删除事件并确认删除。
  • 加载事件信息。
    • 从数据库加载事件数据。
    • 将每个事件作为一个数组存储在该月的适当日期。
  • 输出带有日历信息的 HTML。使用 events 数组,遍历该月的每一天,并在适用的地方附加事件标题和时间。
  • 将事件信息显示为 HTML。接受事件 ID 并加载事件的描述和详细信息

规划应用的文件夹结构

这个应用在完成后会变得有些复杂,所以值得花几分钟来考虑如何组织文件。

为了安全起见,所有可能的东西都将放在 web 根目录或公共文件夹之外:这包括数据库凭证、应用的核心以及运行它的类。由于 web 根目录中没有任何内容,恶意用户将无法在不在服务器上的情况下查看您的文件夹结构,这是一个很好的安全做法。

首先,您有两个文件夹:public包含应用用户可以直接访问的所有文件,比如 CSS、索引文件和 JavaScript 文件,而sys包含非公共文件,比如数据库凭证、应用的类和核心 PHP 文件。

公共文件

public文件夹将作为网络根目录。当用户访问您的应用的 URL 时,这是服务器将首先查找的文件夹。在根级别,它包含用户用来查看和操作存储在数据库中的数据的文件:

  • index.php:这是主文件,以日历格式显示月份,事件标题显示在事件发生当天的方框中。
  • view.php:如果用户点击一个事件标题,他们会被带到这个页面,在这里会显示事件的详细数据。
  • admin.php:创建或修改新事件,使用本页显示的表格。
  • confirmdelete.php:要删除一个事件,用户必须首先通过在此页面提交确认表单来确认该选择。

public文件夹还将有一个名为assets的子文件夹,其中将包含该站点的附加文件。这些文件将根据它们的用途进行分组,在本节中分为四类:通用文件、CSS 文件、JavaScript 文件和表单处理文件。

在资产中创建四个文件夹,分别名为commoncssincjscommon文件夹将存储将在所有可公开访问的页面上使用的文件(即应用的页眉和页脚);css文件夹将存储站点样式表;inc文件夹将存储文件以处理表单提交的输入;而js文件夹将存储站点 JavaScript 文件。

非公共应用文件

sys文件夹将被分成三个子文件夹:class,它将存储应用的所有类文件(如Calendar类);config,存储数据库凭证等应用配置信息;和core,它保存初始化应用的文件。

当一切都组织好了,所有文件都创建好了,文件结构就组织好了,以后就容易伸缩了(见图 4-1 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-1。

The folder structure and files as they appear in Eclipse 4.5 (Mars) on Windows Public and Nonpublic Folders—Why Bother?

你现在可能会问自己,“为什么要花额外的精力来创建公共和非公共文件夹呢?有什么好处?”

要回答这个问题,您需要了解一点 web 服务器的工作原理。服务器本质上是存储文件并使用网络标识符(IP 地址或映射到 IP 地址的 URL)向网络(如万维网)提供选定文件的计算机。一台服务器上可以托管数百个网站或其他应用,每个网站或应用都有自己的文件夹。

服务器允许外部用户访问这些公共文件夹,这意味着可以从服务器所连接的网络访问文件夹中的所有文件。对于包含敏感信息的文件,这并不总是可取的。

幸运的是,公用文件夹中的文件仍然可以访问公用文件夹之外的文件,尽管网络上的用户不能。这允许您对外界隐藏您的敏感数据,但保持您的应用可以访问它。

还有其他方法来隐藏这些信息,但简单地保持敏感数据不公开是最直接、最可靠的方法。

修改开发环境

因为您在这个应用中使用了公共和非公共文件夹,所以有必要对您的开发环境进行快速修改:您需要将服务器指向您的公共文件夹,而不是包含两者的文件夹。在本节中,您将学习如何将您的服务器指向public文件夹。

Note

您可以跳过这一部分,将sys文件夹保留在 public 文件夹中,而不会丢失应用中的任何功能(请记住,文件路径将与本书练习中使用的路径不同)。但是,您将使应用面临潜在的安全风险。强烈建议您花一分钟时间来遵循这些步骤。

地方发展

要更改本地安装中的文档根目录(public文件夹),需要修改服务器的配置文件。本书假设 Apache 被用作 XAMPP 堆栈中的服务器,所以您需要定位httpd.conf文件(在 Mac 上位于/xamppfiles/etc/httpd.conf,在 Linux 上位于/opt/lampp/etc/httpd.conf,在 Windows 上位于C:\wamp\bin\apache\apache2.4.x\conf\httpd.conf)。

httpd.conf中,搜索DocumentRoot指令。这是你设置你的public文件夹路径的地方。该文件应该如下所示:

#

# DocumentRoot: The directory out of which you will serve your

# documents. By default, all requests are taken from this directory, but

# symbolic links and aliases may be used to point to other locations.

#

DocumentRoot "c:\wamp\www\"

此外,在您的httpd.conf文件中搜索引用文档根目录的一行来设置权限。它看起来会像这样:

<Directory "c:/wamp/www/">

找到并修改了上面的路径后,使用 XAMPP 控制面板重新启动 Apache。现在,访问的默认文件夹是应用的public文件夹。为了测试这一点,创建文件index.php并添加以下代码片段:

<?php echo "I’m the new document root!"; ?>

在浏览器中导航到开发环境的文档根目录(默认为localhost)以确保重新配置有效(参见图 4-2 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-2。

The public folder’s index file is displayed after reconfiguring Apache

远程开发

因为远程开发通常发生在托管公司的服务器上,将您的域指向应用的public文件夹的步骤会因托管提供商而异,因此不会在本书中讨论。

但是,在许多情况下,主机会允许您将域指向您的主机帐户中的文件夹。如果是这种情况,只需将域指向public文件夹,一切都应该正常工作。

有些主机不允许文档根目录以外的访问。如果你的主机提供商就是这种情况,只需将sys文件夹放在public文件夹中,并相应地改变文件路径。

制作日历

文件夹结构准备好了,开发环境也设置好了,是时候真正开始开发了。我们将逐步介绍三个事件视图(主视图、单个事件视图和管理视图),从主日历视图开始。

创建数据库

与应用规划过程一样,开发应用的第一步是创建数据库。在您的本地开发环境中,打开 phpMyAdmin ( http://localhost/phpmyadmin在 XAMPP),并打开 SQL 选项卡(如果您没有使用 phpMyAdmin,也可以在 PHP 脚本中执行这些命令)。使用以下 SQL 创建数据库、存储事件数据的表events和一些虚拟条目:

CREATE DATABASE IF NOT EXISTS php-jquery_example``

DEFAULT CHARACTER SET utf8

COLLATE utf8_unicode_ci;

CREATE TABLE IF NOT EXISTS php-jquery_example.events (

``event_id INT(11) NOT NULL AUTO_INCREMENT,

``event_title VARCHAR(80) DEFAULT NULL,

``event_desc TEXT,

``event_start TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',

``event_end TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',

PRIMARY KEY (event_id),

INDEX (event_start)

) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_unicode_ci;

INSERT INTO php-jquery_example.events``

(event_title, event_desc, event_start, event_end) VALUES

('New Year’s Day', 'Happy New Year!',

'2016-01-01 00:00:00', '2016-01-01 23:59:59'),

('Last Day of January', 'Last day of the month! Yay!',

'2016-01-31 00:00:00', '2016-01-31 23:59:59');

Note

前面所有的命令都是 MySQL 特有的。由于这本书主要关注 jQuery 和 PHP,所以我们不会在这里详细介绍 MySQL。有关 MySQL 的更多信息,请查看 Jason Gilmore 的《PHP 和 MySQL 入门》。

在您执行了前面的命令之后,一个名为php-jquery_example的新数据库将出现在左边的列中。点击数据库名称以显示表格,然后点击events表格以查看您创建的条目(参见图 4-3 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-3。

The database, table, and entries after they’re created

使用类连接到数据库

因为您将在这个应用中创建多个需要数据库访问的类,所以创建一个打开并存储该数据库对象的对象是有意义的。这个对象将被称为DB_Connect,它将驻留在名为class.db_connect.inc.php ( /sys/class/class.db_connect.inc.php)的类文件夹中。

这个类将有一个属性和一个方法,两者都受到保护。该属性将被称为$db,并将存储一个数据库对象。该方法将是一个构造函数;这将接受一个可选的数据库对象存储在$db中,或者如果没有传递数据库对象,它将创建一个新的 PDO 对象。

将以下代码插入class.db_connect.inc.php:

<?php

declare(strict_types=1);

/**

* Database actions (DB access, validation, etc.)

*

* PHP version 7

*

* LICENSE: This source file is subject to the MIT License, available

* athttp://www.opensource.org/licenses/mit-license.html

*

* @author     Jason Lengstorf <jason.lengstorf@ennuidesign.com>

* @copyright  2009 Ennui Design

* @licensehttp://www.opensource.org/licenses/mit-license.html

*/

class DB_Connect {

/**

* Stores a database object

*

* @var object A database object

*/

protected $db;

/**

* Checks for a DB object or creates one if one isn’t found

*

* @param object $db A database object

*/

protected function __construct($db=NULL)

{

if ( is_object($db) )

{

$this->db = $db;

}

else

{

// Constants are defined in /sys/config/db-cred.inc.php

$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME;

try

{

$this->db = new PDO($dsn, DB_USER, DB_PASS);

}

catch ( Exception $e )

{

// If the DB connection fails, output the error

die ( $e->getMessage() );

}

}

}

}

?>

Note

前面的函数使用了尚未定义的常量。在下一节中,您将创建文件来定义这些常量。

创建类包装

为了构建应用本身,首先在非公共的sys文件夹(/sys/class/class.calendar.inc.php)中的class文件夹中创建文件class.calendar.inc.php。这个类将扩展DB_Connect类,以便访问数据库对象。在您选择的编辑器中打开该文件,并使用以下代码创建Calendar类:

<?php

declare(strict_types=1);

/**

* Builds and manipulates an events calendar

*

* PHP version 7

*

* LICENSE: This source file is subject to the MIT License, available

* athttp://www.opensource.org/licenses/mit-license.html

*

* @author     Jason Lengstorf <jason.lengstorf@ennuidesign.com>

* @copyright  2009 Ennui Design

* @licensehttp://www.opensource.org/licenses/mit-license.html

*/

class Calendar extends DB_Connect

{

// Methods and properties go here

}

?>

创建了类之后,就可以开始向该类添加属性和方法了。

添加类属性

Calendar类不需要任何公共属性,并且您不会在本书包含的示例中扩展它,所以所有的类属性都是私有的。

按照规划一节中的定义,为Calendar类创建属性:

<?php

declare(strict_types=1);

class Calendar extends DB_Connect

{

/**

* The date from which the calendar should be built

*

* Stored in YYYY-MM-DD HH:MM:SS format

*

* @var string the date to use for the calendar

*/

private $_useDate;

/**

* The month for which the calendar is being built

*

* @var int the month being used

*/

private $_m;

/**

* The year from which the month’s start day is selected

*

* @var int the year being used

*/

private $_y;

/**

* The number of days in the month being used

*

* @var int the number of days in the month

*/

private $_daysInMonth;

/**

* The index of the day of the week the month starts on (0-6)

*

* @var int the day of the week the month starts on

*/

private $_startDay;

// Methods go here

}

?>

Note

为了简洁起见,重复的代码片段中将不包含文档块。

根据原规划,班级性质如下:

  • $_useDate:以YYYY-MM-DD HH:MM:SS格式创建日历时使用的日期
  • $_m:建立日历时使用的月份
  • $_y:建立日历时使用的年份
  • 当前月份有多少天
  • $_startDay:从 0 到 6 的索引,代表一个月从星期几开始

构建构造函数

接下来,您可以构建类构造函数。首先声明它:

<?php

declare(strict_types=1);

class Calendar extends DB_Connect

{

private $_useDate;

private $_m;

private $_y;

private $_daysInMonth;

private $_startDay;

/**

* Creates a database object and stores relevant data

*

* Upon instantiation, this class accepts a database object

* that, if not null, is stored in the object’s private $_db

* property. If null, a new PDO object is created and stored

* instead.

*

* Additional info is gathered and stored in this method,

* including the month from which the calendar is to be built,

* how many days are in said month, what day the month starts

* on, and what day it is currently.

*

* @param object $dbo a database object

* @param string $useDate the date to use to build the calendar

* @return void

*/

public function __construct($dbo=NULL, $useDate=NULL)

{

}

}

?>

构造函数将接受两个可选参数:第一个是数据库对象,第二个是构建日历显示的日期。

检查数据库连接

为了正常运行,该类需要一个数据库连接。构造函数将从DB_Connect调用父构造函数来检查现有的数据库对象,并在可用时使用它,或者如果没有提供对象,它将创建一个新对象。

使用粗体显示的代码设置调用以进行此检查:

<?php

declare(strict_types=1);

class Calendar extends DB_Connect

{

private $_useDate;

private $_m;

private $_y;

private $_daysInMonth;

private $_startDay;

public function __construct($dbo=NULL, $useDate=NULL)

{

/*

* Call the parent constructor to check for

* a database object

*/

parent::__construct($dbo);

}

}

?>

Note

Calendar类构造函数接受一个可选的$dbo参数,该参数被依次传递给DB_Connect构造函数。这允许您创建一个数据库对象,并轻松地传递它以便在类中使用。

创建文件来存储数据库凭据

为了将数据库凭证与应用的其余部分分开,以便于维护,您需要使用一个配置文件。在config文件夹(/sys/config/db-cred.inc.php)中创建一个名为db-cred.inc.php的新文件。在内部,创建一个名为$C(用于常量)的数组,并将每条数据存储为一个新的键值对:

<?php

declare(strict_types=1);

/*

* Create an empty array to store constants

*/

$C = array();

/*

* The database host URL

*/

$C['DB_HOST'] = 'localhost';

/*

* The database username

*/

$C['DB_USER'] = 'root';

/*

* The database password

*/

$C['DB_PASS'] = '';

/*

* The name of the database to work with

*/

$C['DB_NAME'] = 'php-jquery_example';

?>

Note

$C初始化为一个空数组是防止任何被污染的数据被存储在$C中并被定义为常量的一种保护措施。这是一个好习惯,尤其是在处理敏感数据的时候。

保存此文件。如果您没有使用 XAMPP 或者修改了默认的数据库凭证,那么您需要在代码中替换您自己的主机、用户名、密码和数据库名称。

创建初始化文件

此时,您的数据库凭证仍然没有存储为常量。您将使用一个初始化文件来处理这个问题。

初始化文件为应用收集数据、加载文件和组织信息。在这个例子中,它将加载并定义所有必要的常量,创建一个数据库对象,并为类设置一个自动加载函数。其他功能将在以后必要时添加。

创建一个名为init.inc.php的文件,并将它放在core文件夹(/sys/core/init.inc.php)中。在内部,添加以下内容:

<?php

declare(strict_types=1);

/*

* Include the necessary configuration info

*/

include_once '../sys/config/db-cred.inc.php';

/*

* Define constants for configuration info

*/

foreach ( $C as $name => $val )

{

define($name, $val);

}

/*

* Create a PDO object

*/

$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME;

$dbo = new PDO($dsn, DB_USER, DB_PASS);

/*

* Define the auto-load function for classes

*/

function __autoload($class)

{

$filename = "../sys/class/class." . $class . ".inc.php";

if ( file_exists($filename) )

{

include_once $filename;

}

}

?>

当一个脚本试图实例化一个还没有被加载的类时,一个自动加载函数被调用。这是按需将类轻松加载到脚本中的一种便捷方式。有关自动加载的更多信息,请访问 http://php.net/autoload

创建一个索引文件来把所有的内容放在一起

要查看所有活动,请修改public文件夹中的index.php。在内部,只需包含初始化文件并实例化Calendar类。接下来,检查类是否正确加载,如果正确,则输出对象的结构:

<?php

declare(strict_types=1);

/*

* Include necessary files

*/

include_once '../sys/core/init.inc.php';

/*

* Load the calendar for January

*/

$cal = new Calendar($dbo, "2016-01-01 12:00:00");

if ( is_object ($cal) )

{

echo "<pre>", var_dump($cal), "</pre>";

}

?>

导航到http://localhost/后,会输出以下消息:

object(Calendar)#2 (6) {

["_useDate":"Calendar":private]=>

NULL

["_m":"Calendar":private]=>

NULL

["_y":"Calendar":private]=>

NULL

["_daysInMonth":"Calendar":private]=>

NULL

["_startDay":"Calendar":private]=>

NULL

["db":protected]=>

object(PDO)#1 (0) {

}

}

设置基本属性

有了所有的基础设施,您就可以继续完成Calendar类的构造函数了。

检查完数据库对象后,构造函数需要存储几条关于构建日历的月份的数据。

首先,它检查是否向构造函数传递了日期;如果是,则存储在$_useDate属性中;否则,将使用当前日期。

接下来,日期被转换成 UNIX 时间戳(从 Unix epoch 开始的秒数;在 http://en.wikipedia.org/wiki/Unix_time 阅读更多相关内容)之前,月和年被分别提取并存储在$_m$_y中。

最后,$_m$_y用于确定一个月中有多少天被使用,以及该月从星期几开始。

下面的粗体代码将此功能添加到构造函数中:

<?php

declare(strict_types=1);

class Calendar extends DB_Connect

{

private $_useDate;

private $_m;

private $_y;

private $_daysInMonth;

private $_startDay;

public function __construct($dbo=NULL, $useDate=NULL)

{

/*

* Call the parent constructor to check for

* a database object

*/

parent::__construct($dbo);

/*

* Gather and store data relevant to the month

*/

if ( isset($useDate) )

{

$this->_useDate = $useDate;

}

else

{

$this->_useDate = date('Y-m-d H:i:s');

}

/*

* Convert to a timestamp, then determine the month

* and year to use when building the calendar

*/

$ts = strtotime($this->_useDate);

$this->_m = (int)date('m', $ts);

$this->_y = (int)date('Y', $ts);

/*

* Determine how many days are in the month

*/

$this->_daysInMonth = cal_days_in_month(

CAL_GREGORIAN,

$this->_m,

$this->_y

);

/*

* Determine what weekday the month starts on

*/

$ts = mktime(0, 0, 0, $this->_m, 1, $this->_y);

$this->_startDay = (int)date('w', $ts);

}

}

?>

现在,当您重新加载http://localhost/时,所有先前为NULL的属性都将具有值:

object(Calendar)#2 (6) {

["_useDate":"Calendar":private]=>

string(19) "2016-01-01 12:00:00"

["_m":"Calendar":private]=>

int(1)

["_y":"Calendar":private]=>

int(2016)

["_daysInMonth":"Calendar":private]=>

int(31)

["_startDay":"Calendar":private]=>

int(5)

["db":protected]=>

object(PDO)#1 (0) {

}

}

加载事件数据

要加载关于事件的数据,您需要创建一个新的方法来访问数据库并检索它们。因为可以通过两种方式访问事件数据(第二种方式将在本章后面讨论),所以加载数据的动作将保持通用,以便于重用。

这个方法是私有的,命名为_loadEventData()。它接受一个可选参数,即事件的 ID,并按照以下步骤加载事件:

  • 创建一个基本的SELECT查询,从 events 表中加载可用字段。
  • 检查是否传递了一个 ID,如果是,向查询中添加一个WHERE子句以只返回一个事件。
  • 否则,请执行以下两项操作:
    • 找出该月第一天的午夜和该月最后一天的晚上 11:59:59。
    • 添加一个WHERE...BETWEEN子句,只加载当前月份内的日期。
  • 执行查询。
  • 返回结果的关联数组。

综合起来,这个方法看起来是这样的:

<?php

declare(strict_types=1);

class Calendar extends DB_Connect

{

private $_useDate;

private $_m;

private $_y;

private $_daysInMonth;

private $_startDay;

public function __construct($dbo=NULL, $useDate=NULL) {...}

/**

* Loads event(s) info into an array

*

* @param int $id an optional event ID to filter results

* @return array an array of events from the database

*/

private function _loadEventData($id=NULL)

{

$sql = "SELECT

``event_id, event_title, event_desc,

event_start`, `event_end

FROM events";

/*

* If an event ID is supplied, add a WHERE clause

* so only that event is returned

*/

if ( !empty($id) )

{

$sql .= "WHERE event_id=:id LIMIT 1";

}

/*

* Otherwise, load all events for the month in use

*/

else

{

/*

* Find the first and last days of the month

*/

$start_ts = mktime(0, 0, 0, $this->_m, 1, $this->_y);

$end_ts = mktime(23, 59, 59, $this->_m+1, 0, $this->_y);

$start_date = date('Y-m-d H:i:s', $start_ts);

$end_date = date('Y-m-d H:i:s', $end_ts);

/*

* Filter events to only those happening in the

* currently selected month

*/

$sql .= "WHERE event_start``

BETWEEN '$start_date'

AND '$end_date'

ORDER BY event_start";

}

try

{

$stmt = $this->db->prepare($sql);

/*

* Bind the parameter if an ID was passed

*/

if ( !empty($id) )

{

$stmt->bindParam(":id", $id, PDO::PARAM_INT);

}

$stmt->execute();

$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

$stmt->closeCursor();

return $results;

}

catch ( Exception $e )

{

die ( $e->getMessage() );

}

}

}

?>

Note

为了简洁起见,不引用的方法是折叠的。

该方法返回一个数组,当使用您先前输入到数据库中的测试条目时,该数组如下所示:

Array

(

[0] => Array

(

[event_id] => 1

[event_title] => New Year’s Day

[event_desc] => Happy New Year!

[event_start] => 2016-01-01 00:00:00

[event_end] => 2016-01-01 23:59:59

)

[1] => Array

(

[event_id] => 2

[event_title] => Last Day of January

[event_desc] => Last day of the month! Yay!

[event_start] => 2016-01-31 00:00:00

[event_end] => 2016-01-31 23:59:59

)

)

创建在日历中使用的事件对象数组

_loadEventData()的原始输出不能立即在日历中使用。因为事件需要在正确的日期显示,所以从_loadEventData()检索的事件需要按照事件发生的日期进行分组。为了便于参考,事件字段也将被简化。

最终目标是一个事件数组,该数组使用一个月中的某一天作为索引,将每个事件作为一个对象。当新方法完成时,数据库中的两个测试条目最终应该像这样存储:

Array

(

[1] => Array

(

[0] => Event Object

(

[id] => 1

[title] => New Year’s Day

[description] => Happy New Year!

[start] => 2016-01-01 00:00:00

[end] => 2016-01-01 23:59:59

)

)

[31] => Array

(

[0] => Event Object

(

[id] => 2

[title] => Last Day of January

[description] => Last day of the month! Yay!

[start] => 2016-01-31 00:00:00

[end] => 2016-01-31 23:59:59

)

)

)

创建事件类

为此,您必须首先在类文件夹(/sys/class/class.event.inc.php)中创建一个名为Event的新类。它将有五个公共属性($id$title$description$start$end)和一个构造函数,该构造函数将使用数据库查询返回的关联数组来设置这些属性。创建文件,并在其中插入以下代码:

<?php

declare(strict_types=1);

/**

* Stores event information

*

* PHP version 7

*

* LICENSE: This source file is subject to the MIT License, available

* athttp://www.opensource.org/licenses/mit-license.html

*

* @author     Jason Lengstorf <jason.lengstorf@ennuidesign.com>

* @copyright  2010 Ennui Design

* @licensehttp://www.opensource.org/licenses/mit-license.html

*/

class Event

{

/**

* The event ID

*

* @var int

*/

public $id;

/**

* The event title

*

* @var string

*/

public $title;

/**

* The event description

*

* @var string

*/

public $description;

/**

* The event start time

*

* @var string

*/

public $start;

/**

* The event end time

*

* @var string

*/

public $end;

/**

* Accepts an array of event data and stores it

*

* @param array $event Associative array of event data

* @return void

*/

public function __construct($event)

{

if ( is_array($event) )

{

$this->id = $event['event_id'];

$this->title = $event['event_title'];

$this->description = $event['event_desc'];

$this->start = $event['event_start'];

$this->end = $event['event_end'];

}

else

{

throw new Exception("No event data was supplied.");

}

}

}

?>

创建在数组中存储事件对象的方法

既然每个事件都可以存储为一个对象,那么您可以创建一个方法,该方法将循环遍历可用的事件,并将它们存储在一个与事件发生日期相对应的数组中。首先,使用_loadEventData()从数据库加载事件数据。接下来,从每个事件的开始日期中提取一个月中的某一天,并在该天的索引处向数组中添加一个新值。在Calendar类中,创建一个名为_createEventObj()的新方法,并将其设置为私有。从数据库加载事件,并使用以下粗体代码创建新数组:

<?php

declare(strict_types=1);

class Calendar extends DB_Connect

{

private $_useDate;

private $_m;

private $_y;

private $_daysInMonth;

private $_startDay;

public function __construct($dbo=NULL, $useDate=NULL) {...}

private function _loadEventData($id=NULL) {...}

/**

* Loads all events for the month into an array

*

* @return array events info

*/

private function _createEventObj()

{

/*

* Load the events array

*/

$arr = $this->_loadEventData();

/*

* Create a new array, then organize the events

* by the day of the month on which they occur

*/

$events = array();

foreach ( $arr as $event )

{

$day = date('j', strtotime($event['event_start']));

try

{

$events[$day][] = new Event($event);

}

catch ( Exception $e )

{

die ( $e->getMessage() );

}

}

return $events;

}

}

?>

现在可以加载和组织事件,这样输出实际日历的方法 HTML 可以很容易地将日期放在适当的位置。

输出 HTML 以显示日历和事件

至此,您已经建立了数据库,存储了测试事件,并准备好了将事件数据加载和组织到一个易于使用的数组中的方法。您已经准备好将这些碎片放在一起并创建一个日历了!

日历将由一个名为buildCalendar()的公共方法构建。这将生成具有以下属性的日历:

  • 显示月份和年份的标题
  • 工作日缩写,使日历看起来像日历
  • 包含给定日期存在的事件的编号框

首先,在Calendar类中声明buildCalendar()方法,并在H2元素中创建标题。此外,创建一个工作日缩写数组,并遍历它们以生成一个无序列表。为此,添加以下粗体代码:

<?php

declare(strict_types=1);

class Calendar extends DB_Connect

{

private $_useDate;

private $_m;

private $_y;

private $_daysInMonth;

private $_startDay;

public function __construct($dbo=NULL, $useDate=NULL) {...}

private function _loadEventData($id=NULL) {...}

private function _createEventObj() {...}

/**

* Returns HTML markup to display the calendar and events

*

* Using the information stored in class properties, the

* events for the given month are loaded, the calendar is

* generated, and the whole thing is returned as valid markup.

*

* @return string the calendar HTML markup

*/

public function buildCalendar()

{

/*

* Determine the calendar month and create an array of

* weekday abbreviations to label the calendar columns

*/

$cal_month = date('F Y', strtotime($this->_useDate));

define('WEEKDAYS', array('Sun', 'Mon', 'Tue',

'Wed', 'Thu', 'Fri', 'Sat'));

/*

* Add a header to the calendar markup

*/

$html = "\n\t<h2>$cal_month</h2>";

for ( $d=0, $labels=NULL; $d<7; ++$d )

{

$labels .= "\n\t\t<li>" . WEEKDAYS[$d] . "</li>";

}

$html .= "\n\t<ul class=\"weekdays\">"

. $labels . "\n\t</ul>";

/*

* Return the markup for output

*/

return $html;

}

}

?>

修改索引文件

要查看buildCalendar()方法的输出,需要修改public文件夹中的index.php来调用该方法。用粗体显示的代码更新文件:

<?php

declare(strict_types=1);

/*

* Include necessary files

*/

include_once '../sys/core/init.inc.php';

/*

* Load the calendar for January

*/

$cal = new Calendar($dbo, "2016-01-01 12:00:00");

/*

* Display the calendar HTML

*/

echo $cal->buildCalendar();

?>

在浏览器中调出该文件,查看目前的结果(图 4-4 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-4。

The heading and weekday abbreviations

制作日历

下一步是构建实际的日历日。要解决这个问题,需要完成几个步骤。

Create a new unordered list.   Set up a loop (with an iteration counter, a calendar date counter, today’s date, and the month and year stored as variables) that runs as long as the calendar date counter is less than the number of days in the month.   Add a fill class to the days of the week that occur before the first.   Add a today class if the current date is contained within the same month and year and matches the date being generated.   Create an opening and closing list item tag for each day.   Check if the current calendar box falls within the current month, and add the date if so.   Check if the current calendar box is a Saturday, and close the list and open a new one if so.   Assemble the pieces of the list item and append them to the markup.   After the loop, run another loop to add filler days until the calendar week is completed.   Close the final unordered list and return the markup.

首先,通过向buildCalendar()方法添加以下粗体代码来完成步骤 1 和 2:

public function buildCalendar()

{

/*

* Determine the calendar month and create an array of

* weekday abbreviations to label the calendar columns

*/

$cal_month = date('F Y', strtotime($this->_useDate));

define('WEEKDAYS', array('Sun', 'Mon', 'Tue',

'Wed', 'Thu', 'Fri', 'Sat'));

/*

* Add a header to the calendar markup

*/

$html = "\n\t<h2>$cal_month</h2>";

for ( $d=0, $labels=NULL; $d<7; ++$d )

{

$labels .= "\n\t\t<li>" . WEEKDAYS[$d] . "</li>";

}

$html .= "\n\t<ul class=\"weekdays\">"

. $labels . "\n\t</ul>";

/*

* Create the calendar markup

*/

$html .= "\n\t<ul>"; // Start a new unordered list

for ( $i=1, $c=1, $t=date('j'), $m=date('m'), $y=date('Y');

$c<=$this->_daysInMonth; ++$i )

{

// More steps go here

}

/*

* Return the markup for output

*/

return $html;

}

接下来,添加下面的粗体代码来完成步骤 3–5:

public function buildCalendar()

{

/*

* Determine the calendar month and create an array of

* weekday abbreviations to label the calendar columns

*/

$cal_month = date('F Y', strtotime($this->_useDate));

define('WEEKDAYS', array('Sun', 'Mon', 'Tue',

'Wed', 'Thu', 'Fri', 'Sat'));

/*

* Add a header to the calendar markup

*/

$html = "\n\t<h2>$cal_month</h2>";

for ( $d=0, $labels=NULL; $d<7; ++$d )

{

$labels .= "\n\t\t<li>" . WEEKDAYS[$d] . "</li>";

}

$html .= "\n\t<ul class=\"weekdays\">"

. $labels . "\n\t</ul>";

/*

* Create the calendar markup

*/

$html .= "\n\t<ul>"; // Start a new unordered list

for ( $i=1, $c=1, $t=date('j'), $m=date('m'), $y=date('Y');

$c<=$this->_daysInMonth; ++$i )

{

/*

* Apply a "fill" class to the boxes occurring before

* the first of the month

*/

$class = $i<=$this->_startDay ? "fill" : NULL;

/*

* Add a "today" class if the current date matches

* the current date

*/

if ( $c==$t``&&``$m==$this->_m``&&

{

$class = "today";

}

/*

* Build the opening and closing list item tags

*/

$ls = sprintf("\n\t\t<li class=\"%s\">", $class);

$le = "\n\t\t</li>";

// More steps go here

}

/*

* Return the markup for output

*/

return $html;

}

要完成步骤 6-10(实际构建日期,检查该周是否需要换行,组装日期标记,用 filler 完成最后一周,并返回标记),请添加以下粗体代码:

public function buildCalendar()

{

/*

* Determine the calendar month and create an array of

* weekday abbreviations to label the calendar columns

*/

$cal_month = date('F Y', strtotime($this->_useDate));

define('WEEKDAYS', array('Sun', 'Mon', 'Tue',

'Wed', 'Thu', 'Fri', 'Sat'));

/*

* Add a header to the calendar markup

*/

$html = "\n\t<h2>$cal_month</h2>";

for ( $d=0, $labels=NULL; $d<7; ++$d )

{

$labels .= "\n\t\t<li>" . WEEKDAYS[$d] . "</li>";

}

$html .= "\n\t<ul class=\"weekdays\">"

. $labels . "\n\t</ul>";

/*

* Create the calendar markup

*/

$html .= "\n\t<ul>"; // Start a new unordered list

for ( $i=1, $c=1, $t=date('j'), $m=date('m'), $y=date('Y');

$c<=$this->_daysInMonth; ++$i )

{

/*

* Apply a "fill" class to the boxes occurring before

* the first of the month

*/

$class = $i<=$this->_startDay ? "fill" : NULL;

/*

* Add a "today" class if the current date matches

* the current date

*/

if ( $c+1==$t && $m==$this->_m && $y==$this->_y )

{

$class = "today";

}

/*

* Build the opening and closing list item tags

*/

$ls = sprintf("\n\t\t<li class=\"%s\">", $class);

$le = "\n\t\t</li>";

/*

* Add the day of the month to identify the calendar box

*/

if ( $this->_startDay<$i``&&

{

$date = sprintf("\n\t\t\t<strong>d</strong>",$c++);

}

else { $date="``&

/*

* If the current day is a Saturday, wrap to the next row

*/

$wrap = $i!=0``&&

/*

* Assemble the pieces into a finished item

*/

$html .= $ls . $date . $le . $wrap;

}

/*

* Add filler to finish out the last week

*/

while ( $i%7!=1 )

{

$html .= "\n\t\t<li class=\"fill\">``&

++$i;

}

/*

* Close the final unordered list

*/

$html .= "\n\t</ul>\n\n";

/*

* Return the markup for output

*/

return $html;

}

测试现在的功能。图 4-5 显示了浏览器中的无序列表。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-5。

The markup as generated by buildCalendar()

在日历中显示事件

将事件添加到日历显示中非常简单,只需从_createEventObj()加载events数组,并遍历索引中存储的与当前日期匹配的事件(如果存在的话)。使用以下粗体代码将事件数据添加到日历标记中:

public function buildCalendar()

{

/*

* Determine the calendar month and create an array of

* weekday abbreviations to label the calendar columns

*/

$cal_month = date('F Y', strtotime($this->_useDate));

define('WEEKDAYS', array('Sun', 'Mon', 'Tue',

'Wed', 'Thu', 'Fri', 'Sat'));

/*

* Add a header to the calendar markup

*/

$html = "\n\t<h2>$cal_month</h2>";

for ( $d=0, $labels=NULL; $d<7; ++$d )

{

$labels .= "\n\t\t<li>" . WEEKDAYS[$d] . "</li>";

}

$html .= "\n\t<ul class=\"weekdays\">"

. $labels . "\n\t</ul>";

/*

* Load events data

*/

$events = $this->_createEventObj();

/*

* Create the calendar markup

*/

$html .= "\n\t<ul>"; // Start a new unordered list

for ( $i=1, $c=1, $t=date('j'), $m=date('m'), $y=date('Y');

$c<=$this->_daysInMonth; ++$i )

{

/*

* Apply a "fill" class to the boxes occurring before

* the first of the month

*/

$class = $i<=$this->_startDay ? "fill" : NULL;

/*

* Add a "today" class if the current date matches

* the current date

*/

if ( $c+1==$t && $m==$this->_m && $y==$this->_y )

{

$class = "today";

}

/*

* Build the opening and closing list item tags

*/

$ls = sprintf("\n\t\t<li class=\"%s\">", $class);

$le = "\n\t\t</li>";

/*

* Add the day of the month to identify the calendar box

*/

if ( $this->_startDay<$i && $this->_daysInMonth>=$c)

{

/*

* Format events data

*/

$event_info = NULL; // clear the variable

if ( isset($events[$c]) )

{

foreach ( $events[$c] as $event )

{

$link = '<a href="view.php?event_id='

. $event->id . '">' . $event->title

. '</a>';

$event_info .= "\n\t\t\t$link";

}

}

$date = sprintf("\n\t\t\t<strong>d</strong>",$c++);

}

else { $date=" "; }

/*

* If the current day is a Saturday, wrap to the next row

*/

$wrap = $i!=0 && $i%7==0 ? "\n\t</ul>\n\t<ul>" : NULL;

/*

* Assemble the pieces into a finished item

*/

$html .= $ls . $date . $event_info . $le . $wrap;

}

/*

* Add filler to finish out the last week

*/

while ( $i%7!=1 )

{

$html .= "\n\t\t<li class=\"fill\"> </li>";

++$i;

}

/*

* Close the final unordered list

*/

$html .= "\n\t</ul>\n\n";

/*

* Return the markup for output

*/

return $html;

}

Caution

不要忘记将新的$event_info变量添加到循环底部的标记中!

当数据库事件加载到日历显示中时,标题显示在相应日期的旁边(参见图 4-6 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-6。

An event title displayed next to the appropriate date Note

链接的事件标题指向一个尚不存在的名为view.php的文件。该文件将在本章后面的“输出 HTML 以显示完整的事件描述”一节中进行构建和解释。

使日历看起来像日历

此时,您的标记是正确的,您的事件也在那里,但是生成的代码看起来一点也不像日历。

为了纠正这一点,花点时间完成 HTML 标记,并使用 CSS 样式化页面。

Note

因为这本书不是讲 CSS 的,所以用的规则就不详细解释了。有关 CSS 的更多信息,请参阅 David Powers 的《CSS3 入门》。

简而言之,CSS 文件将执行以下操作:

  • 将每个列表项向左浮动。
  • 调整边距和边框,使日期看起来像传统日历。
  • 添加悬停效果,这样鼠标悬停的日子将被突出显示。
  • 事件标题样式。
  • 也为事件标题添加悬停效果。
  • 添加一些 CSS3 风格,包括圆角和阴影,以获得乐趣。

Tip

有关 CSS3 的更多信息,请访问 http://css3.info/

css文件夹(/public/assets/css/style.css)中创建一个名为style.css的新文件,并添加以下规则:

body {

background-color: #789;

font-family: georgia, serif;

font-size: 13px;

}

#content {

display: block;

width: 812px;

margin: 40px auto 10px;

padding: 10px;

background-color: #FFF;

-moz-border-radius: 6px;

-webkit-border-radius: 6px;

border-radius: 6px;

border:2px solid black;

-moz-box-shadow: 0 0 14px #123;

-webkit-box-shadow: 0 0 14px #123;

box-shadow: 0 0 14px #123;

}

h2,p {

margin: 0 auto 14px;

text-align: center;

}

ul {

display: block;

clear: left;

height: 82px;

width: 812px;

margin: 0 auto;

padding: 0;

list-style: none;

background-color: #FFF;

text-align: center;

border: 1px solid black;

border-top: 0;

border-bottom: 2px solid black;

}

li {

position: relative;

float: left;

margin: 0;

padding: 20px 2px 2px;

border-left: 1px solid black;

border-right: 1px solid black;

width: 110px;

height: 60px;

overflow: hidden;

background-color: white;

}

li:hover {

background-color: #FCB;

z-index: 1;

-moz-box-shadow: 0 0 10px #789;

-webkit-box-shadow: 0 0 10px #789;

box-shadow: 0 0 10px #789;

}

.weekdays {

height: 20px;

border-top: 2px solid black;

}

.weekdays li {

height: 16px;

padding: 2px 2px;

background-color: #BCF;

}

.fill {

background-color: #BCD;

}

.weekdays li:hover,li.fill:hover {

background-color: #BCD;

-moz-box-shadow: none;

-webkit-box-shadow: none;

box-shadow: none;

}

.weekdays li:hover,.today {

background-color: #BCF;

}

li strong {

position: absolute;

top: 2px;

right: 2px;

}

li a {

position: relative;

display: block;

border: 1px dotted black;

margin: 2px;

padding: 2px;

font-size: 11px;

background-color: #DEF;

text-align: left;

-moz-border-radius: 6px;

-webkit-border-radius: 6px;

border-radius: 6px;

z-index: 1;

text-decoration: none;

color: black;

font-weight: bold;

font-style: italic;

}

li a:hover {

background-color: #BCF;

z-index: 2;

-moz-box-shadow: 0 0 6px #789;

-webkit-box-shadow: 0 0 6px #789;

box-shadow: 0 0 6px #789;

}

保存样式表,并关闭它;在本章中,您不需要再次修改它。在下一节中,您将创建公共文件,这些文件将在页面中包含这些样式。

创建通用文件—页眉和页脚

这个应用将有多个供用户查看的页面,它们都需要一组通用的 HTML 元素、样式表等等。为了尽可能简化维护,您将使用两个文件header.inc.phpfooter.inc.php来包含这些公共元素。

首先,在common文件夹(/public/assets/common/header.inc.php)中创建一个名为header.inc.php的文件。这个文件将保存 HTML 的DOCTYPE声明,并创建一个head部分,其中包含一个Content-Type meta 标签、文档标题和到文档所需的任何 CSS 文件的链接。

因为文档标题会因页面而异,所以您将设置一个变量$page_title来存储每个页面的标题。

此外,因为一个页面可能需要多个 CSS 文件,所以一个 CSS 文件名数组将被传入一个名为$css_files的变量,并循环生成正确的标记。

在该文件中,放置以下代码:

<!DOCTYPE html

PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd

<html xmlns="http://www.w3.org/1999/xhtml

<head>

<meta http-equiv="Content-Type"

content="text/html;charset=utf-8" />

<title><?php echo $page_title; ?></title>

<?php foreach ( $css_files as $css ): ?>

<link rel="stylesheet" type="text/css" media="screen,projection"

href="assets/css/<?php echo $css; ?>" />

<?php endforeach; ?>

</head>

<body>

接下来,在common文件夹(/public/assets/common/footer.inc.php)中创建一个名为footer.inc.php的文件来包含标记的结束部分。

目前,这个文件不需要做太多事情:它只是关闭了在header.inc.php中打开的bodyhtml标签。随着您继续开发这个应用,这里会添加更多的内容。

将以下内容插入footer.inc.php:

</body>

</html>

将文件添加到索引

要将这些新的片段组合在一起,您需要修改索引文件。首先,将值添加到$page_title$css_files变量中,然后包含头文件。

此外,为了包装页面内容,添加一个新的 ID 为contentdiv,包装对buildCalendar()的调用。

最后,添加对页脚文件的调用来完成页面。完成后,将用粗体显示的代码修改索引文件:

<?php

declare(strict_types=1);

/*

* Include necessary files

*/

include_once '../sys/core/init.inc.php';

/*

* Load the calendar

*/

$cal = new Calendar($dbo, "2016-01-01 12:00:00");

/*

* Set up the page title and CSS files

*/

$page_title = "Events Calendar";

$css_files = array('style.css');

/*

* Include the header

*/

include_once 'assets/common/header.inc.php';

?>

<div id="content">

<?php

/*

* Display the calendar HTML

*/

echo $cal->buildCalendar();

?>

</div>``<!--``end #content

<?php

/*

* Include the footer

*/

include_once 'assets/common/footer.inc.php';

?>

保存更改后,重新加载浏览器以查看结果,如图 4-7 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-7。

The calendar with the header, footer, and CSS styles applied

输出 HTML 以显示完整的事件描述

接下来,您需要允许用户查看事件的细节。这将分三步完成。

Create a method to format an array of a single event’s data when loaded by ID.   Create a method to generate markup containing the data as loaded by the first method.   Create a new file to display the markup generated by the second method.

创建格式化单个事件数据的方法

_createEventObj()类似,这个方法的目的是从_loadEventData()返回的结果集中生成一个Event对象。

因为当只使用一个事件时,标记生成相当简单,所以这个方法所做的就是使用_loadEventData()通过 ID 加载所需的事件,然后从该方法返回第一个(也是唯一一个,因为有了LIMIT 1子句)结果。

将以下方法添加到Calendar类中:

<?php

declare(strict_types=1);

class Calendar extends DB_Connect

{

private $_useDate;

private $_m;

private $_y;

private $_daysInMonth;

private $_startDay;

public function __construct($dbo=NULL, $useDate=NULL) {...}

public function buildCalendar() {...}

private function _loadEventData($id=NULL) {...}

private function _createEventObj() {...}

/**

* Returns a single event object

*

* @param int $id an event ID

* @return object the event object

*/

private function _loadEventById($id)

{

/*

* If no ID is passed, return NULL

*/

if ( empty($id) )

{

return NULL;

}

/*

* Load the events info array

*/

$event = $this->_loadEventData($id);

/*

* Return an event object

*/

if ( isset($event[0]) )

{

return new Event($event[0]);

}

else

{

return NULL;

}

}

}

?>

当被调用时,该方法将返回一个对象(对于 ID 为1)如下所示:

Event Object

(

[id] => 1

[title] => New Year’s Day

[description] => Happy New Year!

[start] => 2016-01-01 00:00:00

[end] => 2016-01-01 23:59:59

)

创建生成标记的方法

既然单个事件的数据数组已经可用,您可以构建一个新的公共方法来将事件数据格式化为 HTML 标记。这个方法将被称为displayEvent();它将接受一个事件的 ID,并使用以下步骤生成 HTML 标记。

Load the event data using _loadEventById().   Use the start and end dates to generate strings to describe the event.   Return the HTML markup to display the event.

通过将粗体代码添加到Calendar类来创建displayEvent()方法:

<?php

declare(strict_types=1);

class Calendar extends DB_Connect

{

private $_useDate;

private $_m;

private $_y;

private $_daysInMonth;

private $_startDay;

public function __construct($dbo=NULL, $useDate=NULL) {...}

public function buildCalendar() {...}

/**

* Displays a given event’s information

*

* @param int $id the event ID

* @return string basic markup to display the event info

*/

public function displayEvent($id)

{

/*

* Make sure an ID was passed

*/

if ( empty($id) ) { return NULL; }

/*

* Make sure the ID is an integer

*/

$id = preg_replace('/[⁰-9]/', '', $id);

/*

* Load the event data from the DB

*/

$event = $this->_loadEventById($id);

/*

* Generate strings for the date, start, and end time

*/

$ts = strtotime($event->start);

$date = date('F d, Y', $ts);

$start = date('g:ia', $ts);

$end = date('g:ia', strtotime($event->end));

/*

* Generate and return the markup

*/

return "<h2>$event->title</h2>"

. "\n\t<p class=\"dates\">$date, $start``&

. "\n\t<p>$event->description</p>";

}

private function _loadEventData($id=NULL) {...}

private function _createEventObj() {...}

private function _loadEventById($id) {...}

}

?>

创建新文件以显示全部事件

为了显示displayEvent()的输出,您将创建一个新文件。这个文件将被称为view.php,它将驻留在公共文件夹(/public/view.php)中。

将使用包含要显示的事件 ID 的查询字符串调用该文件。如果没有提供 ID,用户将返回到日历的主视图。

view.php的顶部,检查一个事件 ID,然后加载初始化文件;页面标题和 CSS 文件在变量中设置,头文件被调用。之后,创建了一个Calendar类的新实例。

接下来,建立一个新的 ID 为contentdiv,并调用displayEvent()方法。添加一个返回主日历页面的链接,关闭div,并包含页脚。

考虑到所有因素,文件最终应该是这样的:

<?php

declare(strict_types=1);

/*

* Make sure the event ID was passed

*/

if ( isset($_GET['event_id']) )

{

/*

* Make sure the ID is an integer

*/

$id = preg_replace('/[⁰-9]/', '', $_GET['event_id']);

/*

* If the ID isn’t valid, send the user to the main page

*/

if ( empty($id) )

{

header("Location: ./");

exit;

}

}

else

{

/*

* Send the user to the main page if no ID is supplied

*/

header("Location: ./");

exit;

}

/*

* Include necessary files

*/

include_once '../sys/core/init.inc.php';

/*

* Output the header

*/

$page_title = "View Event";

$css_files = array("style.css");

include_once 'assets/common/header.inc.php';

/*

* Load the calendar

*/

$cal = new Calendar($dbo);

?>

<div id="content">

<?php echo $cal->displayEvent($id) ?>

<a href="./">``&

</div>``<!--``end #content

<?php

/*

* Output the footer

*/

include_once 'assets/common/footer.inc.php';

?>

通过返回主日历并单击事件标题来测试该文件。view.php文件以匹配日历的格式加载并显示事件信息(见图 4-8 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4-8。

The event information displayed after clicking an event title

摘要

现在您有了一个功能完整的事件日历,它是使用面向对象的 PHP 和 MySQL 创建的。在这个过程中,您学习了如何处理日期,如何将条目组织到对象中以便于访问,以及如何输出标记和样式表以类似于传统的日历。在下一章中,您将构建添加、编辑和创建事件的控件。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/15236.html
标签
VKDoc
评论
发布的文章

Jquery (第三章笔记)

2024-08-18 00:08:37

jquery实现tab切换简单好用

2024-08-18 00:08:35

jQuery Cookie 插件使用教程

2024-08-14 22:08:01

jQuery的DOM操作

2024-08-18 00:08:21

echarts显示中国地图

2024-08-18 00:08:11

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!