วันอังคารที่ 23 พฤศจิกายน พ.ศ. 2553

VB.NET 2005 มีอะไรใหม่ (What's new in VB.NET 2005)

VB.NET 2005 มีอะไรใหม่ (What's new in VB.NET 2005)
มีหลายคนสงสัยว่า VB8 หรือ VB.NET 2005 ปรับปรุงอะไรเพิ่มขึ้นบ้างจากเวอร์ชั่นก่อน ผมเลยค้นคว้ามาบอกกันไว้ครับ เผื่อหลายๆ คนต้องเอาไปอธิบายคนที่สงสัยอีกหลายๆ คน

VB.NET 2005 นั้น มาพร้อมกับ .NET 2.0 ซึ่งได้ปรับปรุงตัวแปลภาษาไป 14 ข้อดังนี้

1. เพิ่ม Continue Statement
คำสั่ง Continue จะใช้ร่วมกับคำสั่ง Do, For, While มีประโยชน์ในการจบการวนครั้งปัจจุบัน แล้วไปเริ่มวนครั้งถัดไปเลย ตัวอย่างการใช้งาน


'การใช้ Continue เพื่อทำลูปครั้งถัดไปเลย
Dim Result As Byte = 0
For Count As Byte = 0 To 4
If Count = 2 Then Continue For
Result += Count
Next Count



จากโค้ดด้านบน การทำงานจะผ่านบรรทัด Result += Count เพียง 4 ครั้งเท่านั้น ทำให้ตัวแปร Result สุดท้ายมีค่าเป็น 0 + 1 +3 + 4 = 8 เพราะในรอบที่ 3 คำสั่ง Continue จะทำงาน ซึ่งมีผลให้ทำงาน ที่ Next ถัดไปแทน

2. เพิ่มความสามารถในการเข้าถึง Form แบบ Visual Basic 6
ถ้าใครเคยใช้ VB6 แล้วเปลี่ยนมาใช้ VB.NET 2003 จะพบว่า สิ่งที่ต้องปรับตัวมากที่สุด คือ เรื่อง Object-Oreinted Programming ซึ่งแม้กระทั่ง Form ที่ใช้ ก็ต้องคิดแบบ OO นั้นคือ Form ทุก Form ต้องเป็น Class ดังนั้นจะเรียกใช้ Form ใดๆได้ ก็ต้องสร้าง Instance หรือ Object ของ Form นั้นๆก่อนเสมอ ซึ่งเป็นเรื่องวุ่นวายมากสำหรับคนที่คุ้นเคย VB6

เมื่อมีผู้เรียกร้องเข้ามามาก ว่าความง่ายของ VB หายไปเยอะ ทางผู้พัฒนา VB Compiler จึงนำ concept เดิมมาใช้ นั้นคือ สามารถเข้าถึงหรือเรียกใช้ Form อื่นๆ ได้โดยไม่ต้องสร้าง Object ก่อน ดังตัวอย่างข้างล่าง

จากเดิมใน VB.NET 2003 เมื่อต้องใช้งาน Form อื่นๆ

'Form1 เรียก Form2 จะต้องสร้าง Object ของ Form2 ก่อนเสมอ

Public Class Form1
Dim objForm2 As New Form2

Sub ShowForm2()
objForm5.Show()
End Sub
End Class


เมื่อต้องใช้งาน Form อื่นๆ ใน VB.NET 2005 ก็สามารถงานได้ง่ายๆ (โดยแบบเดิมก็ยังใช้ได้อยู่)

'Form1 เรียก Form2 โดยไม่ต้องสร้าง Object ของ Form2 เลย
Public Class Form1
Sub ShowForm2()
Form2.Show()
End Sub
End Class

3. เพิ่ม IsNot Operator
จากเดิมที่เวลาเปรียบเทียบ ตัวแปรอ็อบเจ็ค ว่าชี้ไปที่ Object เดียวกันรึเปล่า ก็ใช้คำสั่ง Is หรือ Not ... IS ... ซึ่ง syntax อ่านยาก เพราะต้องมานั่งนิเสธ (~) คำตอบอีกที ทางทีมงานเลยออกคำสั่ง IsNot มาให้ใช้อีกตัว เพื่อทำให้โค้ดอ่านง่ายขึ้น เช่น

'Object1 และ Object2 มาจาก Class เดียวกัน แต่ชี้ไปคนละ Instance
Dim Object1, Object2 As New Object

'ทั้ง 2 บรรทัดข้างล่างนี้ มีความหมายเหมือนกัน
If Not (Object1 Is Object2) Then MsgฺBox("ทั้ง 2 ตัวแปร ไม่ได้ชี้ไปที่อ็อบเจ็คเดียวกัน")
If Object1 IsNot Object2 Then MsgฺBox("ทั้ง 2 ตัวแปร ไม่ได้ชี้ไปที่อ็อบเจ็คเดียวกัน")


4. เพิ่ม TryCast Operator
คำสั่งในการแปลงชนิดข้อมูล (Type Convertion) ในรุ่นก่อน มีอยู่ 2 คำสั่ง คือ CType และ DirectCast ซึ่งทั้ง 2 คำสั่ง เมื่อไม่สามารถแปลงชนิดข้อมูลได้ จะต้องดักข้อผิดพลาดขึ้นด้วยคลาส InvalidCastException แต่สำหรับคำสั่ง TryCast นั้น เมื่อไม่สามารถแปลงชินดข้อมูลได้ คำสังจะ return ค่าออกมาเป็น Nothing เช่น

ในรุ่นก่อนจะมีเพียงคำสั่ง CType และ DirectCast

'กรณีตัวอย่างการใช้ CType และ DirectCast และเปรียบเทียบความแตกต่าง
Try
Dim x As Object = 1.23
Dim y As Integer = CTpye(x, Interger)
'จะมีข้อผิดพลาด(Fails) ในบรรทัดนี้ เพราะในขณะ run-time นั้น x จะมี type เป็น Double
'และการใช้คำสั่ง DirectCast นั้น ชนิดข้อมูลทั้ง 2 ต้องมีความสัมพันธ์แบบ Inheritance กัน
'ซึ่ง Double และ Integer ก็ไม่ได้สืบทอดกันมา
Dim z As Integer = DirectCast(x, Integer)
Catch ex As InvalidCastException
MessageBox.Show("Casting error")
End Try

ตัวอย่างการตรวจสอบ type ก่อน convert และใช้ DirectCast ในการ casting

Dim obj As Object
Dim PrintableObject As IPrintable
If TypeOf obj Is IPrintable Then
PrintableObject = DirectCast(obj, IPrintable)
PrintableObject.Print()
End If

ตัวอย่างคำสั่ง TryCast ที่เป็นการตรวจสอบ type และ convert ให้คำสังเดียว

'กรณีใช้ TryCast จะคล้ายกับคำสั่ง DirectCast เพราะคำสั่งจะ return Nothing
'เมื่อทั้งสองชนิดไม่ได้มีความสัมพันธ์แบบ Inheritance กัน
Dim x As Double = 1.23
Dim y As Object = TryCast(x, Integer)
If y Is Nothing Then
MessageBox.Show("Cannot casting")
End If

(มีฝรั่งบางคนท้วง การตั้ง Keyword การเปลงชนิดข้อมูลของทั้งสามตัวนี้ว่า ไม่คล้องจองกัน คือ CType, DirectCast, TryCast น่าจะตั้งชื่อเป็น Cast, DirectCast, TryCast มากกว่า มันถึงจะลงท้ายด้วย Cast เหมือนกัน ซึงจำง่ายกว่า)

5. เพิ่ม Using Statement
คำสั่งนี้มีประโยชน์มาก สำหรับผู้ที่ต้องใช้งาน unmaged resource อื่นๆ เช่น File, COM wrapper, SQL Connection ซึ่งการใช้คำสั่ง Using ... End Using จะมีการคืนทรัพยากร (dispose) ให้อัตโนมัติ ซึ่งในรุ่นก่อนจำเป็นต้องใช้ คำสั่ง Try ... End Try เพื่อคืนทรัพยากรให้ระบบ ลองดูตัวอย่างข้างล่างนี้

ตัวอย่าง การคืนทรัพยากรระบบโดยใช้ Try

'ทรัพยากรจะถูกคืนให้ระบบ หลัง Finally Statement
Try
Dim fs As New System.IO.StreamReader("C:\boot.ini")
Console.Write(fs.ReadToEnd)
Catch ex As Exception
Console.Write(ex.message)
Finally
fs.Close()
fs.Dispose()
End Try

ถ้าเปลี่ยนมาใช้ Using ก็จะสะดวกขึ้นมาก

'คำสั่ง Using จะคืนทรัพยากรให้ระบบ เมื่อจบ End Using
Using fs As New System.IO.StreamReader("C:\boot.ini")
Console.Write(fs.ReadToEnd)
End Using

6. เพิ่มวิธีการประกาศ index ของ array โดยระบุ 0 เป็น index เริ่มต้น
ปกติ array ใน VB.NET จะเริ่มจาก 0 อยู่แล้ว แต่เพื่อให้โค้ดอ่านง่ายขึ้น โดยเฉพาะคนที่มาจาก VB6 ซึ่งจะเพิ่มส่วนที่มีการบอกไว้ว่า index ของ array นั้นเริ่มที่ 0 เช่น

Dim Num(0 To 9) As Interger

ก็มีความหมายเดียวกับ

Dim Num(9) As Interger

7. Properties (Get, Set) สามารถกำหนด Access Level ต่างกันได้
เดิมทีนั้น Method Get, Set Access Level จะเหมือนกันโดยปริยาย แต่ใน 2005 สามารถให้ Get และ Set มี Access Level คนละอย่างกันได้ ดังตัวอย่าง

'คลาสนี้จะอนุญาตให้คลาสอื่นที่สืบทอดไป สามารถอ่านค่า salaryValue ได้
'แต่การเขียนจะอนุญาตเฉพาะภายในคลาสตัวเองเท่านั้น
Public Class employee
Private salaryValue As Double
Protected Property salary() As Double
Get
Return salaryValue
End Get
Private Set(ByVal value As Double)
salaryValue = value
End Set
End Property
End Class

8. เพิ่มชนิดข้อมูล 3 Unsigned และ 1 Signed
VB.NET 2005 เพิ่ม Data Type แบบจำนวนเต็ม ที่ไม่มีเครื่องหมาย +,- มาเกี่ยวข้อง หรือพูดง่ายๆ ว่า เป็นค่าบวกอย่างเดียว ได้แก่ UShort, UInteger, ULong และเพิ่ม SByte ที่มีเครื่องหมาย +/- ด้วย รายละเอียดดังนี้

UShort มีขนาด 16 bits (2 bytes) เก็บค่าได้ตั้งแต่ 0 ถึง 65,535
UInterger มีขนาด 32 bits (4 bytes) เก็บค่าได้ตั้งแต่ 0 ถึง 4,294,967,295
ULong มีขนาด 64 bits (8 bytes) เก็บค่าได้ตั้งแต่ 0 ถึง 18,446,744,073,709,551,615
SByte มีขนาด 8 bits (1 bytes) เก็บค่าได้ตั้งแต่ -127 ถึง 128

9. เพิ่มชนิดข้อมูล Nullable
ปกติในตัวแปรแบบ Value Type จะเก็บค่าใดค่าหนึ่งตามชนิดข้อมูล แม้จะไม่ได้ใส่ค่าใดๆ ไป ค่าในตัวแปรก็จะมีค่าโดย Default อยู่แล้ว แต่ใน VB.NET 2005 ได้เพิ่มความสามารถให้ตัวแปรไม่มีค่าเริ่มต้น หรือไม่เก็บค่าใดๆ เลยก็ได้ ด้วยคำสั่ง Nullable เช่น

'การประกาศให้ตัวแปรไม่เก็บค่าใดๆ
Dim Number As Nullable(Of Integer)
Number = 20

'กำหนดให้ตัวแปร Number ไม่มีค่าใดๆ
Number = Nothing

'ตรวจสอบค่าในตัวแปร Number ใช้ property ชื่อ HasValue (ไม่สามารถใช้ Is Nothing ในการตรวจสอบได้)
If Number.HasValue Then
MsgBox("Value is " & Number.Value)
Else
MsgBox("No Value")
End If

์ี10. เพิ่มความสามารถในการทำ Operator Overloading
แม้จะไม่มีสถานะการณ์ที่ต้องให้ใช้ Operator Overloading บ่อยๆ แต่มันก็เป็นความสามารถด้าน OO ที่ Advance และมีประโยชน์มาก เพราะบางครั้งเราก็ต้องการใช้ Operator ที่มีอยู่ในความหมายอื่นๆ หรือใช้กับ operands ที่เป็นอ็อบเจ็คอื่นๆ เช่น operator + ปกติแล้วจะใช้กับ operands 2 ตัว ที่เป็นตัวเลข (primitive type) หรืออ็อบเจ็คตัวเลข (reference type) เท่านั้น อันหมายถึงการเอาค่าตัวเลข 2 ตัว มาบวกกัน แต่ถ้าต้องการใช้กับอ็อบเจ็คอื่นๆ ก็ต้อง overload มาปรับแต่งเอง เช่น

ต้องการ overload ตัว operator เครื่องหมาย + เพื่อให้สามารถรวมค่าความยาวของอ็อบเจ็คดินสอได้

'สร้างคลาสดินสอ ที่มีการใช้ Overload เครื่องหมาย + ไว้
Public Class Pencil
Private Length As Integer

Public Sub New(ByVal Length As Integer)
Me.Length = Length
End Sub

Public Overrides Function ToString() As String
Return Length.ToString()
End Function

Public Shared Operator +(ByVal PencilA As Pencil, _
ByVal PencilB As Pencil) As Integer

Return PencilA.Length + PencilB.Length
End Operator
End Class

'สร้างอ็อบเจ็คดินสอ แล้วใช้ operator + จะไม่เกิด error เพราะมีการกำหนดให้ + ใช้ได้กับคลาสนี้แล้ว
'สมมติความยาวดินสอมีหน่วยเป็น เซนติเมตร
Public Shared Sub Main()
Dim PencilA As New Pencil(10)
Dim PencilB As New Pencil(12)

Dim TwoPencilLength As Integer = PencilA + PencilB

Console.WriteLine("รวมความยาวของดินสอ 2 แท่ง เท่ากับ " & _
TwoPencilLength .ToString() & " เซนติเมตร.")
End Sub

ตัวอย่างนี้แสดงให้เห็นถึงการ Overload เครื่องหมาย + มาใช้กับอ็อบเจ็คอื่นๆ ซึ่งที่จริงสามารถดัดแปลง Operator + ไปในความหมายอื่นๆ ก็ได้ เช่น เอาไปเปรียบเทียบหาดินสอที่ยาวที่สุด เป็นต้น

11. เพิ่มความสามารถในการแบ่งโค้ดออกเป็นส่วนๆ โดยใช้ Partial Types
ตัวอย่างที่เห็นได้ชัด คือ การสร้าง Form ใน Visual Studio.NET 2005 เพราะมีการ generate โค้ดแบบ Partial ทำให้เกิดไฟล์ขึ้น 2 ไฟล์ คือไฟล์ xxx.Designer.vb ที่มีการแยกรายละเอียดของการจัดการ Control ต่างๆ และไฟล์ xxx.vb ที่มีเพียง Class...End Class ซึ่งรอการเพิ่ม event procedure และส่วนที่เราต้องเพิ่มเติมลงไป และยังซ่อน xxx.Designer.vb ไว้ข้างหลัง xxx.vb อีกด้วย การแบ่งโค้ดแบบนี้ช่วยโปรแกรมเมอร์มือใหม่ ไม่ต้องเห็นโค้ดที่ยังไม่เข้าใจ และช่วยลดโค้ดที่ไม่ใช่จุดประสงค์หลักของโปรแกรมออกไป (แต่ผมคิดว่าข้อเสียของการทำ Partial คือ ต้องการการจัดการไฟล์เพิ่มขึ้น ซึ่งคงจะยุ่งยากมากขึ้นสำหรับคนที่ใช้ Partial บน IDE ที่ไม่ได้มีการจัดการเรื่องนี้ดีพอ) ตัวอย่างโค้ดคงจะเห็นบ่อยๆ ในผู้ที่ใช้ Visual Studio.NET 2005 แล้ว ผมเลยจะให้ดูโค้ดสั้นๆ ที่ไม่ได้ถูก generate ขึ้นมา

จากคลาสรูปแบบทั่วไป

'ไฟล์ Man.vb
Public Class Man
Sub Walk()
....
End Sub

Sub Run()
....
End Sub
End Class

เมื่อนำมาเขียนแบบ Partial จะต้องเก็บโค้ดไว้เป็น 2 ไฟล์ ซึ่งควรตั้งชื่อไฟล์ให้สอดคลองกันตามวัตถุประสงค์ เพื่อให้ง่ายต่อการ maintain ภายหลัง

'ไฟล์ ManWalk.vb
Partial Public Class Man
Sub Walk()
....
End Sub
End Class

'ไฟล์ ManRun.vb
Partial Public Class Man
Sub Run()
....
End Sub
End Class

12. เพิ่ม Generic Types
เป็นความสามารถด้าน OOP ชั้นสูงอีกตัวที่พูดถึงกันมาก แม้กระทั้ง Java 1.5 ก็ยัง implement ตัว Generic นี้ลงไปด้วย (รวมทั้งเพิ่ง implement เรื่อง enum ที่มีใน VB6 มากนานมากแล้ว) การใช้งาน Generic จะใช้ตรงส่วน Parameter ของ Class, Structure, Interface, Procedure หรือ Delegate ก็ได้ มีประโยชน์ในการกำหนดให้ parameter เป็น data type ที่หลากหลายได้ พูดไปก็คงนึกภาพไม่ออกครับ ต้องลองดูโค้ดเลย

กำหนดคลาสให้มีการใช้ Generic

'คลาสนกแก้ว
Public Class Parrot(Of T)
Sub Speak(ByVal Word As T)
MsgBox(Word.ToString)
End Sub
End Class

การใช้งานคลาสที่เป็น Generic จะพบว่า parameter สามารถเป็น data type ได้หลายแบบ ดังตัวอย่างข้างล่าง ที่เป็นได้ทั้ง Integer หรือ String

'การสร้างอ็อบเจ็คนกแก้ว แล้วให้พูดเลข 3
Dim objParrot As New Parrot(Of Integer)
objParrot.Speak(3)

'การสร้างอ็อบเจ็คนกแก้ว แล้วให้พูดว่า Hello
Dim objParrot As New Parrot(Of String)
objParrot.Speak("Hello")

13. สามารถปรับแต่ง Event ได้ ด้วยคีเวิร์ด Custom
เป็นความสามารถที่ไม่มีใน C# ที่ชาว VB สามารถนำไปโม้ได้ ซึ่งเหนือกว่าด้วยการลองทำ serialize object ดู พบว่าโค้ด Event ยาวกว่า แต่โค้ดสั้นกว่าและใช้ง่ายกว่าเวลานำไปใช้ อ่านเพิ่มเติมที่ .NET 2.0 solution to serialization of objects that raise events และ Better version of the .NET 2.0 event/serialization solution ประโยชน์ของการปรับแต่ง Event คือ การเพิ่มเหตุกาณ์หรือ trigger ให้กับ object ได้ ตามต้องการ ถ้า object นั้นไม่มี Event ที่ต้องการ โครงสร้าง Custom Event เป็นดังนี้

Public Custom Event .... As EventHandler
AddHandler(ByVal value As EventHandler)
...
End AddHandler

RemoveHandler(ByVal value As EventHandler)
...
End RemoveHandler

RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
...
End RaiseEvent
End Event

AddHandler จะเป็นที่ใส่คำสั่งต่างๆ เมื่อ add event ให้ object
RemoveHandler จะเป็นที่ใส่คำสั่งต่างๆ เมื่อ remove event จาก object
RaiseEvent จะเป็นที่ใส่คำสั่งต่างๆ เมื่อมีการ trig event จาก object

14. เพิ่ม parameter การแจ้งเตือน ในการทำงานของ Compiler
เป็น option ของ compiler ในการแจ้ง warning ซึ่งเพิ่มขึ้นมา 2 option คือ /nowarn หมายถึง ไม่ต้องแจ้ง warning ใดๆ เลย และ /warnaserror หมายถึง ให้แจ้ง warning เหมือนเป็น error เลย ทั้ง 2 option นี้ ถ้าใช้ VS.NET 2005 สามารถเซตได้ที่คลิ๊กขวาที่ project เลือก properties ข้างล่างสุด แล้วไปที่ menu ชื่อ compile จะพบ checkbox 2 ตัว ที่ชื่อ Disable All Warnings ก็คือ /nowarn และ Treat All Warnings as Errors ก็คือ /warnaserror นั้นเอง ตัวอย่างคำสั่งใน command-line ในการ compile

vbc /nowarn

และ

vbc /warnaserror+

วันพุธที่ 10 พฤศจิกายน พ.ศ. 2553

Create Nice-Looking PDFs with PHP and FPDF

Learn how to use the free FPDF library to produce great-looking PDF documents from within your PHP scripts. Full example included.

The PDF format can be a handy way to distribute documents to your visitors. A PDF document is self-contained, looks the same on any PDF reader, and is easy to print. PDFs are often used for reports, brochures, manuals, invoices, product data sheets, and lots more.

Often it's useful to be able to create PDF documents dynamically from within a PHP script. For example, you can produce a custom PDF report based on a user's preferences and include up-to-the-minute data.

In this tutorial I'll walk you through the process of creating a nice-looking, 2-page PDF document using PHP. You'll use the freely-available FPDF library to handle the nitty-gritty of PDF creation.

Here's what your PDF will look like (click to view the finished PDF):





=>info
=>If you want to try out the finished PHP script, you can grab the entire code at the end of the article.

Installing FPDF
To use FPDF, you first need to install the FPDF files on your website. To do this, download the FPDF archive file and extract it to a folder within your website. Call the folder fpdf.

Starting the PHP script
Now that you've installed FPDF, you can start writing your PHP script to produce the PDF report. Create a file called report.php in the same place that you saved your fpdf folder, and open the file in a text editor.

The first thing to do is include the FPDF library so that you can use it. The library is called fpdf.php, and it's inside the fpdf folder that you extracted earlier:

require_once( "fpdf/fpdf.php" );


Now add some variables to configure the report:

// Begin configuration


$textColour = array( 0, 0, 0 );

$headerColour = array( 100, 100, 100 );

$tableHeaderTopTextColour = array( 255, 255, 255 );

$tableHeaderTopFillColour = array( 125, 152, 179 );

$tableHeaderTopProductTextColour = array( 0, 0, 0 );

$tableHeaderTopProductFillColour = array( 143, 173, 204 );

$tableHeaderLeftTextColour = array( 99, 42, 57 );

$tableHeaderLeftFillColour = array( 184, 207, 229 );

$tableBorderColour = array( 50, 50, 50 );

$tableRowFillColour = array( 213, 170, 170 );

$reportName = "2009 Widget Sales Report";

$reportNameYPos = 160;

$logoFile = "widget-company-logo.png";

$logoXPos = 50;

$logoYPos = 108;

$logoWidth = 110;

$columnLabels = array( "Q1", "Q2", "Q3", "Q4" );

$rowLabels = array( "SupaWidget", "WonderWidget", "MegaWidget", "HyperWidget" );

$chartXPos = 20;

$chartYPos = 250;

$chartWidth = 160;

$chartHeight = 80;

$chartXLabel = "Product";

$chartYLabel = "2009 Sales";

$chartYStep = 20000;


$chartColours = array(

array( 255, 100, 100 ),

array( 100, 255, 100 ),

array( 100, 100, 255 ),

array( 255, 255, 100 ),

);


$data = array(

array( 9940, 10100, 9490, 11730 ),

array( 19310, 21140, 20560, 22590 ),

array( 25110, 26260, 25210, 28370 ),

array( 27650, 24550, 30040, 31980 ),

);



// End configuration


These variables make it easy to tweak your report by keeping all the key configuration data at the top of the file. The variables include:


Various colours used in the report.
Each colour is specified as a 3-element array containing red, green and blue values (in the range 0-255).

The report title ("2009 Widget Sales Report") and position.


The URL and dimensions of the company logo image.
You'll include this image in the title page of the report.

The row and column labels for the report data. You'll use these when displaying the table and chart in the report.
Configuration settings for the chart. These include the chart position, dimensions, axis labels, and the step value to use for the Y-axis scale.
The colours to use for the chart bars. As with the other report colours, these are specified as 3-element arrays. There are 4 colours: 1 for each bar in the chart.
The report data. This is a 2-dimensional array containing 4 rows of quarterly sales figures, 1 row per product.

Creating the title page
Now that you've set up the report configuration, you're ready to start building the PDF. First, you'll create the title page for the report. This consists of the company logo and the report name, centred in the page.

Creating the FPDF object

The first thing you need to do is create a new FPDF object to hold the PDF data. The FPDF constructor accepts 3 optional arguments, as follows:

The page orientation. Use 'P' for portrait, or 'L' for landscape. The default is 'P'.
The units to use for the page measurements. Use 'pt', 'mm', 'cm', or 'in'. The default is 'mm'.
The page format. Possible values include 'A3', 'A4', 'A5', 'Letter', and 'Legal'. Or you can specify a custom width and height with a 2-element array. The default value is 'A4'.
For this example, use a portrait orientation, millimetres for units, and A4 format:


/**
Create the title page
**/

$pdf = new FPDF( 'P', 'mm', 'A4' );
Setting the text colour

Now set the colour to use for text in the page. You do this by calling the FPDF SetTextColor() method, passing in the red, green and blue values of the colour to use (each value should be in the range 0-255). Use the colour values in the $textColour array that you created in the configuration section earlier:


$pdf->SetTextColor( $textColour[0], $textColour[1], $textColour[2] );
Creating a page

Now you're ready to create a new blank page in the PDF by calling FPDF's AddPage() method:


$pdf->AddPage();
Adding the logo image

FPDF makes it really easy to insert images in the page. Just call the Image() method, passing in the following arguments:

The path of the image file. This can be an absolute path, or relative to the PHP script. You can also use a URL.
The X and Y position of the top left corner of the image in the PDF. If you omit these then the current position is used.
The width and height of the image in the PDF. If you omit both values then the original image dimensions are used (at 72 DPI). If you omit 1 value then the other value is calculated automatically.
The image type. Allowed values include 'JPG', 'JPEG', 'PNG' and 'GIF' (upper- or lower-case). If you omit this value then FPDF guesses the image format from the filename extension.
A URL to link the image to. This is optional — if you supply a URL then the image becomes a clickable link.
All measurements, such as X and Y positions, widths and heights, use the units you specified when you created the PDF (mm in this case).

You can create your own logo image (make sure it's 300 DPI) or download my example image. Save your logo image in the same folder as your PHP script, then insert the image in the PDF as follows:


// Logo
$pdf->Image( $logoFile, $logoXPos, $logoYPos, $logoWidth );
Setting the font

FPDF lets you choose the font face, style and size to use for text in the PDF. To do this, you call the SetFont() method, which takes the following arguments:

The font family. You can use any of the following standard family names: 'Courier', 'Helvetica', 'Arial', 'Times', 'Symbol', or 'ZapfDingbats'.
The font style. Options include: '' (regular), 'B' (bold), 'I' (italic), and 'U' (underline). You can combine these — for example, 'BU' for bold, underlined text.
The font size. You specify this in points (it defaults to 12 points).
For the report name on the title page, use an Arial Bold 24-point font:


// Report Name
$pdf->SetFont( 'Arial', 'B', 24 );
As well as using the standard fonts, you can import any TrueType or Type 1 font using the AddFont() method. See the manual on the FPDF website for details.

Adding some text

You're now ready to add the report name. FPDF objects have a concept of "the current position", which is where the next piece of text or other element will be inserted. Since you want the report name to appear just after halfway down the page, you first need to move the current position down to this point, which is 160mm from the top of the page (this is stored in the $reportNameYPos configuration variable). To do this, use FPDF's Ln() method, which adds a line break with an optional height value:


$pdf->Ln( $reportNameYPos );
If you don't specify a height for the line break then the height of the last printed cell is used.

Now add the report name. There are a few different ways that you can add text using FPDF. In this case, you'll use the Cell() method, which, amongst other things, lets you easily centre text.

Cell() takes the following arguments (all of them optional):

The cell width and height. If you omit the width then the cell stretches to the right margin. If you omit the height then it defaults to zero.
The string of text to print. Defaults to ''.
Whether to draw a border around the cell. This can be either a number (0=no border, 1=border), or a string containing 1 or more of the following: 'L' (left), 'T' (top), 'R' (right), and 'B' (bottom). Default: 0.
Where to place the current position after drawing the cell. Values can be 0 (to the right), 1 (to the start of the next line), or 2 (below). Default: 0.
The text alignment. Possible values are 'L' (left align), 'C' (centre), or 'R' (right align). Default: 'L'.
Whether the cell background should be filled with colour. true = filled, false = transparent. Default: false.
A URL to link to. If specified, turns the text cell into a link.
Now, use Cell() to insert the report name and centre it, as follows:

$pdf->Cell( 0, 15, $reportName, 0, 0, 'C' );

Creating a page header and intro text
That's the title page done. Now you'll create the page containing some header text, a heading, and some intro text, followed by a table and chart of sales data.

First the page header. Add a new page, then output the page header, which consists of the report name centred at the top of the page using an Arial Regular 17-point font. Use the $headerColour configuration variable to set the text colour:


$pdf->AddPage();
$pdf->SetTextColor( $headerColour[0], $headerColour[1], $headerColour[2] );
$pdf->SetFont( 'Arial', '', 17 );
$pdf->Cell( 0, 15, $reportName, 0, 0, 'C' );
Now for the intro text. First print a heading using the regular text colour and an Arial 20-point font. Since you don't need this text to be centred, you can use the simpler Write() method, which takes the line height, the text to write, and an optional link URL:


$pdf->SetTextColor( $textColour[0], $textColour[1], $textColour[2] );
$pdf->SetFont( 'Arial', '', 20 );
$pdf->Write( 19, "2009 Was A Good Year" );
Now you can output the intro text itself in 12-point Arial. This consists of a 16mm line break, followed by the first paragraph, a 12mm line break, and the final paragraph. Give each line a line height of 6mm:


$pdf->Ln( 16 );
$pdf->SetFont( 'Arial', '', 12 );
$pdf->Write( 6, "Despite the economic downturn, WidgetCo had a strong year. Sales of the HyperWidget in particular exceeded expectations. The fourth quarter was generally the best performing; this was most likely due to our increased ad spend in Q3." );
$pdf->Ln( 12 );
$pdf->Write( 6, "2010 is expected to see increased sales growth as we expand into other countries." );
The Write() method automatically wraps text when it reaches the right side of the page.

Adding a table of data
Next you'll add a table of sales data below the intro text. First, set the border colour for the table. The SetDrawColor() method sets the colour to use for borders and other lines, so you can use this to set the table cell borders. Then move down 15mm to create a space between the intro text and the table:


$pdf->SetDrawColor( $tableBorderColour[0], $tableBorderColour[1], $tableBorderColour[2] );
$pdf->Ln( 15 );
Creating the table header row

The table header row consists of the "PRODUCT", "Q1, "Q2", "Q3", and "Q4" cells. The "PRODUCT" cell uses different text and background colours to the other header cells.

You already know to call the SetTextColor() method to set the text colour to use. To set the background colour to use, you call SetFillColor(), which takes the same RGB arguments as SetTextColor().

To create table cells you use — you guessed it — the Cell() method, specifying the cell width, height, contents, and alignment. You'll also pass 1 as the 4th argument to set a border, and true as the 7th argument to fill the cell with a background colour.

Here, then, is the code to create the table header row. First you set a bold font, then create the left-aligned "PRODUCT" cell with appropriate text and fill colours. Finally, you set colours for the remaining 4 header cells, then loop through the $columnLabels array to display the cells using centred text:


// Create the table header row
$pdf->SetFont( 'Arial', 'B', 15 );

// "PRODUCT" cell
$pdf->SetTextColor( $tableHeaderTopProductTextColour[0], $tableHeaderTopProductTextColour[1], $tableHeaderTopProductText
Colour[2] );
$pdf->SetFillColor( $tableHeaderTopProductFillColour[0], $tableHeaderTopProductFillColour[1], $tableHeaderTopProductFill
Colour[2] );
$pdf->Cell( 46, 12, " PRODUCT", 1, 0, 'L', true );

// Remaining header cells
$pdf->SetTextColor( $tableHeaderTopTextColour[0], $tableHeaderTopTextColour[1], $tableHeaderTopTextColour[2] );
$pdf->SetFillColor( $tableHeaderTopFillColour[0], $tableHeaderTopFillColour[1], $tableHeaderTopFillColour[2] );

for ( $i=0; $iCell( 36, 12, $columnLabels[$i], 1, 0, 'C', true );
}

$pdf->Ln( 12 );
The space character before the word "PRODUCT" in the code helps to pad the word within the table cell so that it isn't hard up against the left edge of the cell. The same trick is used later on with the product names in the left hand column. (Unfortunately there's currently no way to control cell padding with FPDF without extending the class.)

Creating the data rows

The rest of the table consists of 4 rows of sales figures — 1 row for each product — over the 4 quarters. First, set a couple of variables:


// Create the table data rows

$fill = false;
$row = 0;

These variables work as follows:

$fill : Whether a cell should be filled or not. You'll toggle this value every time you've drawn a row to create a striped row effect.

$row : The current row number. This lets you display the appropriate row label for each row as you move through the table.

Now you can loop through the $data array using a foreach loop, printing a row at a time. For each row you create the left header cell containing the product name, and the 4 data cells containing the sales data. Set appropriate text and background colours for each cell as you go.

To display the data cells, use a for loop to move through the 4-element array containing the data, calling the PHP number_format() function to print the sales figure with thousands separators.

After displaying a row, you increment the $row variable, toggle the $fill variable, and use Ln() to move down to the start of the next line, ready to output the next row.

Here's the code for the whole loop:


foreach ( $data as $dataRow ) {

// Create the left header cell
$pdf->SetFont( 'Arial', 'B', 15 );
$pdf->SetTextColor( $tableHeaderLeftTextColour[0], $tableHeaderLeftTextColour[1], $tableHeaderLeftTextColour[2] );
$pdf->SetFillColor( $tableHeaderLeftFillColour[0], $tableHeaderLeftFillColour[1], $tableHeaderLeftFillColour[2] );
$pdf->Cell( 46, 12, " " . $rowLabels[$row], 1, 0, 'L', $fill );

// Create the data cells
$pdf->SetTextColor( $textColour[0], $textColour[1], $textColour[2] );
$pdf->SetFillColor( $tableRowFillColour[0], $tableRowFillColour[1], $tableRowFillColour[2] );
$pdf->SetFont( 'Arial', '', 15 );

for ( $i=0; $iCell( 36, 12, ( ' }

$row++;
$fill = !$fill;
$pdf->Ln( 12 );
}


Creating a bar chart
The last element of the page is a bar chart showing the total sales figures for the 4 products over the whole year.

Calculating scales and bar width

The first thing to do is compute the scales for the X and Y axes. For the X scale this is simply the number of products to display divided by the desired chart width (subtracting some millimetres to allow for space to the left of the bars):


/***
Create the chart
***/

// Compute the X scale
$xScale = count($rowLabels) / ( $chartWidth - 40 );
To compute the Y scale, you need to find the total sales figure for each product, then determine the highest sales figure across all the products. You can then divide this by the desired chart height to get the Y scale:


// Compute the Y scale

$maxTotal = 0;

foreach ( $data as $dataRow ) {
$totalSales = 0;
foreach ( $dataRow as $dataCell ) $totalSales += $dataCell;
$maxTotal = ( $totalSales > $maxTotal ) ? $totalSales : $maxTotal;
}

$yScale = $maxTotal / $chartHeight;
Now that you know the X scale, you can work out the width (in mm) of each bar in the chart. This is the inverse of the X scale value, reduced by a factor of 1.5 to allow some space between each bar:


// Compute the bar width
$barWidth = ( 1 / $xScale ) / 1.5;
Adding the axis lines and labels

So far, so good. Now you can add the X and Y axis lines, data labels, and axis labels. Use Arial 10-point for the data labels.

To create lines in FDPF, you use the Line() method, which accepts 4 arguments: the X and Y co-ordinates of the start of the line, and the X and Y co-ordinates of the end of the line.

For the X axis, draw a horizontal line along the bottom of the chart, allowing 30mm for the Y-axis labels on the left. Then loop through each product name in the $rowLabels array, printing the product name as a text cell at the appropriate point:


// Add the axes:

$pdf->SetFont( 'Arial', '', 10 );

// X axis
$pdf->Line( $chartXPos + 30, $chartYPos, $chartXPos + $chartWidth, $chartYPos );

for ( $i=0; $i <>SetXY( $chartXPos + 40 + $i / $xScale, $chartYPos );
$pdf->Cell( $barWidth, 10, $rowLabels[$i], 0, 0, 'C' );
}
The SetXY() method lets you set the current position to a specific location on the page.

For the Y axis, draw a vertical line up the left side of the chart, again allowing 30mm for the Y-axis labels. Extend the line 8mm above the desired chart height to make room for the axis label later on. Then loop from zero up to the highest bar value, $maxTotal, that you calculated earlier. Jump in steps of $chartYStep (20,000) dollars. At each step, display the current value (right-aligned) and a short tick mark:


// Y axis
$pdf->Line( $chartXPos + 30, $chartYPos, $chartXPos + 30, $chartYPos - $chartHeight - 8 );

for ( $i=0; $i <= $maxTotal; $i += $chartYStep ) { $pdf->SetXY( $chartXPos + 7, $chartYPos - 5 - $i / $yScale );
$pdf->Cell( 20, 10, '$' . number_format( $i ), 0, 0, 'R' );
$pdf->Line( $chartXPos + 28, $chartYPos - $i / $yScale, $chartXPos + 30, $chartYPos - $i / $yScale );
}
Now you can add the axis labels. Use Arial Bold 12-point. Place the X-axis label below the data labels, and the Y-axis label at the top of the Y axis:


// Add the axis labels
$pdf->SetFont( 'Arial', 'B', 12 );
$pdf->SetXY( $chartWidth / 2 + 20, $chartYPos + 8 );
$pdf->Cell( 30, 10, $chartXLabel, 0, 0, 'C' );
$pdf->SetXY( $chartXPos + 7, $chartYPos - $chartHeight - 12 );
$pdf->Cell( 20, 10, $chartYLabel, 0, 0, 'R' );
Drawing the data bars

The last stage of creating the chart is to draw the bars themselves. To draw a bar you can use FPDF's Rect() method, which draws a rectangle using the following arguments:

The X and Y co-ordinates of the upper left corner of the rectangle.
The width and height of the rectangle.
The rectangle style. This can be 'D' or '' (draw a border), 'F' (fill with the current fill colour), or 'DF' / 'FD' (draw and fill).
Now draw the bars. First, Set a variable, $xPos, to track the current bar X position; set it 40mm to the right of the chart's start position to allow for the Y-axis labels and a gap at the start of the bars. Then create a variable, $bar, to hold the current bar number; you'll use this to work out which fill colour to use for each bar:


// Create the bars
$xPos = $chartXPos + 40;
$bar = 0;
Now loop through the $data array, totalling up the value in each row and drawing a bar from the X axis up to that value, scaled using $yScale. Colour each bar differently by using the $bar counter and the colour values in the $chartColours array. After you've drawn each bar, move the X position along to the start of the next bar, increment the $bar counter, and continue the loop:


foreach ( $data as $dataRow ) {

// Total up the sales figures for this product
$totalSales = 0;
foreach ( $dataRow as $dataCell ) $totalSales += $dataCell;

// Create the bar
$colourIndex = $bar % count( $chartColours );
$pdf->SetFillColor( $chartColours[$colourIndex][0], $chartColours[$colourIndex][1], $chartColours[$colourIndex][2] );
$pdf->Rect( $xPos, $chartYPos - ( $totalSales / $yScale ), $barWidth, $totalSales / $yScale, 'DF' );
$xPos += ( 1 / $xScale );
$bar++;
}
The above code uses the PHP modulus (%) operator to repeat the bar colours if the number of bars happens to be greater than the number of elements in the $chartColours array.

Sending the PDF to the browser
Your PDF is finished! The only thing left to do is send the PDF to the browser so that the user can view or download it.

To do this, you call FPDF's Output() method to send the PDF data. This accepts 2 arguments: the suggested filename for the PDF, and a destination flag. This flag can have any of the following values:

I : Displays the PDF inline if supported by the browser, otherwise it's downloaded.
D: Forces the PDF to be downloaded.
F: Saves the file to a folder on the server.
S: Returns the PDF data as a string.

For this example, use the I option to display the PDF inline if possible:


/***
Serve the PDF
***/

$pdf->Output( "report.pdf", "I" );

?>
Output() automatically sends an HTTP "Content-type: application/pdf" header, which tells the browser to expect a PDF document.

You're now ready to test your script. Open your browser and visit the script's URL — for example, www.example.com/report.php. You should see the PDF appear in your browser window. Alternatively you might see a dialog appear that lets you save the PDF to your hard drive. You can then open up the PDF in your PDF viewer, such as Acrobat Reader or Preview.

That's it! You've now created a PDF document on the fly using nothing but PHP and FPDF. Good work!

The complete script
Here's the complete PHP script for you to copy, paste, and play with:


SetTextColor( $textColour[0], $textColour[1], $textColour[2] );
$pdf->AddPage();

// Logo
$pdf->Image( $logoFile, $logoXPos, $logoYPos, $logoWidth );

// Report Name
$pdf->SetFont( 'Arial', 'B', 24 );
$pdf->Ln( $reportNameYPos );
$pdf->Cell( 0, 15, $reportName, 0, 0, 'C' );


/**
Create the page header, main heading, and intro text
**/

$pdf->AddPage();
$pdf->SetTextColor( $headerColour[0], $headerColour[1], $headerColour[2] );
$pdf->SetFont( 'Arial', '', 17 );
$pdf->Cell( 0, 15, $reportName, 0, 0, 'C' );
$pdf->SetTextColor( $textColour[0], $textColour[1], $textColour[2] );
$pdf->SetFont( 'Arial', '', 20 );
$pdf->Write( 19, "2009 Was A Good Year" );
$pdf->Ln( 16 );
$pdf->SetFont( 'Arial', '', 12 );
$pdf->Write( 6, "Despite the economic downturn, WidgetCo had a strong year. Sales of the HyperWidget in particular exceeded expectations. The fourth quarter was generally the best performing; this was most likely due to our increased ad spend in Q3." );
$pdf->Ln( 12 );
$pdf->Write( 6, "2010 is expected to see increased sales growth as we expand into other countries." );


/**
Create the table
**/

$pdf->SetDrawColor( $tableBorderColour[0], $tableBorderColour[1], $tableBorderColour[2] );
$pdf->Ln( 15 );

// Create the table header row
$pdf->SetFont( 'Arial', 'B', 15 );

// "PRODUCT" cell
$pdf->SetTextColor( $tableHeaderTopProductTextColour[0], $tableHeaderTopProductTextColour[1], $tableHeaderTopProductTextColour[2] );
$pdf->SetFillColor( $tableHeaderTopProductFillColour[0], $tableHeaderTopProductFillColour[1], $tableHeaderTopProductFillColour[2] );
$pdf->Cell( 46, 12, " PRODUCT", 1, 0, 'L', true );

// Remaining header cells
$pdf->SetTextColor( $tableHeaderTopTextColour[0], $tableHeaderTopTextColour[1], $tableHeaderTopTextColour[2] );
$pdf->SetFillColor( $tableHeaderTopFillColour[0], $tableHeaderTopFillColour[1], $tableHeaderTopFillColour[2] );

for ( $i=0; $iCell( 36, 12, $columnLabels[$i], 1, 0, 'C', true );
}

$pdf->Ln( 12 );

// Create the table data rows

$fill = false;
$row = 0;

foreach ( $data as $dataRow ) {

// Create the left header cell
$pdf->SetFont( 'Arial', 'B', 15 );
$pdf->SetTextColor( $tableHeaderLeftTextColour[0], $tableHeaderLeftTextColour[1], $tableHeaderLeftTextColour[2] );
$pdf->SetFillColor( $tableHeaderLeftFillColour[0], $tableHeaderLeftFillColour[1], $tableHeaderLeftFillColour[2] );
$pdf->Cell( 46, 12, " " . $rowLabels[$row], 1, 0, 'L', $fill );

// Create the data cells
$pdf->SetTextColor( $textColour[0], $textColour[1], $textColour[2] );
$pdf->SetFillColor( $tableRowFillColour[0], $tableRowFillColour[1], $tableRowFillColour[2] );
$pdf->SetFont( 'Arial', '', 15 );

for ( $i=0; $iCell( 36, 12, ( '$' . number_format( $dataRow[$i] ) ), 1, 0, 'C', $fill );
}

$row++;
$fill = !$fill;
$pdf->Ln( 12 );
}


/***
Create the chart
***/

// Compute the X scale
$xScale = count($rowLabels) / ( $chartWidth - 40 );

// Compute the Y scale

$maxTotal = 0;

foreach ( $data as $dataRow ) {
$totalSales = 0;
foreach ( $dataRow as $dataCell ) $totalSales += $dataCell;
$maxTotal = ( $totalSales > $maxTotal ) ? $totalSales : $maxTotal;
}

$yScale = $maxTotal / $chartHeight;

// Compute the bar width
$barWidth = ( 1 / $xScale ) / 1.5;

// Add the axes:

$pdf->SetFont( 'Arial', '', 10 );

// X axis
$pdf->Line( $chartXPos + 30, $chartYPos, $chartXPos + $chartWidth, $chartYPos );

for ( $i=0; $i <>SetXY( $chartXPos + 40 + $i / $xScale, $chartYPos );
$pdf->Cell( $barWidth, 10, $rowLabels[$i], 0, 0, 'C' );
}

// Y axis
$pdf->Line( $chartXPos + 30, $chartYPos, $chartXPos + 30, $chartYPos - $chartHeight - 8 );

for ( $i=0; $i <= $maxTotal; $i += $chartYStep ) { $pdf->SetXY( $chartXPos + 7, $chartYPos - 5 - $i / $yScale );
$pdf->Cell( 20, 10, '$' . number_format( $i ), 0, 0, 'R' );
$pdf->Line( $chartXPos + 28, $chartYPos - $i / $yScale, $chartXPos + 30, $chartYPos - $i / $yScale );
}

// Add the axis labels
$pdf->SetFont( 'Arial', 'B', 12 );
$pdf->SetXY( $chartWidth / 2 + 20, $chartYPos + 8 );
$pdf->Cell( 30, 10, $chartXLabel, 0, 0, 'C' );
$pdf->SetXY( $chartXPos + 7, $chartYPos - $chartHeight - 12 );
$pdf->Cell( 20, 10, $chartYLabel, 0, 0, 'R' );

// Create the bars
$xPos = $chartXPos + 40;
$bar = 0;

foreach ( $data as $dataRow ) {

// Total up the sales figures for this product
$totalSales = 0;
foreach ( $dataRow as $dataCell ) $totalSales += $dataCell;

// Create the bar
$colourIndex = $bar % count( $chartColours );
$pdf->SetFillColor( $chartColours[$colourIndex][0], $chartColours[$colourIndex][1], $chartColours[$colourIndex][2] );
$pdf->Rect( $xPos, $chartYPos - ( $totalSales / $yScale ), $barWidth, $totalSales / $yScale, 'DF' );
$xPos += ( 1 / $xScale );
$bar++;
}


/***
Serve the PDF
***/

$pdf->Output( "report.pdf", "I" );

?>

Summary
In this tutorial you've learned how to use PHP with the FPDF library to produce a good-looking PDF report. Along the way you've looked at some useful methods of the FPDF library, and seen some techniques for creating tables and charts using FPDF.

There's a lot more to FPDF, such as the ability to create headers and footers, use automatic page breaks, and more. Find out more by reading the online manual at the FPDF website.

Have fun!

--------------------------------------------
Tutorial by Matt Doyle | Level: Intermediate | Published on 26 February 2010
Categories:
Web Development > PHP
. number_format( $dataRow[$i] ) ), 1, 0, 'C', $fill );
}

$row++;
$fill = !$fill;
$pdf->Ln( 12 );
}


Creating a bar chart
The last element of the page is a bar chart showing the total sales figures for the 4 products over the whole year.

Calculating scales and bar width

The first thing to do is compute the scales for the X and Y axes. For the X scale this is simply the number of products to display divided by the desired chart width (subtracting some millimetres to allow for space to the left of the bars):


/***
Create the chart
***/

// Compute the X scale
$xScale = count($rowLabels) / ( $chartWidth - 40 );
To compute the Y scale, you need to find the total sales figure for each product, then determine the highest sales figure across all the products. You can then divide this by the desired chart height to get the Y scale:


// Compute the Y scale

$maxTotal = 0;

foreach ( $data as $dataRow ) {
$totalSales = 0;
foreach ( $dataRow as $dataCell ) $totalSales += $dataCell;
$maxTotal = ( $totalSales > $maxTotal ) ? $totalSales : $maxTotal;
}

$yScale = $maxTotal / $chartHeight;
Now that you know the X scale, you can work out the width (in mm) of each bar in the chart. This is the inverse of the X scale value, reduced by a factor of 1.5 to allow some space between each bar:


// Compute the bar width
$barWidth = ( 1 / $xScale ) / 1.5;
Adding the axis lines and labels

So far, so good. Now you can add the X and Y axis lines, data labels, and axis labels. Use Arial 10-point for the data labels.

To create lines in FDPF, you use the Line() method, which accepts 4 arguments: the X and Y co-ordinates of the start of the line, and the X and Y co-ordinates of the end of the line.

For the X axis, draw a horizontal line along the bottom of the chart, allowing 30mm for the Y-axis labels on the left. Then loop through each product name in the $rowLabels array, printing the product name as a text cell at the appropriate point:


// Add the axes:

$pdf->SetFont( 'Arial', '', 10 );

// X axis
$pdf->Line( $chartXPos + 30, $chartYPos, $chartXPos + $chartWidth, $chartYPos );

for ( $i=0; $i <>SetXY( $chartXPos + 40 + $i / $xScale, $chartYPos );
$pdf->Cell( $barWidth, 10, $rowLabels[$i], 0, 0, 'C' );
}
The SetXY() method lets you set the current position to a specific location on the page.

For the Y axis, draw a vertical line up the left side of the chart, again allowing 30mm for the Y-axis labels. Extend the line 8mm above the desired chart height to make room for the axis label later on. Then loop from zero up to the highest bar value, $maxTotal, that you calculated earlier. Jump in steps of $chartYStep (20,000) dollars. At each step, display the current value (right-aligned) and a short tick mark:


// Y axis
$pdf->Line( $chartXPos + 30, $chartYPos, $chartXPos + 30, $chartYPos - $chartHeight - 8 );

for ( $i=0; $i <= $maxTotal; $i += $chartYStep ) { $pdf->SetXY( $chartXPos + 7, $chartYPos - 5 - $i / $yScale );
$pdf->Cell( 20, 10, '$' . number_format( $i ), 0, 0, 'R' );
$pdf->Line( $chartXPos + 28, $chartYPos - $i / $yScale, $chartXPos + 30, $chartYPos - $i / $yScale );
}
Now you can add the axis labels. Use Arial Bold 12-point. Place the X-axis label below the data labels, and the Y-axis label at the top of the Y axis:


// Add the axis labels
$pdf->SetFont( 'Arial', 'B', 12 );
$pdf->SetXY( $chartWidth / 2 + 20, $chartYPos + 8 );
$pdf->Cell( 30, 10, $chartXLabel, 0, 0, 'C' );
$pdf->SetXY( $chartXPos + 7, $chartYPos - $chartHeight - 12 );
$pdf->Cell( 20, 10, $chartYLabel, 0, 0, 'R' );
Drawing the data bars

The last stage of creating the chart is to draw the bars themselves. To draw a bar you can use FPDF's Rect() method, which draws a rectangle using the following arguments:

The X and Y co-ordinates of the upper left corner of the rectangle.
The width and height of the rectangle.
The rectangle style. This can be 'D' or '' (draw a border), 'F' (fill with the current fill colour), or 'DF' / 'FD' (draw and fill).
Now draw the bars. First, Set a variable, $xPos, to track the current bar X position; set it 40mm to the right of the chart's start position to allow for the Y-axis labels and a gap at the start of the bars. Then create a variable, $bar, to hold the current bar number; you'll use this to work out which fill colour to use for each bar:


// Create the bars
$xPos = $chartXPos + 40;
$bar = 0;
Now loop through the $data array, totalling up the value in each row and drawing a bar from the X axis up to that value, scaled using $yScale. Colour each bar differently by using the $bar counter and the colour values in the $chartColours array. After you've drawn each bar, move the X position along to the start of the next bar, increment the $bar counter, and continue the loop:


foreach ( $data as $dataRow ) {

// Total up the sales figures for this product
$totalSales = 0;
foreach ( $dataRow as $dataCell ) $totalSales += $dataCell;

// Create the bar
$colourIndex = $bar % count( $chartColours );
$pdf->SetFillColor( $chartColours[$colourIndex][0], $chartColours[$colourIndex][1], $chartColours[$colourIndex][2] );
$pdf->Rect( $xPos, $chartYPos - ( $totalSales / $yScale ), $barWidth, $totalSales / $yScale, 'DF' );
$xPos += ( 1 / $xScale );
$bar++;
}
The above code uses the PHP modulus (%) operator to repeat the bar colours if the number of bars happens to be greater than the number of elements in the $chartColours array.

Sending the PDF to the browser
Your PDF is finished! The only thing left to do is send the PDF to the browser so that the user can view or download it.

To do this, you call FPDF's Output() method to send the PDF data. This accepts 2 arguments: the suggested filename for the PDF, and a destination flag. This flag can have any of the following values:

I : Displays the PDF inline if supported by the browser, otherwise it's downloaded.
D: Forces the PDF to be downloaded.
F: Saves the file to a folder on the server.
S: Returns the PDF data as a string.

For this example, use the I option to display the PDF inline if possible:


/***
Serve the PDF
***/

$pdf->Output( "report.pdf", "I" );

?>
Output() automatically sends an HTTP "Content-type: application/pdf" header, which tells the browser to expect a PDF document.

You're now ready to test your script. Open your browser and visit the script's URL — for example, www.example.com/report.php. You should see the PDF appear in your browser window. Alternatively you might see a dialog appear that lets you save the PDF to your hard drive. You can then open up the PDF in your PDF viewer, such as Acrobat Reader or Preview.

That's it! You've now created a PDF document on the fly using nothing but PHP and FPDF. Good work!

The complete script
Here's the complete PHP script for you to copy, paste, and play with:


SetTextColor( $textColour[0], $textColour[1], $textColour[2] );
$pdf->AddPage();

// Logo
$pdf->Image( $logoFile, $logoXPos, $logoYPos, $logoWidth );

// Report Name
$pdf->SetFont( 'Arial', 'B', 24 );
$pdf->Ln( $reportNameYPos );
$pdf->Cell( 0, 15, $reportName, 0, 0, 'C' );


/**
Create the page header, main heading, and intro text
**/

$pdf->AddPage();
$pdf->SetTextColor( $headerColour[0], $headerColour[1], $headerColour[2] );
$pdf->SetFont( 'Arial', '', 17 );
$pdf->Cell( 0, 15, $reportName, 0, 0, 'C' );
$pdf->SetTextColor( $textColour[0], $textColour[1], $textColour[2] );
$pdf->SetFont( 'Arial', '', 20 );
$pdf->Write( 19, "2009 Was A Good Year" );
$pdf->Ln( 16 );
$pdf->SetFont( 'Arial', '', 12 );
$pdf->Write( 6, "Despite the economic downturn, WidgetCo had a strong year. Sales of the HyperWidget in particular exceeded expectations. The fourth quarter was generally the best performing; this was most likely due to our increased ad spend in Q3." );
$pdf->Ln( 12 );
$pdf->Write( 6, "2010 is expected to see increased sales growth as we expand into other countries." );


/**
Create the table
**/

$pdf->SetDrawColor( $tableBorderColour[0], $tableBorderColour[1], $tableBorderColour[2] );
$pdf->Ln( 15 );

// Create the table header row
$pdf->SetFont( 'Arial', 'B', 15 );

// "PRODUCT" cell
$pdf->SetTextColor( $tableHeaderTopProductTextColour[0], $tableHeaderTopProductTextColour[1], $tableHeaderTopProductTextColour[2] );
$pdf->SetFillColor( $tableHeaderTopProductFillColour[0], $tableHeaderTopProductFillColour[1], $tableHeaderTopProductFillColour[2] );
$pdf->Cell( 46, 12, " PRODUCT", 1, 0, 'L', true );

// Remaining header cells
$pdf->SetTextColor( $tableHeaderTopTextColour[0], $tableHeaderTopTextColour[1], $tableHeaderTopTextColour[2] );
$pdf->SetFillColor( $tableHeaderTopFillColour[0], $tableHeaderTopFillColour[1], $tableHeaderTopFillColour[2] );

for ( $i=0; $iCell( 36, 12, $columnLabels[$i], 1, 0, 'C', true );
}

$pdf->Ln( 12 );

// Create the table data rows

$fill = false;
$row = 0;

foreach ( $data as $dataRow ) {

// Create the left header cell
$pdf->SetFont( 'Arial', 'B', 15 );
$pdf->SetTextColor( $tableHeaderLeftTextColour[0], $tableHeaderLeftTextColour[1], $tableHeaderLeftTextColour[2] );
$pdf->SetFillColor( $tableHeaderLeftFillColour[0], $tableHeaderLeftFillColour[1], $tableHeaderLeftFillColour[2] );
$pdf->Cell( 46, 12, " " . $rowLabels[$row], 1, 0, 'L', $fill );

// Create the data cells
$pdf->SetTextColor( $textColour[0], $textColour[1], $textColour[2] );
$pdf->SetFillColor( $tableRowFillColour[0], $tableRowFillColour[1], $tableRowFillColour[2] );
$pdf->SetFont( 'Arial', '', 15 );

for ( $i=0; $iCell( 36, 12, ( '$' . number_format( $dataRow[$i] ) ), 1, 0, 'C', $fill );
}

$row++;
$fill = !$fill;
$pdf->Ln( 12 );
}


/***
Create the chart
***/

// Compute the X scale
$xScale = count($rowLabels) / ( $chartWidth - 40 );

// Compute the Y scale

$maxTotal = 0;

foreach ( $data as $dataRow ) {
$totalSales = 0;
foreach ( $dataRow as $dataCell ) $totalSales += $dataCell;
$maxTotal = ( $totalSales > $maxTotal ) ? $totalSales : $maxTotal;
}

$yScale = $maxTotal / $chartHeight;

// Compute the bar width
$barWidth = ( 1 / $xScale ) / 1.5;

// Add the axes:

$pdf->SetFont( 'Arial', '', 10 );

// X axis
$pdf->Line( $chartXPos + 30, $chartYPos, $chartXPos + $chartWidth, $chartYPos );

for ( $i=0; $i <>SetXY( $chartXPos + 40 + $i / $xScale, $chartYPos );
$pdf->Cell( $barWidth, 10, $rowLabels[$i], 0, 0, 'C' );
}

// Y axis
$pdf->Line( $chartXPos + 30, $chartYPos, $chartXPos + 30, $chartYPos - $chartHeight - 8 );

for ( $i=0; $i <= $maxTotal; $i += $chartYStep ) { $pdf->SetXY( $chartXPos + 7, $chartYPos - 5 - $i / $yScale );
$pdf->Cell( 20, 10, '$' . number_format( $i ), 0, 0, 'R' );
$pdf->Line( $chartXPos + 28, $chartYPos - $i / $yScale, $chartXPos + 30, $chartYPos - $i / $yScale );
}

// Add the axis labels
$pdf->SetFont( 'Arial', 'B', 12 );
$pdf->SetXY( $chartWidth / 2 + 20, $chartYPos + 8 );
$pdf->Cell( 30, 10, $chartXLabel, 0, 0, 'C' );
$pdf->SetXY( $chartXPos + 7, $chartYPos - $chartHeight - 12 );
$pdf->Cell( 20, 10, $chartYLabel, 0, 0, 'R' );

// Create the bars
$xPos = $chartXPos + 40;
$bar = 0;

foreach ( $data as $dataRow ) {

// Total up the sales figures for this product
$totalSales = 0;
foreach ( $dataRow as $dataCell ) $totalSales += $dataCell;

// Create the bar
$colourIndex = $bar % count( $chartColours );
$pdf->SetFillColor( $chartColours[$colourIndex][0], $chartColours[$colourIndex][1], $chartColours[$colourIndex][2] );
$pdf->Rect( $xPos, $chartYPos - ( $totalSales / $yScale ), $barWidth, $totalSales / $yScale, 'DF' );
$xPos += ( 1 / $xScale );
$bar++;
}


/***
Serve the PDF
***/

$pdf->Output( "report.pdf", "I" );

?>

Summary
In this tutorial you've learned how to use PHP with the FPDF library to produce a good-looking PDF report. Along the way you've looked at some useful methods of the FPDF library, and seen some techniques for creating tables and charts using FPDF.

There's a lot more to FPDF, such as the ability to create headers and footers, use automatic page breaks, and more. Find out more by reading the online manual at the FPDF website.

Have fun!

--------------------------------------------
Tutorial by Matt Doyle | Level: Intermediate | Published on 26 February 2010
Categories:
Web Development > PHP

วันจันทร์ที่ 1 พฤศจิกายน พ.ศ. 2553

setTimeout กับ setInterval นั้นมีข้อแตกต่างกัน

หลายคนอาจเคยเขียน JavaScript ในการจับเวลามาแล้ว แต่ทราบหรือไม่ว่าการใช้ setTimeout กับ setInterval นั้นมีข้อแตกต่างกันอยู่
setTimeout ใช้สำหรับการเรียก function ใดๆ ให้ทำงานหลังจากเวลาที่เรากำหนดไว้ เมื่อ function นั้นๆ ถูกเรียนแล้วมันก็จะจบการทำงานไป หากต้องการให้มันหยุดทำงานก่อนเวลาที่กำหนดไว้ก็ให้ใช้ clearTimeout มันก็จะหยุดให้เรา
ตัวอย่างการใช้งาน

var timeoutID = setTimeout(function() {alert("Hello TimeOut");}, 1000);

วิธีการยกเลิก timeout
clearTimeout(timeoutID);
เราจำเป็นต้องจำค่า ID ที่ได้จาก setTimeout เพื่อนำมาบอกให้มันหยุดทำงาน


setInterval จะเป็นการเรียกไปเรื่อยๆ จนกว่าเราจะสั่งให้มันหยุดทำงาน ด้วยคำสั่ง clearInterval
ตัวอย่างการใช้งาน

var intervalID = window.setInterval(function() {alert("Hello Interval");}, 1000);

วิธีการยกเลิก Interval
window.clearInterval(intervalID);

เช่นเดียวกับ clearTimeout ครับ

วันพฤหัสบดีที่ 16 กันยายน พ.ศ. 2553

ความแตกต่างของคำสั่งSQL Union กับ Union All ?

คำสั่ง UNION
คำสั่ง UNION เป็นคำสั่งที่ใช้ในการ select ดึงข้อมูลที่มีความสัมพันธ์กันระหว่าง 2 ตาราง จริงๆ ก็เหมือนกับการใช้คำสั่ง JOIN นั่นแหล่ะครับ แต่อย่างไรก็ตาม ถ้าใช้คำสั่ง UNION มันจะทำการดึงข้อมูลมาให้เราเฉพาะที่ข้อมูลในแถว และคอลัมน์ไม่เหมือนกันมาให้ คล้ายๆกับการใช้คำสั่ง distinct นั่นแหล่ะครับ

คำสั่ง UNION ALL
ส่วน คำสั่ง UNION ALL ก็เหมือนกับคำสั่ง UNION แต่ข้อมูลในทุกแถวและทุกคอลัมน์จะกถูกดึงออกมาหมด ไม่สนใจว่าจะเหมือนหรือไม่เหมือนกันก็ตาม

ความแตกต่างของคำสั่ง UNION กับ คำสั่ง UNION ALL
ความแตกต่างของคำสั่ง UNION กับ คำสั่ง UNION ALL ก็คือ คำสั่ง UNION ALL จะดึงข้อมูลในแถวและคอลัมน์ที่ซ้ำกันออกมาด้วย แต่ คำสั่ง UNION จะดึงข้อมูลในแถวและคอลัมน์ที่ไม่ซ้ำกัน

และการใช้คำสั่ง UNION จะมีประสิทธิภาพมากกว่าการใช้คำสั่ง ELECT DISTINCT

ตัวอย่างเช่น
Table 1 : First,Second,Third,Fourth,Fifth
Table 2 : First,Second,Fifth,Sixth

ผลลัพธ์
UNION: First,Second,Third,Fourth,Fifth,Sixth (สังเกตจะเห็นว่าผลลัพธ์ที่แสดงไม่ซ้ำกัน)
UNION ALL: First,First,Second,Second,Third,Fourth,Fifth,Fifth,Sixth,Sixth (สังเกตจะเห็นว่าผลลัพธ์ที่แสดงซ้ำกัน)

วันอังคารที่ 17 สิงหาคม พ.ศ. 2553

Transaction with Stored Procedure in MySQL Server

Transaction with Stored Procedure in MySQL Server
Stored Procedure in MySQL Server is not atomic by default. To ensure atomicity, you need to write all your SQL Statements within START TRANSACTION .. COMMIIT Block. Moreover, you need to declare EXIT HANDLER for SQLEXCEPTION and SQLWARNINGS to ROLLBACK all of the SQL Statements in START TRANSACTION .. COMMIIT Block.
==========================================

DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
DECLARE EXIT HANDLER FOR SQLWARNING ROLLBACK;

==========================================

You need to keep in mind those two important issues while using Transaction and Exit Handler in MySQL Server.

# Never declare any Variable within START TRANSACTION .. COMMIIT Block.

# Exit Handler Declaration should be your last Declaration just ahead of START TRANSACTION .. COMMIIT Block.

Let's do some coding. In the following code snippet, I write "sp_cheque_leaves_posting" Procedure that accepts two Parameters. Second one is OUT Parameter that returns 1 on succeess of this Stored Procedure Routine.

============================================================================

CREATE PROCEDURE sp_cheque_leaves_posting(IN param_requisition_id INT, OUT param_sp_success TINYINT)

BEGIN

DECLARE details_id_loop INT DEFAULT 0;
DECLARE starting_serial_no INT;
DECLARE record_not_found INT DEFAULT 0;
DECLARE cursor_details_id CURSOR FOR SELECT details_id FROM khan_trial.table_requisition_details WHERE requisition_master_id = param_requisition_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET record_not_found = 1;

DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
DECLARE EXIT HANDLER FOR SQLWARNING ROLLBACK;

START TRANSACTION;

SET param_sp_success = 0;

SELECT MAX(ending_serial_no) INTO starting_serial_no FROM table_cheque_leaves_posting;

IF starting_serial_no IS NULL THEN
SET starting_serial_no = 1;
ELSE
SET starting_serial_no = starting_serial_no + 1;
END IF;

OPEN cursor_details_id;

all_details_id:LOOP

FETCH cursor_details_id INTO details_id_loop;

IF record_not_found THEN
LEAVE all_details_id;
END IF;

INSERT INTO table_cheque_leaves_posting
VALUES(details_id_loop,starting_serial_no,starting_serial_no+9,12,12,'A Rahim Khan');

SET starting_serial_no = starting_serial_no + 10;

END LOOP all_details_id;

CLOSE cursor_details_id;

UPDATE table_requisition_master SET cheque_leaves_posting = 1
WHERE requisition_id = param_requisition_id;

SET param_sp_success = 1;

COMMIT;

END

=======================================================
Database has three tables i.e. “table_cheque_leaves_posting”, “table_requisition_details”, “table_requisition_master”. “table_cheque_leaves_posting” does not allow duplicate “details_id”. Call the "sp_cheque_leaves_posting" routine in the following way.

===============================

CALL sp_cheque_leaves_posting(1,@a);
SELECT @a;

Output:
@a
--------
0
================================

That is the Stored Procedure has not completed successfully. Inserting duplicate value for “details_id” Field in “table_cheque_leaves_posting” has resulted in SQLEXCEPTION and SQL Statements within the START TRANSACTION .. COMMIIT Block has not commited as well.

ถ้ายัง ลองนี่ดูครับว่าใช้ได้มั้ยคัรบ

ประกาศการสร้าง stored procedure ด้วยนะครับ
BEGIN
declare exit handler for not found rollback;
declare exit handler for sqlexception rollback;
declare exit handler for sqlwarning rollback;

START TRANSACTION;
Bla .. Bla .. Bla .. อะไรก็ว่ากันไปคัรบ;
COMMIT;
END

วันจันทร์ที่ 16 สิงหาคม พ.ศ. 2553

แก้ WGA Window XP แบบสมบูรณ์

ทำแบบนี้ได้ 100 % โดยไม่ต้องใช้โปรแกรมใดๆครับ

แก้ WGA Window XP แบบสมบูรณ์

แล้วทำวิธีนี้มันต่างกันยังไงหรอ ผมลองทำแล้วมันได้อะ ไม่ต้องโหลดโปรแกรมอะไรเลย

จากที่หลาย ๆ คนได้โดนเจ้าตัว WGA ของทาง Microsoft เข้าไปทำให้หลายท่านเกิดความรำคาญ แต่วันนี้เรามีวิธีที่เด็ดขาดโดยมันจะไม่กลับมารบกวนท่านอีกโดยการทำให้มันเป็นของแท้ซะเลย ทำให้เจ้า WGA ไม่มากวนใจท่านอีกต่อไป และยังสามารถติดตั้งซอฟแวร์ของไมโครซอฟท์ได้โดยที่ตัวตรวจจับมันเห็นเป็นของแท้ เรามาเริ่มกันเลยดีกว่า

1. พิมพ์ regedit ในช่อง run แล้วเข้าไปที่ HKey_Local_Machine\Software\Microsoft\WindowsNT\Current Version\WPAEvents,
ทางด้านขวา ดับเบิ้ลคลิ๊กที่ oobetimer แล้วลบตัวเลขที่อยู่ในนั้นให้หมด (จะเหลือเลข 0 อยู่สี่ตัว ลบไม่ได้)
อันนี้เป็นการล้างค่าที่ไมโครซอฟท์ใช้ตรวจสอบวินโดวส์ขอเราครับ เสร็จแล้วกด OK แล้วปิดไปได้เลย

2. จากนั้นพิมพ์ %systemroot%\system32\oobe\msoobe.exe /a ลงในช่อง run แล้วกด Enter
จะปรากฎหน้าต่างของ Activate Windows ขึ้นมา จากนั้นให้เลือกที่
Yes, I want to telephone a customer service representative to activate Windows แล้วคลิ๊กที่ Next


3. จากนั้นคลิ๊กที่ Change Product Key โดยที่ไม่ต้องใส่อะไรทั้งนั้นในหน้านี้


4. จากนั้นให้ใส่ Product Key B3P7V-Q2WTH-CRK4R-YHJRF-39H4M
แล้วคลิ๊กที่ Update เมื่อเสร็จเรียบร้อยแล้วก็ให้ปิดหน้าต่างนี้ไปได้เลย โดยคลิ๊กที่ X ที่มุมขวาบน


5. รีสตาร์ทเครื่องหนึ่งครั้ง แล้งลองพิมพ์ %systemroot%\system32\oobe\msoobe.exe /a แล้วกด Enter
จะปรากฎคำว่า Windows is already activated ถ้าขึ้นตามนี้ก็แสดงว่าทุกอย่างเรียบร้อยแล้วครับ

6. ทดสอบโดยการตรวจสอบกับทางไมโครซอฟท์ โดยการเปิด Windows Explorer แล้วเลือกที่ Help
แล้วเลือกที่ ตรวจสอบลิจสิทธิ์วินโดวส์ ถ้ามันบอกว่าเป็นของแท้ก็ลุย อัพเดท โหลดและติดตั้งโปรแกรมฟรีของไมโครซอฟท์
ทั้ง Windows Defeder , WMP11 และอื่น ๆ ได้เลย

วันอาทิตย์ที่ 18 กรกฎาคม พ.ศ. 2553

Flow control (IF/CASE) ใน Mysq

Flow control (IF/CASE) ใน Mysql
Flow control ใน Mysql
อ้างอิงจาก http://dev.mysql.com/doc/refman/5.0/en/control-flow-functions.html
คืออะไร? ทำไมต้องใช้?
บางครั้งแม้มันจะง่ายและเร็วกว่า ที่จะทำส่วนคำนวณใน Php ก่อนแล้วค่อยมาทำ Query ใน Mysql แต่ในบางกรณีเล็กๆน้อย หรือบางครั้งที่ต้องการการ Customize โดยไม่ต้องเขียนโค้ดเพิ่ม การเขียน IF/Case เข้าไปใน Mysql ก็สร้างความสะดวกได้มาก
IF(expr1,expr2,expr3)
If expr1 is TRUE (expr1 <> 0 and expr1 <> NULL) then IF() returns expr2; otherwise it returns expr3. IF() returns a numeric or string value, depending on the context in which it is used.
mysql> SELECT IF(1>2,2,3); -> 3
mysql> SELECT IF(1<2,’yes’,'no’); -> ‘yes’
mysql> SELECT IF(STRCMP(’test’,'test1′),’no’,'yes’); -> ‘no’
เป็น IF แบบอย่างง่าย โดยจะมี 3 parameter คือ
- expr1 = Condition โดยให้ใส่เป็นเงื่อนไขต่างๆเช่น a>1 , b < a หรืออื่นๆ ที่ return ค่าเป็น boolean (true/false)
- expr2 = จะทำงานเป็น Condition เป็น true โดย Function IF จะ return ค่า expr2 กลับมา
- expr3 = จะทำงานเป็น Condition เป็น false โดย Function IF จะ return ค่า expr3 กลับมา
โดย IF แบบนี้ จะใช้งานเหมือนกับ Function ทั่วไปเลย ซึ่งข้อเสียคือทำให้ทำ Nested If (If หลายๆเงื่อนไข) ค่อนข้างลำบาก
CASE
CASE value WHEN [compare_value] THEN result [WHEN [compare_value] THEN result …] [ELSEresult] END
CASE WHEN [condition] THEN result [WHEN [condition] THEN result …] [ELSE result] END
mysql> SELECT CASE 1 WHEN 1 THEN ‘one’
-> WHEN 2 THEN ‘two’ ELSE ‘more’ END;
-> ‘one’
mysql> SELECT CASE WHEN 1>0 THEN ‘true’ ELSE ‘false’ END;
-> ‘true’
mysql> SELECT CASE BINARY ‘B’
-> WHEN ‘a’ THEN 1 WHEN ‘b’ THEN 2 END;
-> NULL
Case ของ Mysql จะต่างกับ IF ตรงที่ดูเป็นโครงสร้างภาษา ไม่ใช่เป็น Function
โดยรูปแบบจะเหมือนกับ Switch/Select ของภาษาอื่น คือ จะเข้า Case เมื่อ value/condition มีค่าเท่ากับ compare_value
Trick สำหรับการทำ IF เงื่อนไขแบบเป็นช่วง สามารถดัดแปลงใช้ CASE ช่วยได้
Ex.
(CASE TRUE WHEN point_all<20 THEN 1 WHEN point_all<100 THEN 2 WHEN point_all<500 THEN 3 WHEN point_all<5000 THEN 4 ELSE 5 END) as point_all_lv,
โดยหลักการก็ง่ายๆ ให้ Value ตั้งต้นเป็น TRUE และ ใน WHEN ก็ใส่เป็น Condition ไปแทน ทีนี้เราก็ได้ IF ที่น่าตาอ่านง่ายกว่าแบบ Function

วันอังคารที่ 6 กรกฎาคม พ.ศ. 2553

ทำไมการใช้ index จึงทำให้ query ข้อมูลได้ไวขึ้น?

ทำไมการใช้ index จึงทำให้ query ข้อมูลได้ไวขึ้น?
การทำ index ในฐานข้อมูล ก็คลายกับการทำ index ในท้ายเล่มของหนังสือ การที่เราจะหาเนื้อหาในหนังสือเราก็เปิดไปที่ index แล้วก็หาว่าตัวเนื้อหาที่เราต้องการอ่านนั้นอยู่หน้าไหนบ้าง เราก็เปิดไปยังหน้านั้นได้เลย โดยที่ไม่ต้องมาเปิดหนังสือดูทุกหน้าว่าเนื้อหาที่เรากำลังหานั้นอยู่หน้าไหน

ในฐานข้อมูลการทำ index ก็จะทำให้กับ field หรือคอลัมน์ที่เรามีเงื่อนไขในการ query บ่อยๆ การดึงข้อมูลก็จะไปดูที่ index แล้วกระโดดไปยังตำแหน่งที่เก็บข้อมูลเลยโดยไม่ต้องค้นหาทุกแถวในตาราง

ตัวอย่างเช่น การทำ index ให้กับตาราง employees ที่คอลัมน์ emp_id
1 rowid
2 rowid
3 rowid
. .
. .
. .

เมื่อมีการ query

SELECT *
FROM employees
WHERE emp_id = 3;

Database ก็จะวิ่งไปดึงข้อมูลยังตำแหน่งที่เก็บข้อมูลของ emp003 มาแสดง โดยที่ไม่ต้องวิ่งไปหาทุกๆ แถวในตาราง employees

Oracle Database มี Index อยู่หลายประเภทแต่ที่ถูกใช้กันทั่วไปคือ B-Tree Indexes


(รูปภาพจาก www.oracle.com)

จากรูปจะเห็นว่า B-tree Index นั้นมี block อยู่สองประเภทด้วยกันคือ Branch blocks ไว้สำหรับการค้นหา และ leaf blocks สำหรับเก็บค่า ในการค้นหาก็จะแบ่งเป็นช่วงๆ ตามขอบเขตแต่ละ block ทำให้การค้นหานั้นมีประสิทธิภาพ

ซึ่ง การใช้ index นั้น ยังมีรายละเอียดเสริมเพื่อเพิ่มความเข้าใจอีก โดย คุณ Siamnobita ได้อธิบายเกี่ยวกับ index ด้วยกัน 5 ข้อดังต่อไปนี้

1. สิ่งที่ทำให้เราสามารถค้นหาใน index ได้เร็วนั้นเนื่องจากมีการ sort ตามค่าในคอลัมน์ด้วย เคยมีคนตั้งคำถามใน narisa ประมาณว่าทำไม oracle ถึงไม่เรียงลำดับข้อมูลในตารางซะเลย จะได้ไม่ต้องใช้ index คำตอบก็คือการจัดเก็บแบบเรียงลำดับนั้นทำได้ยากกว่าและเกิดต้นทุนตามมาเช่น เวลาที่ใช้เมื่อมีการเพิ่ม record ใหม่ พื้นที่ว่างเมื่อเกิดการ split block เป็นต้น อย่างไรก็ดีหากเราไม่มีปัญหากับต้นทุนเหล่านี้ เราก็สามารถจัดเก็บข้อมูลในตารางแบบเรียงลำดับได้เลย นั่นคือใช้ index-organized table (IOT) ซึ่งถือเป็นวิธีที่เร็วที่สุดในการค้นหาข้อมูลตาม primary key

2. ปกติเวลา oracle อ่านข้อมูลจะอ่านทีละ block ไม่ใช่ทีละแถว ดังนั้นจะดูว่าเร็วหรือช้า จะนับจากว่า oracle ต้อง access ข้อมูลจำนวนกี่ block เช่น

ถ้าดูจากรูปด้านบน index มี 3 level
เมื่อเรา select * from ... where index_column = ??
oracle จะอ่านข้อมูลทั้งสิ้น 4 block คือ root block ตัวบนสุด, branch block ตัวกลาง, leaf block ตัวล่างสุด, table block ซึ่งรู้ได้ทันทีเมื่อรู้ค่า rowid จำนวน level ที่น้อยที่สุดที่เป็นไปได้คือ 1 level คือเก็บ rowid ไว้ใน root block เลย ซึ่งจะเกิดในกรณีที่ข้อมูลมีจำนวนไม่มาก ( โดย default ขนาดของ block ประมาณ 8K )

3. คำถามคือ หากข้อมูลมีขนาดเล็ก ๆ เช่น ตารางมีขนาดแค่ block เดียว การใช้ index ยังมีประโยชน์อยู่หรือไม่ เดิมผมเคยเข้าใจว่าไม่มีประโยชน์เหมือนกัน แต่เมื่อได้อ่าน blog ของคุณ richard foote ซึ่งทำการทดสอบให้เห็นชัด ๆ ไปเลย พบว่า index ก็ยังมีประโยชน์อยู่ดี เนื่องจาก

3.1 ในการ full table scan นอกจากตัว block ที่เก็บ data แล้วยังต้องอ่าน header block ด้วยจึงมี cost ที่เกิดขึ้นไม่ใช่แค่ 1 I/O เมื่อเทียบกับ index ที่ใช้ 2 I/O (root block + data block) ก็พอ ๆ กัน

3.2 ในการ full table scan จะเก็บ data ที่อ่านมาบน buffer cache ด้าน LRU ซึ่งจะอยู่ใน memory ได้ไม่นาน ขณะที่ index scan จะวางไว้ด้าน MRU ทำให้มีโอกาสใช้ประโยชน์จากการอ่านจาก memory โดยตรงได้มากกว่า โดยเฉพาะเมื่อมีการเรียกใช้ข้อมูลจาก table บ่อย ๆ

4. ในข้อ 3 เขาทดสอบเฉพาะกรณี index unique scan เช่นค้นตาม primary key เท่านั้น หากเป็น index range scan จะเป็นอีกกรณีหนึ่ง ซึ่งทุก rowid ที่เจอใน index ก็จะต้องมีการ access table block หนึ่งครั้ง แม้ว่า block นั้นจะอยู่บน memory แล้วก็ยังเป็น cost อยู่ดี ดังนั้น full table scan ก็อาจจะคุ้มกว่าขึ้นกับจำนวนแถวที่ต้องการ

5. ประโยชน์ของ index อีกข้อ คือโดยปกติจำนวน column ใน index จะน้อยกว่า column ใน table มาก ๆ ดังนั้นขนาดของ index ก็จะเล็กกว่า table มาก ๆ ด้วย หากเราต้องการดูเฉพาะข้อมูลที่อยู่ใน index อยู่แล้ว เราก็อาจ full scan ที่ index แทน table ไปเลยก็ได้ ซึ่งกรณีนี้จะเป็นการ fast full scan ซึ่งอ่าน index แบบ multi block เหมือน table scan ( full scan ใน index มี 2 แบบ full scan เฉย ๆ กับ fast full scan แบบแรกอ่านทีละ block ซึ่งช้ากว่า แต่ข้อดีคือ ผลลัพธ์มีการ sort ตาม index แบบหลังจะไม่มีการเรียงลำดับ

ปล. ต้องขอบคุณคำอธิบายดี ๆ จาก คุณ Siamnobita มาก ๆ ครับ

วันอังคารที่ 29 มิถุนายน พ.ศ. 2553

การใช้ JOIN ใน SQL

การใช้ JOIN ใน SQL
การทำ JOIN ใน SQL คือการนำตาราง 2 ตารางมารวมกัน โดยเงื่อนไขการรวมจะมีดังนี้

INNER JOIN เลือกเฉพาะข้อมูลที่มี key ตรงกันเท่านั้นมาแสดง
OUTER JOIN นำข้อมูลทั้งหมดมาแสดงแม้ว่า key ไม่ตรงกัน
LEFT JOIN ทำการ JOIN ข้อมูลกับตารางที่อยู่ทางซ้าย
RIGHT JOIN ทำการ JOIN ข้อมูลกับตารางที่อยู่ทางขวา
ไวยากรณ์ของการ JOIN มีดังนี้

SELECT columns_list
FROM left_table { INNER | OUTER | LEFT | RIGHT } JOIN right_table
ON join_condition
WHERE select_condition
ตัวอย่าง กำหนดให้ตาราง A และ B มีข้อมูลดังนี้

TABLE_A
------------------------
id Name
------------------------
1 John
2 Susan
3 Tony
TABLE_B
------------------------
id Salary
------------------------
1 23,000
2 12,000
5 32,100
ตัวอย่าง SQL Statement:

SELECT *
FROM TABLE_A INNER JOIN TABLE_B
ON TABLE_A.id = TABLE_B.id
ผลการการ JOIN แต่ละแบบจะได้ผลดังนี้:

INNER JOIN: ผลที่ได้จะเห็นว่าข้อมูลที่ไม่ตรงทั้งใน TABLE_A และ TABLE_B จะไม่แสดง

------------------------
id Name Salary
------------------------
1 John 23,000
2 Susan 12,000

OUTER JOIN: จะได้ข้อมูลทั้งหมด เท่าที่จะแสดงได้ เช่น ใน TABLE_A มีข้อมูลของ Tony แต่ TABLE_B ไม่มีข้อมูลของ Tony (id 3) ดังนั้นข้อมูลที่ได้ จะมีข้อมูลของ Tony แสดงแต่ไม่มีข้อมูลเงินเดือนของ Tony

------------------------
id Name Salary
------------------------
1 John 23,000
2 Susan 12,000
3 Tony
5 32,100
LEFT JOIN: ข้อมูลที่ได้จะยึดข้อมูลในตารางทางซ้ายเป็นหลัก ในที่นี้ให้ TABLE_A เป็น TABLE ด้านซ้าย จะเห็นได้จากข้อมูลใน TABLE_A ซึ่งมีรายชื่อของ John, Susan และ Tony ผลที่ได้ก็จะแสดงข้อมูลของพนักงานทั้ง 3 คน แต่ข้อมูลของ Tony จะไม่มีเงินเดือนแสดง เนื่องจากไม่มีข้อมูลของ Tony อยู่ในตาราง TABLE_B

------------------------
id Name Salary
------------------------
1 John 23,000
2 Susan 12,000
3 Tony

RIGHT JOIN: หลักการจะเหมือนกับ LEFT JOIN แต่จะต่างกันตรงที่ข้อมูลที่ได้จะยึดเอาข้อมูลในตารางด้ายขวาเป็นหลัก

------------------------
id Name Salary
------------------------
1 John 23,000
2 Susan 12,000
5 32,100

เพิ่มเติม

ในกรณีที่ต้องการ Join ข้อมูลตั้งแต่ 3 table ขึ้นไป ให้เลือก Table ขึ้นมาสองอันแล้ว Join กันก่อน จากนั้นจึงค่อย Join กับ Table ที่เหลือ เช่น


SELECT Table1.id, Table2.salary, Table3.position
FROM (Table1 INNER JOIN Table2 ON Table1.id = Table2.id)
INNER JOIN Table3 ON Table1.id = Table3.id;

วันอาทิตย์ที่ 27 มิถุนายน พ.ศ. 2553

เพิ่มความเร็วการ SELECT ด้วย MySQL Query Cache

เพิ่มความเร็วการ SELECT ด้วย MySQL Query Cache

บทความนี้ขอแนะนำอีกวีธีแต่จะมีผลทั้งเซิร์ฟเวอร์ฐานข้อมูล (Database Server) เลย ด้วยการเปิดคุณสมบัติ Query Cache ของ MySQL ให้มีการเก็บ SELECT statement และผลลัพธ์ที่ได้ไว้ใน cache ซึ่งถ้าเรารันคำสั่ง SELECT ครั้งต่อไปที่มี statement เหมือนกัน MySQL จะดึงผลลัพธ์การ query มาจาก cache โดยตรง ไม่ต้องไป query จาก table มาใหม่ ทำให้ผลการค้นหาเร็วขึ้นมาก


ถ้าข้อมูลใน table มีการเปลี่ยนแปลง cache ที่เก็บไว้จะถูกลบออกไป เพื่อให้ผลลัพธ์ในการค้นหาครั้งต่อไปถูกต้อง

ทดสอบก่อนการเปิดใช้ MySQL Query Cache

ในบทความนี้จะทดสอบกับ Fedora 10 โดย MySQL ที่ติดตั้งมาในแผ่นดีวีดี ไม่ได้เปิดคุณสมบัติ Query Cache ไว้ สามารถตรวจสอบได้จากคำสั่ง SHOW Variables ใน mysql

mysql> SHOW Variables WHERE Variable_name RLIKE 'query_cache';
+------------------------------+---------+
| Variable_name | Value |
+------------------------------+---------+
| have_query_cache | YES |
| query_cache_limit | 1048576 |
| query_cache_min_res_unit | 4096 |
| query_cache_size | 0 |
| query_cache_type | ON |
| query_cache_wlock_invalidate | OFF |
+------------------------------+---------+
6 rows in set (0.00 sec)
การที่จะใช้ Query Cache ได้นั้น ตัวแปร have_query_cache ต้องเท่ากับ ‘YES’ ตัวแปร ‘query_cache_type’ เท่ากับ ‘ON’ นอกจากนี้ต้องคอนฟิกค่า query_cache_size ด้วย เพื่อจองขนาด memory เพื่อใช้เก็บ query cache ค่า 0 คือการปิดคุณสมบัติ query cache

ทดสอบรัน SELECT ก่อนการเปิด query cache

หมายเหตุ

ตัวอย่างที่ทดสอบนี้ ไม่มีการสร้าง INDEX หรือคีย์ของฟิลด์ที่ชื่อ item_name
table ที่ใช้ทดสอบมีข้อมูลประมาณ 300,000 rows
mysql> SELECT item_id, item_name FROM items WHERE item_name = 'GK809A0';
+---------+-----------+
| item_id | item_name |
+---------+-----------+
| 261351 | GK809A0 |
+---------+-----------+
1 row in set (0.21 sec)
ลองรัน SELECT หลายๆ ครั้ง ด้วยคำสั่งเหมือนกัน เวลาที่ใช้จะใกล้เคียงกันประมาณ 0.21 วินาที

คอนฟิกค่า query_cache_size

แก้ไขไฟล์ /etc/my.cnf โดยเพิ่มคอนฟิก query_cache_size ลงไป ให้อยู่ภายใต้คอนฟิกของ [mysqld] เช่นต้องการจอง memory ขนาด 32 Mbytes สำหรับทำเป็น cache ตัวอย่างคอนฟิกไฟล์จะเป็นดังนี้

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Default to using old password format for compatibility with mysql 3.x
# clients (those using the mysqlclient10 compatibility package).
old_passwords=1
query_cache_size=32M
หลังจากแก้ไขคอนฟิกไฟล์ /etc/my.cnf รันคำสั่ง service เพื่อรีสตาร์ต MySQL Server ใหม่

[root@db-server ~]# service mysql restart
Shutting down MySQL. [ OK ]
Starting MySQL. [ OK ]
ทดสอบรัน SELECT ครั้งแรกหลังเปิดการใช้ query cache

mysql> SELECT item_id, item_name FROM items WHERE item_name = 'GK809A0';
+---------+-----------+
| item_id | item_name |
+---------+-----------+
| 261351 | GK809A0 |
+---------+-----------+
1 row in set (0.23 sec)
ยังคงใช้เวลา 0.23 วินาที เพราะว่าครั้งแรกนี้ยังไม่มีข้อมูลใน cache เลย

สามารถใช้คำสั่ง SHOW STATUS เพื่อดูสถิติการใช้ cache ของ MySQL

mysql> SHOW STATUS WHERE Variable_name RLIKE 'Qcache';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 1 |
| Qcache_free_memory | 33535344 |
| Qcache_hits | 0 |
| Qcache_inserts | 1 |
| Qcache_lowmem_prunes | 0 |
| Qcache_not_cached | 1 |
| Qcache_queries_in_cache | 1 |
| Qcache_total_blocks | 4 |
+-------------------------+----------+
8 rows in set (0.00 sec)
ค่าตัวแปร Qcache_not_cached เพิ่มเป็น 1 คือการ SELECT ครั้งนี้ไม่ได้ดึงข้อมูลจาก cache และค่า Qcache_inserts เป็น 1 คือเริ่มมีการใส่ผลลัพธ์เข้าไปใน cache

ทดลองรัน SELECT อีกครั้งนึง โดยพิมพ์ statement ให้เหมือนเดิมทุกอย่าง ทั้งตัวพิมพ์ใหญ่ พิมพ์เล็ก การเว้นวรรค ต้องเหมือนกันหมด ย้ำอีกที ต้องเหมือนกันหมด เพราะ MySQL ใช้ในการเปรียบเทียบกับ statement ที่เก็บไว้ใน cache

mysql> SELECT item_id, item_name FROM items WHERE item_name = 'GK809A0';
+---------+-----------+
| item_id | item_name |
+---------+-----------+
| 261351 | GK809A0 |
+---------+-----------+
1 row in set (0.00 sec)
เวลาที่ใช้กลายเป็น 0.00 วินาทีไปเลย เพราะว่าผลลัพธ์จากการ SELECT ครั้งนี้ MySQL ไปดึงมาจาก cache แทน

เราสามารถตรวจสอบว่าผลลัพธ์จากการ SELECT มาจาก cache โดยดูค่า Qcache_hits ที่เพิ่มขึ้น จากคำสั่ง SHOW STATUS

mysql> SHOW STATUS WHERE Variable_name RLIKE 'Qcache';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 1 |
| Qcache_free_memory | 33535344 |
| Qcache_hits | 1 |
| Qcache_inserts | 1 |
| Qcache_lowmem_prunes | 0 |
| Qcache_not_cached | 1 |
| Qcache_queries_in_cache | 1 |
| Qcache_total_blocks | 4 |
+-------------------------+----------+
8 rows in set (0.00 sec)

การเพิ่มความเร็วในการหาข้อมูลของ MySQL ด้วยวิธีการทำ Index

การเพิ่มความเร็วในการหาข้อมูลของ MySQL ด้วยวิธีการทำ Index

สวัสดีครับ ผมเขียนบทความนี้ขึ้นมาเนื่องจาก พบว่า Database ที่ผมใช้งานอยู่ Select ข้อมูลมาค่อนข้างช้า และผมก็ใช้วิธีการ Set Index เพิ่มเข้าไป มันก็สามารถทำงานได้เร็วขึ้นมากทีเดียว ก็เลยลองเขียนมาให้อ่านกันดู เผื่อใครเจอปัญหาทำนองนี้อยู่

ปกติแล้วหลังจากเขียนโปรแกรม php ที่ใช้กับ database MySQL เสร็จเรียบร้อยแล้ว ก็มักจะจบตรงนั้น พอใช้งานซักพัก ก็จะเริ่มพบปัญหาว่าทำไม ใช้ไปเรื่อยๆ มันก็ช้ามากขึ้น ซึ่งก็ต้องมาทำการจูนกันนิดหน่อย โดยการสร้าง Index ในบทความนี้ผมขอยกตัวอย่างว่า ผมใช้ table 1 อันก็แล้วกัน โดยมี field ดังข้างล่าง

idINT10(Primary)
fname VARCHAR30(Primary)
lname VARCHAR30(Primary)
salaryINT10(Primary)


แล้วเกิดผมอยากจะ Select ข้อมูลว่าให้แสดงพนักงานที่มีเงินเดือนมากกว่า 5,000 บาทโดยใช้คำสั่งข้างล่าง

select * from emp where salary>5000;

ในกรณีนี้ MySQL จะทำ full table scan คือ จะทำการอ่านข้อมูลจากทุก record ว่าตรงตามเงื่อนไขหรือเปล่า ซึ่งเราสามารถตรวจสอบดูได้ โดยใช้คำสั่ง

explain select * from emp where salary>5000;

ผลลัพธ์ที่ได้จะเป็นดังนี้

tabletypepossible_keyskey key_len ref rows Extra
empALL 2073 where used


โดยช่อง table จะแสดงว่าชื่อ table ของ record ที่ถูกดึงข้อมูลมาคือ table อะไร ในตัวอย่างนี้คือ table ที่ชื่อ emp
ช่อง type จะแสดง วิธีการอ่านข้อมูลจาก table นั้น ในที่นี้ ALL หมายถึง database จะอ่านมาทุก record เพื่อนำมาเปรียบเทียบกับเงื่อนไข where
rows หมายถึงจำนวน row ที่ถูกอ่านขึ้นมา
ซึ่งหลังจากที่เรามีการ set index ที่ field salary แล้วทำการตรวจสอบอีกครั้งด้วยคำสั่ง

explain select * from emp where salary>5000;

จะได้ผลลัพธ์ดังนี้

tabletypepossible_keyskey key_len ref rows Extra
emprefsalarysalary10const 1247


โดย type จะเปลี่ยนจาก ALL มาเป็น ref ซึ่งหมายถึง database จะดึงมาเฉพาะ row ที่มีเงื่อนไขตรงกับ index ที่ set ไว้
possible_keys คือ index key ที่อาจจะใช้สำหรับการทำคำสั่งนี้
key คือ index key ที่ใช้จริงๆสำหรับการทำคำสั่งนี้
rows คือ จำนวน row ที่อ่านขึ้นมา ซึ่งพบว่าในกรณีนี้จะมีการ row ขี้นมาน้อยกว่าตอนที่อ่านขึ้นมาทั้งหมด ซึ่งก็จะช่วยให้ database ติดต่อกับ I/O น้อย ลง ก็จะช่วยให้การทำงานเร็วขึ้น

จริงๆแล้ว เนื้อหาเกี่ยวกับการใช้คำสั่ง explain ก็มีอยู่อีกพอสมควร เช่น ในกรณีที่มีการทำ join กันหลายๆ table ซึ่งในส่วนนี้จะมีการกล่าวเอาไว้ ใน manual ของ MySQL อยู่แล้ว

วันอาทิตย์ที่ 20 มิถุนายน พ.ศ. 2553

windows firewall เปิดไม่ได้

ทำยังไงดี เปิด Windows Firewall ใน Control Panel ไม่ได้ (http://www.zealzonecafe.com/board/index.php?topic=30.msg40#msg40)

เปิด service ของ Windows Fiewall ไม่ได้

ซึ่งอาจจะมีผลทำให้เรา ping หาเครื่อง ๆ นั้นไม่เจอ

วิธีแก้ไข ให้ทำการ Rebuilding the WMI Repository มีดังนี้
========================================
สำหรับ Windows XP Service Pack 2
คลิ๊ก Start, Run แล้วพิมพ์คำสั่ง

• rundll32 wbemupgd, UpgradeRepository

------------------------
สำหรับ Windows Server 2003
คลิ๊ก Start, Run แล้วพิมพ์คำสั่ง

• rundll32 wbemupgd, RepairWMISetup

------------------------
สำหรับ Windows versions อื่น ๆ
คลิ๊ก Start, Run แล้วพิมพ์คำสั่ง CMD.EXE กด Enter เข้า command prompt

พิมพ์คำสั่ง
• net stop winmgmt

เปิด Windows Explorer, เพื่อเปลี่ยนโฟลเดอร์ %windir%\System32\Wbem\Repository. (ยกตัวอย่างเช่น, %windir%\System32\Wbem\Repository_bad.). %windir% ใช้แทน path ไปยังไดเร็คทอรี่ของ Windows , ซึ่งมักจะเป็น C:\Windows.

กลับไปที่หน้าต่าง Command Prompt , แล้วพิมพ์คำสั่งด้านล่างแล้วกด Enter ทีละคำสั่ง

• net start winmgmt

• EXIT


ทีนี้กลับไปที่ Control Panel ท่านก็จะสามารถเปิด service ของ Windows Firewall ได้แล้วครับ