Petunjuk jenis PHPDoc untuk array objek?


417

Jadi, di PHPDoc kita dapat menentukan di @varatas deklarasi variabel anggota untuk mengisyaratkan pada tipenya. Kemudian sebuah IDE, misalnya. PHPEd, akan tahu jenis objek apa yang bekerja dengannya dan akan dapat memberikan wawasan kode untuk variabel itu.

<?php
  class Test
  {
    /** @var SomeObj */
    private $someObjInstance;
  }
?>

Ini berfungsi dengan baik sampai saya perlu melakukan hal yang sama ke array objek untuk bisa mendapatkan petunjuk yang tepat ketika saya beralih melalui objek-objek itu nanti.

Jadi, apakah ada cara untuk mendeklarasikan tag PHPDoc untuk menentukan bahwa variabel anggota adalah array dari SomeObjs? @vararray tidak cukup, dan @var array(SomeObj)sepertinya tidak valid, misalnya.


2
Ada beberapa referensi di blog Netbeans 6.8 dev ini bahwa IDE sekarang cukup pintar untuk menyimpulkan jenis anggota array: blogs.sun.com/netbeansphp/entry/php_templates_improved
John Carter

3
@therefromhere: tautan Anda rusak. Saya pikir URL baru adalah: blogs.oracle.com/netbeansphp/entry/php_templates_improved
DanielaWaranie

Untuk orang-orang seperti saya, lewat dan mencari jawaban: jika Anda menggunakan PHPStorm, lihat jawaban yang paling banyak dipilih: itu memiliki petunjuk khusus! stackoverflow.com/a/1763425/1356098 (ini tidak berarti itu seharusnya menjadi jawaban untuk OP, karena dia meminta PHPEd, misalnya)
Erenor Paz

Jawaban:


337

Menggunakan:

/* @var $objs Test[] */
foreach ($objs as $obj) {
    // Typehinting will occur after typing $obj->
}

saat mengetikkan variabel sebaris, dan

class A {
    /** @var Test[] */
    private $items;
}

untuk properti kelas.

Jawaban sebelumnya dari '09 ketika PHPDoc (dan IDE seperti Zend Studio dan Netbeans) tidak memiliki opsi itu:

Yang terbaik yang bisa Anda lakukan adalah mengatakan,

foreach ($Objs as $Obj)
{
    /* @var $Obj Test */
    // You should be able to get hinting after the preceding line if you type $Obj->
}

Saya sering melakukan itu di Zend Studio. Tidak tahu tentang editor lain, tetapi itu harus berhasil.


3
Ini masuk akal tetapi tidak berhasil untuk PHPEd 5.2. Satu-satunya hal yang saya dapat lakukan dengan bekerja adalah foreach ($ Objs sebagai / ** @var Test * / $ Obj), yang sangat jelek. :(
Artem Russakovskii

14
Catatan di Netbeans 7, sepertinya penting Anda hanya memiliki satu tanda bintang - /** @var $Obj Test */tidak berfungsi.
contrebis

3
@contrebis: "@var" adalah tag docblock yang valid. Jadi, bahkan jika IDE Anda tidak mendukungnya dalam docblock "/ ** ... /" dan hanya mendukung "@var" di "/ ... * /" - tolong, tolong jangan ubah docblock yang benar. Ajukan masalah ke pelacak bug IDE Anda untuk membuat IDE Anda memenuhi standar. Bayangkan tim pengembangan Anda / pengembang eksternal / komunitas menggunakan IDE yang berbeda. Pertahankan apa adanya dan bersiaplah untuk masa depan.
DanielaWaranie

181
Pastikan Anda melihat di bawah! Saya hampir tidak menggulir ke bawah - akan menjadi KESALAHAN BESAR !!! Banyak IDE AKAN mendukung sintaks yang lebih baik! (petunjuk: @var Object[] $objectsmengatakan bahwa "$ object" adalah array dari instance Object.)
Thom Porter

10
/** @var TYPE $variable_name */adalah sintaks yang benar; jangan membalik urutan jenis dan nama variabel (seperti yang disarankan sebelumnya dalam komentar) karena tidak akan berfungsi dalam semua kasus.
srcspider

893

Dalam IDE PhpStorm dari JetBrains, Anda dapat menggunakan /** @var SomeObj[] */, misalnya:

/**
 * @return SomeObj[]
 */
function getSomeObjects() {...}

The dokumentasi phpdoc merekomendasikan metode ini:

ditentukan mengandung satu jenis, definisi Jenis menginformasikan pembaca jenis setiap elemen array. Hanya satu Jenis yang diharapkan sebagai elemen untuk array yang diberikan.

Contoh: @return int[]


10
Saya baru saja mengunduh dan telah menggunakan phpstorm selama seminggu terakhir. Mengalahkan Aptana (yang bagus untuk bebas). Inilah yang saya cari. Sebenarnya, ini adalah cara yang sama seperti Anda melakukannya untuk JavaScript, saya seharusnya sudah menebaknya
Juan Mendes

3
Terima kasih sobat! Inilah yang saya cari. PHPStorm luar biasa.
Erik Schierboom

5
Ini tidak berfungsi di Netbeans, saya kecewa. Jetbrains membuat beberapa alat yang sangat bagus.
Keyo

10
@fishbone @Keyo ini berfungsi di Netbeans sekarang (setidaknya pada 7.1 nightly build, mungkin sebelumnya), meskipun sepertinya Anda perlu menggunakan variabel sementara (bug?). Mengisyaratkan untuk foreach(getSomeObjects() as $obj)tidak berhasil, tetapi berhasil$objs = getSomeObjects(); foreach($objs as $obj)
John Carter

2
Akan menyenangkan untuk memiliki @var Obj[string]array asosiatif.
donquixote

59

Petunjuk Netbeans:

Anda mendapatkan penyelesaian kode untuk $users[0]->dan untuk $this->berbagai kelas Pengguna.

/**
 * @var User[]
 */
var $users = array();

Anda juga dapat melihat jenis array dalam daftar anggota kelas ketika Anda selesai $this->...


4
bekerja di PhpStorm 9 EAP juga: / ** * @var UserInterface [] * / var $ users = []; // Array of Objs mengimplementasikan Interface
Ronan

Saya sudah mencobanya di NetBeans IDE 8.0.2, tetapi saya mendapat saran dari kelas yang saat ini saya
ikuti

juga bekerja di Eclipse 4.6.3 (idk apa yang didukung versi diperkenalkan, tetapi berfungsi, dan apa yang saya gunakan sekarang)
hanshenrik

Sayangnya ini tidak berfungsi setelah menggunakan array_pop()atau fungsi serupa karena beberapa alasan. Tampaknya Netbeans tidak menyadari fungsi-fungsi itu mengembalikan elemen tunggal dari array input.
William W

29

Untuk menentukan variabel adalah array objek:

$needles = getAllNeedles();
/* @var $needles Needle[] */
$needles[1]->...                        //codehinting works

Ini bekerja di Netbeans 7.2 (Saya menggunakannya)

Bekerja juga dengan:

$needles = getAllNeedles();
/* @var $needles Needle[] */
foreach ($needles as $needle) {
    $needle->...                        //codehinting works
}

Oleh karena itu penggunaan deklarasi di dalam foreachtidak diperlukan.


2
Solusi ini lebih bersih daripada jawaban yang diterima dalam pandangan saya, karena Anda dapat menggunakan foreach beberapa kali dan tipe isyarat akan terus bekerja tanpa /* @var $Obj Test */anotasi baru setiap kali.
Henry

Saya melihat dua masalah di sini: 1. phpdoc yang tepat dimulai dengan /** 2. Format yang benar adalah@var <data-type> <variable-name>
Christian

@Christian 1: pertanyaan utama bukan phpdoc tetapi typehinting 2: format yang benar tidak seperti yang Anda katakan, bahkan menurut jawaban lain. Bahkan, saya melihat 2 masalah dengan komentar Anda, dan saya ingin tahu mengapa Anda tidak membuat jawaban Anda sendiri dengan format yang benar
Highmastdon

1. Typehinting berfungsi dengan phpdoc ... jika Anda tidak menggunakan docblock, IDE Anda tidak akan mencoba menebak apa yang Anda tulis dalam beberapa komentar acak. 2. Format yang benar, seperti beberapa jawaban lain juga katakan adalah apa yang saya sebutkan di atas; tipe data sebelum nama variabel . 3. Saya tidak menulis jawaban lain karena pertanyaannya tidak membutuhkan jawaban lain dan saya lebih suka tidak mengedit kode Anda.
Christian

24

PSR-5: PHPDoc mengusulkan bentuk notasi gaya Generics.

Sintaksis

Type[]
Type<Type>
Type<Type[, Type]...>
Type<Type[|Type]...>

Nilai dalam Koleksi MUNGKIN bahkan menjadi array lain dan bahkan Koleksi lain.

Type<Type<Type>>
Type<Type<Type[, Type]...>>
Type<Type<Type[|Type]...>>

Contohnya

<?php

$x = [new Name()];
/* @var $x Name[] */

$y = new Collection([new Name()]);
/* @var $y Collection<Name> */

$a = new Collection(); 
$a[] = new Model_User(); 
$a->resetChanges(); 
$a[0]->name = "George"; 
$a->echoChanges();
/* @var $a Collection<Model_User> */

Catatan: Jika Anda mengharapkan IDE untuk melakukan bantuan kode, maka itu adalah pertanyaan lain tentang apakah IDE mendukung notasi koleksi gaya generik PHPDoc.

Dari jawaban saya untuk pertanyaan ini .



11

Saya lebih suka membaca dan menulis kode bersih - sebagaimana diuraikan dalam "Kode Bersih" oleh Robert C. Martin. Saat mengikuti kredo Anda, Anda tidak perlu meminta pengembang (sebagai pengguna API Anda) untuk mengetahui struktur (internal) array Anda.

Pengguna API dapat bertanya: Apakah itu array dengan satu dimensi saja? Apakah objek tersebar di semua tingkatan array multi dimensi? Berapa banyak loop bersarang (foreach, dll.) Yang saya perlukan untuk mengakses semua objek? Jenis objek apa yang "disimpan" dalam array itu?

Saat Anda menguraikan Anda ingin menggunakan array itu (yang berisi objek) sebagai array satu dimensi.

Seperti yang dijelaskan oleh Nishi, Anda dapat menggunakan:

/**
 * @return SomeObj[]
 */

untuk itu.

Tetapi sekali lagi: waspada - ini bukan notasi docblock standar. Notasi ini diperkenalkan oleh beberapa produsen IDE.

Oke, oke, sebagai pengembang Anda tahu bahwa "[]" terikat ke sebuah array di PHP. Tapi apa arti "sesuatu []" dalam konteks PHP normal? "[]" berarti: membuat elemen baru di dalam "sesuatu". Elemen baru bisa menjadi segalanya. Tetapi apa yang ingin Anda ungkapkan adalah: array objek dengan tipe yang sama dan tipe tepat. Seperti yang Anda lihat, produsen IDE memperkenalkan konteks baru. Konteks baru yang harus Anda pelajari. Konteks baru yang harus dipelajari oleh pengembang PHP lain (untuk memahami dokumen Anda). Gaya buruk (!).

Karena array Anda memiliki satu dimensi, Anda mungkin ingin memanggil "array objek" sebagai "daftar". Ketahuilah bahwa "daftar" memiliki arti yang sangat istimewa dalam bahasa pemrograman lain. Akan lebih baik jika menyebutnya "koleksi" misalnya.

Ingat: Anda menggunakan bahasa pemrograman yang memungkinkan Anda semua opsi OOP. Gunakan kelas sebagai ganti array dan buat kelas Anda dapat dilalui seperti array. Misalnya:

class orderCollection implements ArrayIterator

Atau jika Anda ingin menyimpan objek internal pada level yang berbeda dalam array multi dimensi / struktur objek:

class orderCollection implements RecursiveArrayIterator

Solusi ini menggantikan array Anda dengan objek tipe "orderCollection", tetapi sejauh ini tidak memungkinkan penyelesaian kode di dalam IDE Anda. Baik. Langkah berikutnya:

Menerapkan metode yang diperkenalkan oleh antarmuka dengan docblocks - khususnya:

/**
 * [...]
 * @return Order
 */
orderCollection::current()

/**
 * [...]
 * @return integer E.g. database identifier of the order
 */
orderCollection::key()

/**
 * [...]
 * @return Order
 */
orderCollection::offsetGet()

Jangan lupa untuk menggunakan petunjuk tipe untuk:

orderCollection::append(Order $order)
orderCollection::offsetSet(Order $order)

Solusi ini berhenti memperkenalkan banyak:

/** @var $key ... */
/** @var $value ... */

seluruh file kode Anda (misalnya dalam loop), seperti yang dikonfirmasi Zahymaka dengan jawabannya. Pengguna API Anda tidak dipaksa untuk memperkenalkan docblock itu, untuk memiliki penyelesaian kode. Untuk memiliki @return hanya pada satu tempat mengurangi redundansi (@var) semut mungkin. Taburkan "docBlocks with @var" akan membuat kode Anda lebih mudah dibaca.

Akhirnya kamu sudah selesai. Terlihat sulit untuk dicapai? Sepertinya mengambil palu untuk memecahkan kacang? Tidak benar-benar, karena Anda terbiasa dengan antarmuka itu dan dengan kode bersih. Ingat: kode sumber Anda ditulis sekali / banyak dibaca.

Jika penyelesaian kode IDE Anda tidak berfungsi dengan pendekatan ini, beralihlah ke pendekatan yang lebih baik (mis. IntelliJ IDEA, PhpStorm, Netbeans) atau ajukan permintaan fitur pada pelacak masalah produsen IDE Anda.

Terima kasih kepada Christian Weiss (dari Jerman) karena menjadi pelatih saya dan karena mengajari saya hal-hal hebat. PS: Temui saya dan dia di XING.


ini terlihat seperti cara yang "benar", tetapi saya tidak bisa menggunakannya dengan Netbeans. Membuat sedikit contoh: imgur.com/fJ9Qsro
fehrlich

2
Mungkin pada tahun 2012 ini adalah "bukan standar", tapi sekarang ini digambarkan sebagai fungsi built-in phpDoc.
Wirone

@ Wirone sepertinya phpDocumentor menambahkan ini ke manualnya sebagai reaksi terhadap produsen ide. Bahkan jika Anda memiliki dukungan alat yang luas itu tidak berarti bahwa itu adalah praktik terbaik. Itu mulai mendapatkan SomeObj [] tersebar di lebih banyak dan lebih banyak proyek, mirip dengan mengharuskan, require_once, sertakan dan include_once lakukan bertahun-tahun yang lalu. Dengan autoloading, tampilan pernyataan itu turun di bawah 5%. Semoga SomeObj [] turun ke tingkat yang sama dalam 2 tahun ke depan mendukung pendekatan di atas.
DanielaWaranie

1
Saya tidak mengerti mengapa? Ini notasi yang sangat sederhana dan jelas. Ketika Anda melihat SomeObj[]Anda tahu itu adalah array dua dimensi SomeObjcontoh dan kemudian Anda tahu apa yang harus dilakukan dengannya. Saya tidak berpikir itu tidak mengikuti kredo "kode bersih".
Wirone

Ini seharusnya jawabannya. Namun, tidak semua pendekatan dukungan IDE @return <className>untuk current()dan semua orang. PhpStorm mendukung sehingga sangat membantu saya. Terima kasih sobat!
Pavel

5

Gunakan array[type]di Zend Studio.

Di Zend Studio, array[MyClass]atau array[int]atau bahkan array[array[MyClass]]bekerja dengan baik.


5

Di NetBeans 7.0 (mungkin lebih rendah juga) Anda bisa mendeklarasikan tipe pengembalian "array with Text objects" sama seperti @return Textdan petunjuk kode akan berfungsi:

Sunting: memperbarui contoh dengan saran @Bob Fanger

/**
 * get all Tests
 *
 * @return Test|Array $tests
 */
public function getAllTexts(){
    return array(new Test(), new Test());
}

dan gunakan saja:

$tests =  $controller->getAllTests();
//$tests->         //codehinting works!
//$tests[0]->      //codehinting works!

foreach($tests as $text){
    //$test->      //codehinting works!
}

Itu tidak sempurna tetapi lebih baik daripada hanya membiarkannya hanya "dicampur", yang tidak membawa nilai.

CONS adalah Anda diizinkan untuk menginjak array sebagai Obyek Teks yang akan melempar kesalahan.


1
Saya menggunakan "@return array | Test Some description." yang memicu perilaku yang sama tetapi sedikit lebih jelas.
Bob Fanger

1
Ini adalah solusi , bukan solusi. Apa yang Anda katakan di sini adalah "fungsi ini dapat mengembalikan objek bertipe 'Test', ATAU sebuah array". Namun secara teknis tidak memberi tahu Anda apa pun tentang apa yang mungkin ada dalam array.
Byson

5

Seperti yang disebutkan DanielaWaranie dalam jawabannya - ada cara untuk menentukan jenis $ item ketika Anda mengulangi lebih dari $ item dalam $ collectionObject: Tambahkan @return MyEntitiesClassNameke current()dan sisa dari Iteratordan ArrayAccess-metode yang mengembalikan nilai.

Ledakan! Tidak perlu di /** @var SomeObj[] $collectionObj */atas foreach, dan berfungsi baik dengan objek koleksi, tidak perlu mengembalikan koleksi dengan metode khusus yang dijelaskan sebagai @return SomeObj[].

Saya kira tidak semua IDE mendukungnya tetapi berfungsi dengan baik di PhpStorm, yang membuat saya lebih bahagia.

Contoh:

class MyCollection implements Countable, Iterator, ArrayAccess {

    /**
     * @return User
     */
    public function current() {
        return $this->items[$this->cursor];
    }

    //... implement rest of the required `interface` methods and your custom
}

Apa yang berguna saya akan menambahkan memposting jawaban ini

Dalam kasus saya current()dan sisa interface-metode yang diterapkan di Abstractkelas -koleksi dan saya tidak tahu apa jenis entitas yang akhirnya akan disimpan dalam koleksi.

Jadi di sini adalah triknya: Jangan tentukan tipe return di kelas abstrak, alih-alih gunakan instuksi PhpDoc @methoddalam deskripsi kelas koleksi tertentu.

Contoh:

class User {

    function printLogin() {
        echo $this->login;
    }

}

abstract class MyCollection implements Countable, Iterator, ArrayAccess {

    protected $items = [];

    public function current() {
        return $this->items[$this->cursor];
    }

    //... implement rest of the required `interface` methods and your custom
    //... abstract methods which will be shared among child-classes
}

/**
 * @method User current()
 * ...rest of methods (for ArrayAccess) if needed
 */
class UserCollection extends MyCollection {

    function add(User $user) {
        $this->items[] = $user;
    }

    // User collection specific methods...

}

Sekarang, penggunaan kelas:

$collection = new UserCollection();
$collection->add(new User(1));
$collection->add(new User(2));
$collection->add(new User(3));

foreach ($collection as $user) {
    // IDE should `recognize` method `printLogin()` here!
    $user->printLogin();
}

Sekali lagi: Saya kira tidak semua IDE mendukungnya, tetapi PhpStorm mendukungnya. Coba milikmu, kirim komentar hasilnya!


Voucher untuk mendorong sejauh itu, tapi sayangnya saya masih bisa menyelesaikan sendiri untuk mengkhususkan koleksi untuk menggantikan tipe generik java lama .... yuck '
Sebas

Terima kasih. Bagaimana Anda bisa mengetikkan metode statis?
Yevgeniy Afanasyev

3

Saya tahu saya terlambat ke pesta, tapi saya sudah mengerjakan masalah ini baru-baru ini. Saya harap seseorang melihat ini karena jawaban yang diterima, meskipun benar, bukan cara terbaik Anda bisa melakukan ini. Setidaknya tidak di PHPStorm, saya belum menguji NetBeans.

Cara terbaik melibatkan perluasan kelas ArrayIterator daripada menggunakan tipe array asli. Ini memungkinkan Anda untuk mengetikkan petunjuk di tingkat kelas daripada di tingkat contoh, yang berarti Anda hanya perlu PHPDoc sekali, tidak di seluruh kode Anda (yang tidak hanya berantakan dan melanggar KERING, tetapi juga bisa bermasalah ketika datang ke refactoring - PHPStorm memiliki kebiasaan melewatkan PHPDoc saat refactoring)

Lihat kode di bawah ini:

class MyObj
{
    private $val;
    public function __construct($val) { $this->val = $val; }
    public function getter() { return $this->val; }
}

/**
 * @method MyObj current()
 */
class MyObjCollection extends ArrayIterator
{
    public function __construct(Array $array = [])
    {
        foreach($array as $object)
        {
            if(!is_a($object, MyObj::class))
            {
                throw new Exception('Invalid object passed to ' . __METHOD__ . ', expected type ' . MyObj::class);
            }
        }
        parent::__construct($array);
    }

    public function echoContents()
    {
        foreach($this as $key => $myObj)
        {
            echo $key . ': ' . $myObj->getter() . '<br>';
        }
    }
}

$myObjCollection = new MyObjCollection([
    new MyObj(1),
    new MyObj('foo'),
    new MyObj('blah'),
    new MyObj(23),
    new MyObj(array())
]);

$myObjCollection->echoContents();

Kuncinya di sini adalah PHPDoc @method MyObj current()mengesampingkan jenis kembali yang diwarisi dari ArrayIterator (yaitu mixed). Dimasukkannya PHPDoc ini berarti bahwa ketika kita beralih menggunakan properti kelas foreach($this as $myObj), kita kemudian mendapatkan penyelesaian kode ketika merujuk ke variabel$myObj->...

Bagi saya, ini adalah cara paling rapi untuk mencapai ini (setidaknya sampai PHP memperkenalkan Typed Array, jika mereka melakukannya), seperti yang kita nyatakan tipe iterator di kelas iterable, bukan pada contoh kelas yang tersebar di seluruh kode.

Saya belum menunjukkan di sini solusi lengkap untuk memperluas ArrayIterator, jadi jika Anda menggunakan teknik ini, Anda mungkin juga ingin:

  • Sertakan PHPDoc tingkat kelas lainnya sesuai kebutuhan, untuk metode seperti offsetGet($index)dannext()
  • Pindahkan cek kewarasan is_a($object, MyObj::class)dari konstruktor ke metode pribadi
  • Sebut pemeriksaan kewarasan ini (sekarang pribadi) dari penggantian metode seperti offsetSet($index, $newval)danappend($value)

Solusi yang sangat bagus dan bersih! :)
Marko Šutija

2

Masalahnya adalah bahwa @varhanya dapat menunjukkan tipe tunggal - Tidak mengandung formula yang kompleks. Jika Anda memiliki sintaks untuk "array Foo", mengapa berhenti di situ dan tidak menambahkan sintaks untuk "array array, yang berisi 2 Foo dan tiga Bar"? Saya mengerti bahwa daftar elemen mungkin lebih umum dari itu, tetapi ini merupakan lereng yang licin.

Secara pribadi, saya pernah beberapa kali @var Foo[]menandakan "array Foo", tetapi tidak didukung oleh IDE.


5
Salah satu hal yang saya sukai dari C / C ++ adalah ia benar-benar melacak tipe ke level ini. Itu akan menjadi lereng yang sangat menyenangkan untuk dilepas.
Brilliand

2
Didukung oleh Netbeans 7.2 (setidaknya itulah versi yang saya gunakan), tetapi dengan ajustment sedikit yaitu: /* @var $foo Foo[] */. Tulis saja jawaban di bawah tentang itu. Ini juga dapat digunakan di dalam foreach(){}loop
Highmastdon


-5

Saya telah menemukan sesuatu yang berfungsi, itu bisa menyelamatkan nyawa!

private $userList = array();
$userList = User::fetchAll(); // now $userList is an array of User objects
foreach ($userList as $user) {
   $user instanceof User;
   echo $user->getName();
}

11
Satu-satunya masalah adalah bahwa memperkenalkan kode tambahan untuk dieksekusi, yang murni digunakan oleh IDE Anda saja. Jauh lebih baik untuk menentukan jenis petunjuk di dalam komentar saja.
Ben Rowe

1
Wow ini bekerja dengan baik. Anda akan berakhir dengan kode tambahan tetapi tampaknya tidak berbahaya. Saya akan mulai melakukan: $ x instanceof Y; // typehint
Igor Nadj

3
Beralih ke IDE yang memberi Anda penyelesaian kode berdasarkan docblock atau inspeksi. Jika Anda tidak ingin mengganti file IDE Anda, permintaan fitur pada pelacak isu IDE Anda.
DanielaWaranie

1
Jika ini melempar pengecualian jika jenisnya tidak benar, ini dapat berguna untuk memeriksa jenis runtime. Jika ...
lilbyrdie
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.