การสร้างไฟล์กำหนดค่าใน PHP


104

ฉันต้องการสร้างไฟล์กำหนดค่าสำหรับโปรเจ็กต์ PHP แต่ไม่แน่ใจว่าวิธีใดดีที่สุดในการทำเช่นนี้

ฉันมี 3 ความคิดจนถึงตอนนี้

1- ใช้ตัวแปร

$config['hostname'] = "localhost";
$config['dbuser'] = "dbuser";
$config['dbpassword'] = "dbpassword";
$config['dbname'] = "dbname";
$config['sitetitle'] = "sitetitle";

2- ใช้ค่าคงที่

define('DB_NAME', 'test');
define('DB_USER', 'root');
define('DB_PASSWORD', '');
define('DB_HOST', 'localhost');
define('TITLE', 'sitetitle');

3- ใช้ฐานข้อมูล

ฉันจะใช้ config ในคลาสดังนั้นฉันไม่แน่ใจว่าวิธีไหนดีที่สุดหรือมีวิธีที่ดีกว่านี้


12
4) ใช้ไฟล์ ini 5) ใช้ไฟล์ YAML 6) ใช้ไฟล์ JSON 7) ... มีหลายวิธี ... กำหนดเกณฑ์บางอย่างเพื่อตัดสินอย่างน้อยก็ไม่มี "ดีที่สุด" โดยรวม
หลอกลวง

@deceze วิธีอดอาหารคืออะไร? (memory and fast)
Ali Akbar Azizi

นี่ควรจะเป็นเรื่องที่น่าสนใจสำหรับคุณแล้ว: stackoverflow.com/questions/823352/…
อ่าน

1
ฉันใช้วิธีที่ Laravel ทำ (เมื่อไม่ใช้ Laravel นั่นคือ) ฉันสร้างคลาสที่โหลดไฟล์กำหนดค่าเฉพาะขึ้นอยู่กับชื่อโฮสต์ แล้วเรียกมันโดยใช้Config::get('key');. pastebin.com/4iTnjEuM
MisterBla

คำตอบ:


224

วิธีหนึ่งที่เรียบง่าย แต่หรูหราคือการสร้างconfig.phpไฟล์ (หรืออะไรก็ได้ที่คุณเรียกว่า) ที่ส่งคืนอาร์เรย์:

<?php

return array(
    'host' => 'localhost',
    'username' => 'root',
);

แล้ว:

$configs = include('config.php');

10
ฉันชอบวิธีนี้ด้วย - ฉันคิดว่ามันสะอาดกว่าแค่ประกาศตัวแปรในไฟล์ที่รวมอยู่และสมมติว่ามันจะอยู่ในสคริปต์ของคุณ
Colin M

วิธีคำตอบในการสร้างไฟล์กำหนดค่านี้อยู่ที่ไหน สำหรับ php มือใหม่อย่างผม?
Luka

@Luka คุณสามารถใช้ฟังก์ชันvar_export
Hasan Bayat

78

ใช้ไฟล์ INI เป็นโซลูชันที่ยืดหยุ่นและมีประสิทธิภาพ! PHP มีฟังก์ชันดั้งเดิมในการจัดการอย่างถูกต้อง ตัวอย่างเช่นสามารถสร้างไฟล์ INI ได้ดังนี้:

app.ini

[database]
db_name     = mydatabase
db_user     = myuser
db_password = mypassword

[application]
app_email = mailer@myapp.com
app_url   = myapp.com

ดังนั้นสิ่งเดียวที่คุณต้องทำคือโทร:

$ini = parse_ini_file('app.ini');

จากนั้นคุณสามารถเข้าถึงคำจำกัดความได้อย่างง่ายดายโดยใช้$iniอาร์เรย์

echo $ini['db_name'];     // mydatabase
echo $ini['db_user'];     // myuser
echo $ini['db_password']; // mypassword
echo $ini['app_email'];   // mailer@myapp.com

สำคัญ:เพื่อความปลอดภัยไฟล์ INI ต้องอยู่ในโฟลเดอร์ที่ไม่ใช่สาธารณะ


ยังปลอดภัยที่จะใช้? หากผู้ใช้เดาเส้นทางไปยังไฟล์ ini และไปที่นั่นในเบราว์เซอร์ผู้ใช้จะเห็นว่ามีอะไรอยู่ในไฟล์หรือไม่
NickGames

1
@NickGames คุณต้องวางไฟล์ไว้ในโฟลเดอร์ที่ไม่ใช่สาธารณะมิฉะนั้นคุณจะตกอยู่ในความเสี่ยงด้านความปลอดภัยอย่างร้ายแรง
Marcio Mazzucato

2
@NickGames โปรดดูความคิดเห็นที่ 1 ในเอกสารของ parse_ini_file ()
R Picheta

20
ฉันชอบแนวทางนี้ เคล็ดลับโบนัส: เปลี่ยนชื่อไฟล์เป็น app.ini.php ;<?php die(); ?>แล้วเพิ่มบรรทัดแรก ในกรณีที่ไฟล์นี้ปรากฏในโฟลเดอร์สาธารณะโดยไม่ได้ตั้งใจไฟล์นั้นจะถือว่าเป็นไฟล์ PHP และตายในบรรทัดแรก หากไฟล์ถูกอ่านด้วยparse_ini_fileมันจะถือว่าบรรทัดแรกเป็นความคิดเห็นเนื่องจากไฟล์;.
Andreas

1
หมายเหตุ: หากค่าในไฟล์ ini มีอักขระที่ไม่ใช่ตัวเลขและตัวอักษรจำเป็นต้องอยู่ในเครื่องหมายคำพูดคู่ ( ") ตัวอย่างเช่นรหัสผ่านใด ๆ ที่มีอักขระที่ไม่ใช่ตัวเลขและตัวอักษร
Key Shang

25

ผมใช้วิวัฒนาการเล็กน้อย @hugo_leonardo 's วิธีการแก้ปัญหา :

<?php

return (object) array(
    'host' => 'localhost',
    'username' => 'root',
    'pass' => 'password',
    'database' => 'db'
);

?>

สิ่งนี้ช่วยให้คุณสามารถใช้ไวยากรณ์ของวัตถุเมื่อคุณรวม php: $configs->hostแทน$configs['host'].

นอกจากนี้หากแอปของคุณมีการกำหนดค่าที่คุณต้องการในฝั่งไคลเอ็นต์ (เช่นแอป Angular) คุณสามารถให้config.phpไฟล์นี้มีการกำหนดค่าทั้งหมดของคุณ (รวมอยู่ในไฟล์เดียวแทนที่จะเป็นไฟล์เดียวสำหรับ JavaScript และอีกไฟล์หนึ่งสำหรับ PHP) เคล็ดลับคือการมีไฟล์ PHP อื่นที่จะechoเฉพาะข้อมูลฝั่งไคลเอ็นต์ (เพื่อหลีกเลี่ยงการแสดงข้อมูลที่คุณไม่ต้องการแสดงเช่นสตริงการเชื่อมต่อฐานข้อมูล) เรียกมันว่าget_app_info.php:

<?php

    $configs = include('config.php');
    echo json_encode($configs->app_info);

?>

ข้างต้นสมมติว่าคุณconfig.phpมีapp_infoพารามิเตอร์:

<?php

return (object) array(
    'host' => 'localhost',
    'username' => 'root',
    'pass' => 'password',
    'database' => 'db',
    'app_info' => array(
        'appName'=>"App Name",
        'appURL'=> "http://yourURL/#/"
    )
);

?>

ดังนั้นข้อมูลของฐานข้อมูลของคุณจะอยู่ที่ฝั่งเซิร์ฟเวอร์ แต่ข้อมูลแอปของคุณสามารถเข้าถึงได้จาก JavaScript ของคุณโดยมีตัวอย่าง$http.get('get_app_info.php').then(...);ประเภทการโทร


ทำไมต้องทำให้เป็นวัตถุ?
TheCrazyProfessor

4
การทำให้เป็นวัตถุทำให้การจัดการข้อมูลง่ายขึ้นมาก ช่วยให้ตัวอย่างเช่นรับapp_infoพารามิเตอร์ทั้งหมดไปยัง JavaScript เป็น JSON โดยมีบรรทัดขั้นต่ำของโค้ด
BoDeX

ออบเจ็กต์ยังมีผลข้างเคียงจากการถูกส่งผ่านโดยการอ้างอิงตั้งแต่ PHP 5 ซึ่งอาจเป็นหรือไม่ใช่สิ่งที่ดี อาร์เรย์ถูกส่งผ่านด้วยค่า (แต่ใช้งานเป็น COW) ดังนั้นจึงควรใช้ config อาร์เรย์แทนออบเจ็กต์ config
Mikko Rantalainen

@BoDeX ฉันชอบวิธีนี้เสมอและดูเหมือนจะเป็นแนวทางที่ได้รับความนิยมในบทความส่วนใหญ่ แต่ฉันจะเข้าถึงสิ่งนี้ผ่านชั้นเรียนได้อย่างไร ฉันอ่านในบทความด้านความปลอดภัยว่าการสร้างตัวแปรส่วนกลางไม่ใช่ความคิดที่ดีคุณจะแนะนำอย่างไร
Kevlwig

22

ตัวเลือกที่ฉันเห็นด้วยข้อดี / จุดอ่อนคือ:

กลไกตามไฟล์

สิ่งเหล่านี้ต้องการให้โค้ดของคุณดูในตำแหน่งเฉพาะเพื่อค้นหาไฟล์ ini นี่เป็นปัญหาที่ยากในการแก้ไขและเป็นปัญหาที่มักเกิดขึ้นในแอปพลิเคชัน PHP ขนาดใหญ่ อย่างไรก็ตามคุณอาจต้องแก้ปัญหาเพื่อค้นหาโค้ด PHP ที่รวม / ใช้ซ้ำในรันไทม์

วิธีการทั่วไปในการนี้คือใช้ไดเร็กทอรีสัมพัทธ์เสมอหรือค้นหาจากไดเร็กทอรีปัจจุบันขึ้นไปเพื่อค้นหาไฟล์ที่มีชื่อเฉพาะในไดเร็กทอรีฐานของแอ็พพลิเคชัน

รูปแบบไฟล์ทั่วไปที่ใช้สำหรับไฟล์กำหนดค่า ได้แก่ โค้ด PHP, ไฟล์ในรูปแบบ, JSON, XML, YAML และ PHP แบบอนุกรม

โค้ด PHP

สิ่งนี้ให้ความยืดหยุ่นอย่างมากในการแสดงโครงสร้างข้อมูลที่แตกต่างกันและ (สมมติว่ามีการประมวลผลผ่านการรวมหรือต้องการ) โค้ดที่แยกวิเคราะห์จะพร้อมใช้งานจากแคช opcode ซึ่งให้ประโยชน์ด้านประสิทธิภาพ

include_pathให้หมายถึงการสรุปสถานที่มีศักยภาพของไฟล์โดยไม่ต้องอาศัยรหัสเพิ่มเติม

ในทางกลับกันสาเหตุหลักประการหนึ่งในการแยกการกำหนดค่าออกจากรหัสคือการแยกความรับผิดชอบ มีเส้นทางสำหรับการฉีดโค้ดเพิ่มเติมลงในรันไทม์

หากการกำหนดค่าถูกสร้างขึ้นจากเครื่องมืออาจเป็นไปได้ที่จะตรวจสอบความถูกต้องของข้อมูลในเครื่องมือ แต่ไม่มีฟังก์ชันมาตรฐานในการหลบหนีข้อมูลสำหรับการฝังลงในโค้ด PHP ตามที่มีอยู่สำหรับ HTML, URLs, คำสั่ง MySQL, คำสั่งเชลล์ ... .

ข้อมูลต่อเนื่อง สิ่งนี้ค่อนข้างมีประสิทธิภาพสำหรับการกำหนดค่าจำนวนน้อย (สูงสุดประมาณ 200 รายการ) และอนุญาตให้ใช้โครงสร้างข้อมูล PHP ใด ๆ ต้องใช้รหัสเพียงเล็กน้อยในการสร้าง / แยกวิเคราะห์ไฟล์ข้อมูล (ดังนั้นคุณสามารถใช้ความพยายามในการตรวจสอบให้แน่ใจว่าไฟล์นั้นเขียนด้วยการอนุญาตที่เหมาะสมเท่านั้น)

การหลีกเลี่ยงเนื้อหาที่เขียนไปยังไฟล์จะถูกจัดการโดยอัตโนมัติ

เนื่องจากคุณสามารถทำให้เป็นอนุกรมวัตถุได้จึงสร้างโอกาสในการเรียกใช้รหัสได้ง่ายๆโดยการอ่านไฟล์การกำหนดค่า (วิธีมายากล __wakeup)

ไฟล์ที่มีโครงสร้าง

การจัดเก็บเป็นไฟล์ INI ตามที่ Marcel หรือ JSON หรือ XML แนะนำยังมี api ง่ายๆในการแมปไฟล์เข้ากับโครงสร้างข้อมูล PHP (และยกเว้น XML เพื่อหลีกเลี่ยงข้อมูลและสร้างไฟล์) ในขณะที่กำจัดการเรียกโค้ด ช่องโหว่โดยใช้ข้อมูล PHP แบบอนุกรม

จะมีลักษณะการทำงานที่คล้ายคลึงกับข้อมูลที่ทำให้เป็นอนุกรม

การจัดเก็บฐานข้อมูล

นี่เป็นข้อพิจารณาที่ดีที่สุดในกรณีที่คุณมีการกำหนดค่าจำนวนมาก แต่มีการคัดเลือกในสิ่งที่จำเป็นสำหรับงานปัจจุบัน - ฉันรู้สึกประหลาดใจที่พบว่ามีรายการข้อมูลประมาณ 150 รายการการดึงข้อมูลจากอินสแตนซ์ MySQL ภายในเครื่องทำได้เร็วกว่า ยกเลิกการกำหนดค่าเริ่มต้นของดาต้าไฟล์

OTOH ไม่ใช่สถานที่ที่ดีในการจัดเก็บข้อมูลประจำตัวที่คุณใช้เชื่อมต่อกับฐานข้อมูลของคุณ!

สภาพแวดล้อมการดำเนินการ

คุณสามารถตั้งค่าในสภาพแวดล้อมการดำเนินการที่ PHP กำลังทำงานอยู่

สิ่งนี้จะลบข้อกำหนดใด ๆ สำหรับโค้ด PHP ที่จะค้นหาในตำแหน่งเฉพาะสำหรับการกำหนดค่า OTOH ไม่ได้ปรับขนาดให้เหมาะสมกับข้อมูลจำนวนมากและยากที่จะเปลี่ยนแปลงในระดับสากลที่รันไทม์

บนไคลเอนต์

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

ในทางกลับกันสิ่งนี้มีประโยชน์มากมายสำหรับการจัดเก็บข้อมูลที่ละเอียดอ่อนซึ่งเป็นของผู้ใช้ปลายทาง - หากคุณไม่ได้จัดเก็บข้อมูลนี้ไว้บนเซิร์ฟเวอร์จะไม่สามารถถูกขโมยจากที่นั่นได้

ไดเรกทอรีเครือข่าย สถานที่ที่น่าสนใจอีกแห่งในการจัดเก็บข้อมูลการกำหนดค่าคือใน DNS / LDAP นี้จะทำงานสำหรับจำนวนเล็ก ๆ ของชิ้นเล็ก ๆ ของข้อมูล - แต่คุณไม่จำเป็นต้องติดรูปแบบปกติ 1 - พิจารณาเช่นSPF

โครงสร้างพื้นฐานรองรับการแคชการจำลองแบบและการแจกจ่าย ดังนั้นจึงทำงานได้ดีสำหรับโครงสร้างพื้นฐานขนาดใหญ่มาก

ระบบควบคุมเวอร์ชัน

การกำหนดค่าเช่นรหัสควรได้รับการจัดการและควบคุมเวอร์ชันดังนั้นการรับการกำหนดค่าโดยตรงจากระบบ VC ของคุณจึงเป็นโซลูชันที่ทำงานได้ แต่บ่อยครั้งสิ่งนี้มาพร้อมกับค่าใช้จ่ายด้านประสิทธิภาพที่สำคัญดังนั้นอาจแนะนำให้ใช้แคช


6

มันเป็นเรื่องยากที่จะจัดเก็บข้อมูลการกำหนดค่าฐานข้อมูลของคุณในฐานข้อมูล - คุณไม่คิดเหรอ?

แต่จริงๆแล้วนี่เป็นคำถามที่มีความคิดเห็นค่อนข้างมากเพราะสไตล์ใด ๆ ก็ใช้ได้จริงและทุกอย่างเป็นเรื่องของความชอบ โดยส่วนตัวแล้วฉันจะใช้ตัวแปรคอนฟิกูเรชันมากกว่าค่าคงที่ - โดยทั่วไปแล้วเพราะฉันไม่ชอบสิ่งต่างๆในพื้นที่ส่วนกลางเว้นแต่จำเป็น ไม่มีฟังก์ชันใดใน codebase ของฉันที่ควรจะสามารถเข้าถึงรหัสผ่านฐานข้อมูลของฉันได้อย่างง่ายดาย (ยกเว้นตรรกะการเชื่อมต่อฐานข้อมูลของฉัน) - ดังนั้นฉันจึงใช้มันที่นั่นแล้วก็น่าจะทำลายมัน

แก้ไข : เพื่อตอบความคิดเห็นของคุณ - ไม่มีกลไกการแยกวิเคราะห์ใดที่เร็วที่สุด (ini, json และอื่น ๆ ) แต่ก็ไม่ใช่ส่วนของแอปพลิเคชันของคุณที่คุณต้องให้ความสำคัญกับการปรับให้เหมาะสมเนื่องจากความแตกต่างของความเร็วจะ ไม่สำคัญกับไฟล์ขนาดเล็กเช่นนี้


2

กำหนดจะทำให้ค่าคงที่พร้อมใช้งานทุกที่ในคลาสของคุณโดยไม่จำเป็นต้องใช้ global ในขณะที่ตัวแปรต้องการ global ในคลาสฉันจะใช้ DEFINE แต่อีกครั้งหากพารามิเตอร์ db ควรเปลี่ยนระหว่างการทำงานของโปรแกรมคุณอาจต้องการใช้ตัวแปร


วิธี fastet ในการรัน php คืออะไร? const หรือ var?
Ali Akbar Azizi

1
@CooPer การกำหนดค่าคงที่ช้ากว่าการกำหนดตัวแปรอย่างมีนัยสำคัญ แต่การใช้มันจะเร็วกว่าเล็กน้อย เนื่องจากสิ่งเหล่านี้จะถูกใช้ในที่เดียวตัวแปรโดยรวมจะให้ประสิทธิภาพที่สูงขึ้น
Colin M

"อย่างมีนัยสำคัญ" เป็นคำที่ค่อนข้างหนักหากคุณจะดูด้วยวิธีนี้บางทีคุณควรติดต่อพวก php dev และขอให้พวกเขาลบการสนับสนุนอย่างต่อเนื่อง!
phpalix

@phpalix การกำหนดค่าคงที่อาจช้ากว่าการกำหนดตัวแปรที่มีค่าเดียวกันตั้งแต่ 10-20 เท่า ฉันบอกว่ามันสำคัญมาก อย่างไรก็ตามหากคุณใช้ค่าคงที่อย่างหนักตลอดทั้งแอปพลิเคชันของคุณมันอาจจะคุ้มค่ามาก แต่ไม่แนะนำให้สร้างค่าคงที่เพื่อใช้ครั้งเดียว
Colin M

2

หากคุณคิดว่าคุณจะใช้มากกว่า 1 db ไม่ว่าด้วยเหตุผลใดก็ตามให้ใช้ตัวแปรเพราะคุณจะสามารถเปลี่ยนพารามิเตอร์หนึ่งตัวเพื่อสลับไปยังฐานข้อมูลที่ต่างกันโดยสิ้นเชิง เช่นสำหรับการทดสอบการสำรองข้อมูลอัตโนมัติเป็นต้น


2

คุณสามารถสร้างคุณสมบัติคงที่ของแม่มดคลาส config

class Config 
{
    static $dbHost = 'localhost';
    static $dbUsername = 'user';
    static $dbPassword  = 'pass';
}

จากนั้นคุณสามารถใช้งานได้ง่าย:

Config::$dbHost  

บางครั้งในโครงการของฉันฉันใช้รูปแบบการออกแบบ SINGLETON เพื่อเข้าถึงข้อมูลการกำหนดค่า สะดวกสบายในการใช้งาน

ทำไม?

ตัวอย่างเช่นคุณมีแหล่งข้อมูล 2 แหล่งในโครงการของคุณ และคุณสามารถเลือกแม่มดของพวกเขาได้เปิดใช้งาน

  • mysql
  • json

ที่ไหนสักแห่งในไฟล์ config ที่คุณเลือก:

$dataSource = 'mysql' // or 'json'

เมื่อคุณเปลี่ยนแหล่งที่มาของแอปทั้งหมดเปลี่ยนเป็นแหล่งข้อมูลใหม่ทำงานได้ดีและไม่จำเป็นต้องเปลี่ยนรหัส

ตัวอย่าง:

กำหนดค่า:

class Config 
{
  // ....
  static $dataSource = 'mysql';
  / .....
}

คลาส Singleton:

class AppConfig
{
    private static $instance;
    private $dataSource;

    private function __construct()
    {
        $this->init();
    }

    private function init()
    {
        switch (Config::$dataSource)
        {
            case 'mysql':
                $this->dataSource = new StorageMysql();
                break;
            case 'json':
                $this->dataSource = new StorageJson();
                break;
            default:
                $this->dataSource = new StorageMysql();
        }
    }

    public static function getInstance()
    {
        if (empty(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getDataSource()
    {
        return $this->dataSource;
    }
}

... และบางแห่งในรหัสของคุณ (เช่นในชั้นบริการบางประเภท):

$container->getItemsLoader(AppConfig::getInstance()->getDataSource()) // getItemsLoader need Object of specific data source class by dependency injection

เราสามารถรับวัตถุ AppConfig จากที่ใดก็ได้ในระบบและได้รับสำเนาเดียวกันเสมอ (ขอบคุณแบบคงที่) เมธอด init () ของคลาสเรียกว่า In the constructor ซึ่งรับประกันการดำเนินการเพียงครั้งเดียว Init () body ตรวจสอบค่าของ config $ dataSource และสร้างออบเจ็กต์ใหม่ของคลาสแหล่งข้อมูลเฉพาะ ตอนนี้สคริปต์ของเราสามารถรับวัตถุและดำเนินการได้โดยไม่รู้ว่าการใช้งานเฉพาะใดมีอยู่จริง


1

โดยปกติฉันจะสร้างไฟล์ conn.php ไฟล์เดียวที่มีการเชื่อมต่อฐานข้อมูลของฉัน จากนั้นฉันรวมไฟล์นั้นในไฟล์ทั้งหมดที่ต้องการการสืบค้นฐานข้อมูล


1
ฉันรู้ แต่คุณบันทึกไฟล์ฐานข้อมูลด้วยตัวแปรหรือ const อย่างไร และทำไม?
Ali Akbar Azizi

0

นี่คือทางของฉัน

<?php

define('DEBUG',0);

define('PRODUCTION',1);



#development_mode : DEBUG / PRODUCTION

$development_mode = PRODUCTION;



#Website root path for links

$app_path = 'http://192.168.0.234/dealer/';



#User interface files path

$ui_path = 'ui/';

#Image gallery path

$gallery_path = 'ui/gallery/';


$mysqlserver = "localhost";
$mysqluser = "root";
$mysqlpass = "";
$mysqldb = "dealer_plus";

?>

มีข้อสงสัยกรุณาแสดงความคิดเห็น


3
สวัสดี! ช่วยยกตัวอย่างการใช้งานได้ไหม ขอบคุณ
Nick
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.