Saturday, July 13, 2019

PHP 7.4.x, dan Kebiasaan-Kebiasaan yang Perlu Anda Ubah

Logo PHP
Logo PHP.

Selalu ada hal-hal yang menarik untuk diceritakan dari sejarah bahasa pemrograman, salah satunya adalah bahasa pemrograman PHP yang hingga saat ini telah digunakan oleh 79% dari situs web di seluruh dunia. Terdapat berbagai kritik negatif mengenai keberadaannya, beberapa di antaranya adalah karena inkonsistensi hasil yang diberikan ketika sebuah data dibandingkan atau disatukan dengan data yang lain, juga mengenai keputusan penamaan fungsi yang sangat tidak terpola. Tapi di luar dari semua itu, Rasmus Lerdorf sendiri mengatakan bahwa beliau tidak pernah merencanakan PHP. Semuanya terjadi begitu saja.

“Well, I didn’t plan PHP. I think in terms of solving problems, not in terms of software projects. I actually hate programming, but I love solving problems.”

Dengan proyek pengembangan perangkat lunak yang tidak terencana seperti itu tentu saja akan membawa kita pada kebiasaan-kebiasaan buruk yang terbentuk bukan karena kita yang tidak disiplin akan tetapi karena tidak adanya aturan yang jelas mengenai kaidah-kaidah pemrograman. Tidak ada aturan yang pasti apakah 123 == '123x' akan memberikan hasil yang sama dengan '123' == '123x'. Bahasa sederhananya: PHP itu terlalu bebas!

Sejak rilis PHP 7.0.0 hingga sekarang, telah begitu banyak perubahan yang terjadi di dalam ekosistem PHP. Dan Saya akui kondisinya makin membaik. Kecepatan eksekusinya meningkat drastis. Masih belum bisa dikatakan sempurna memang, namun sampai sekarang fitur-fitur baru dan perbaikan-perbaikan masalah di sana-sini masih terus dilakukan. Melalui artikel ini, Saya ingin membahas beberapa hal terkait dengan cara Anda menulis kode PHP di masa lalu. Karena PHP versi 7.0.0 sudah tidak sama lagi dengan PHP versi yang dulu. Meskipun kode lama Anda masih dapat bekerja di versi PHP yang baru, akan tetapi tidak ada salahnya jika Anda mengikuti beberapa saran Saya untuk mulai membiasakan diri menulis kode PHP dengan lebih baik lagi karena jujur saja Saya tidak suka membaca kode-kode PHP yang kalian tulis!

Gunakan Sintaks Jajaran Pendek

Fitur ini sebenarnya sudah ada sejak PHP 5.4.0 bersamaan dengan hadirnya fitur Trait. Selain dapat meringkas kode dan memperkecil ukuran berkas, sintaks ini juga akan sangat serasi ketika dipadukan dengan perintah peletakan dan pengambilan data jajaran:

$array = ['A', 'B', 0, true];

$array[0] = 'Z';

echo $array[0];

Nilai Alternatif

Dahulu kita menggunakan cara-cara seperti ini untuk menentukan nilai alternatif ketika data yang diminta tidak ada:

$var = isset($array['var']) ? $array['var'] : 1;
$var = array_key_exists('var', $array) ? $array['var'] : 1;

Namun sejak kemunculan operator penggabungan null pada PHP 7.0.0, sekarang kita bisa dengan sangat mudah membuat deretan nilai alternatif tanpa harus melalui perintah isset berkali-kali:

$var = $array['var'] ?? 1;
$var = $foo['var'] ?? $bar['var'] ?? $baz ?? 1;

Argumen Fungsi

func_get_arg(), func_get_args(), dan func_num_args() adalah fungsi-fungsi yang paling sering kita gunakan untuk mendapatkan data argumen dari sebuah fungsi. Cara penggunaannya adalah seperti berikut:

function image($image, $x = null, $y = null, $w = null, $h = null) {
    // `image('.\path\to\image.jpg', $w, $h)`
    if (func_num_args() < 4) {
        $w = $x;
        $h = $y;
        return resize($image, $w, $h);
    }
    // `image('.\path\to\image.jpg', $x, $y, $w, $h)`
    return crop($image, $x, $y, $w, $h);
}

Perhitungan-perhitungan argumen fungsi seperti ini biasanya dibuat untuk mempermudah pengguna dalam menggunakan fungsi utama, yaitu dengan cara mengizinkan mereka untuk melompati beberapa variabel yang sifatnya opsional. Contoh lain dapat Anda temukan juga pada cara kerja fungsi substr().

Beberapa orang menganggap fitur-fitur tersebut (fungsi func_get_arg(), func_get_args(), dan func_num_args()) sebagai fitur yang buruk dan tidak logis karena fungsi tidak seharusnya memiliki akses kepada pengurai sintaks PHP itu sendiri. Kelemahan ini juga tampak pada fungsi get_called_class() dan get_parent_class(), yang sekarang sudah mengalami perkembangan yang lebih baik dengan hadirnya konstanta *::class pada PHP 5.5, sehingga kita tidak perlu lagi menggunakan fitur-fitur tersebut.

Untuk membuat peraturan kondisi berdasarkan argumen fungsi menjadi tampak lebih nyaman untuk dibaca, kita bisa menggunakan fitur pembongkar argumen yang hadir pada PHP 5.6:

function image(...$args) {
    // `image('.\path\to\image.jpg', $w, $h)`
    if (count($args) < 4) {
        return resize(...$args);
    }
    // `image('.\path\to\image.jpg', $x, $y, $w, $h)`
    return crop(...$args);
}

Dan untuk mendapatkan nama kelas yang sedang dieksekusi, kita bisa menggunakan konstanta *::class atau __CLASS__ seperti ini:

class Foo {

    public function getClassName1() {
        return static::class;
    }

    public function getClassName2() {
        return __CLASS__;
    }

}

Oya! Sebagai informasi tambahan saja, pada JavaScript kita bisa menuliskan arguments begitu saja untuk mendapatkan daftar argumen fungsi yang ada. Jadi, di sini arguments berlaku sebagai token keyword yang bekerja sebagaimana token if, var, function, dan kawan-kawan:

function image() {
    if (arguments.length < 4) {
        return resize.apply({}, arguments);
    }
    return crop.apply({}, arguments);
}

Fitur Pembongkaran dan Pengepakan Argumen

Kasus ini hampir sama dengan kasus sebelumnya, hanya saja di sini Saya lebih cenderung kepada fitur sebaliknya yaitu fitur pengepakan argumen. Dengan fitur ini, kita tidak perlu lagi menggunakan fungsi-fungsi yang biasanya memiliki pola penamaan *_array seperti call_user_func_array() dan preg_replace_callback_array(). Karena kita bisa meletakkan data jajaran sebagai deret argumen fungsi dengan cara seperti ini:

$args = ['A', 0, true];

call_user_func('foo', ...$args); // Sama dengan `call_user_func('foo', $args[0], $args[1], $args[2])`

Operator Sebar pada Jajaran

Fitur ini dapat digunakan untuk mengeliminasi fungsi array_merge(). Cara penggunaannya adalah seperti ini:

$array = ['A', 'B', 'C'];

return [0, 1, 2, ...$array]; // `[0, 1, 2, 'A', 'B', 'C']`

Selain dari itu, fitur ini juga memiliki satu kelebihan lagi yaitu dapat diletakkan di antara data jajaran. Jadi tidak harus berada di akhir jajaran:

return [0, 1, 2, ...$array, true]; // `[0, 1, 2, 'A', 'B', 'C', true]`

Perintah-perintah yang kompatibel untuk digunakan pada PHP dengan versi yang lebih lama adalah sebagai berikut:

return array_merge([0, 1, 2], $array);

return [0, 1, 2] + $array;
$a = [0, 1, 2, true];
$b = array_pop($a);

return array_merge($a, $array, [$b]);

Deklarasi Tipe pada Argumen dan Pengembalian Fungsi

Dengan menggunakan fitur ini, selain dapat menampilkan pesan kesalahan ketika masukan data yang diberikan ke dalam argumen fungsi tidak sesuai dengan jenis data yang diinginkan, fitur ini juga dapat mempermudah pembaca kode sumber untuk memahami apa maksud dan penggunaan fungsi tersebut tanpa harus membaca dokumentasi secara lengkap:

interface Get {
    public function chunk(array $data, int $i): array {}
    public function files(string $path, string $x): Pages {}
    public function __toString(): string {}
}

Pada bagian __toString() sebenarnya agak berlebihan. Kode di atas hanya sebagai contoh saja.

Konversi Penamaan

Ada baiknya untuk mengikuti kaidah-kaidah tertentu dalam menuliskan nama konstanta, variabel, kelas dan fungsi seperti bagaimana cara memisahkan kata-kata pada deret nama, apakah dengan membedakan besar dan kecilnya huruf, atau dengan menggunakan karakter garis bawah seperti ini:

$foo;
$foobar;
$foobarbaz;
$foo;
$fooBar;
$fooBarBaz;
$Foo;
$FooBar;
$FooBarBaz;
$foo;
$foo_bar;
$foo_bar_baz;

Kita juga perlu menentukan bagaimana urutan prioritas pada masing-masing sub-nama. Apakah ingin mengaturnya dari kiri ke kanan untuk menciptakan kesan hirarki, atau dengan cara mengaturnya dari kanan ke kiri untuk menciptakan kesan kalimat yang dapat dibaca secara alami:

function button(): string {}
function button_primary(): string {}
function button_primary_large(): string {}
function button(): string {}
function buttonPrimary(): string {}
function buttonPrimaryLarge(): string {}
namespace {
    function button(): string {}
}

namespace button {
    function primary(): string {}
}

namespace button\primary {
    function large(): string {}
}
function button(): string {}
function primaryButton(): string {}
function largePrimaryButton(): string {}

Pastikan untuk mengikuti pola-pola tersebut secara konsisten hingga proyek Anda selesai sehingga kode Anda akan lebih mudah untuk diurus di kemudian hari. Pola yang konsisten juga dapat mempermudah Anda dan orang lain untuk melakukan pencarian nama konstanta, variabel, kelas dan fungsi tertentu melalui perintah Find & Replace karena konsep penamaan yang sudah terpola.

Hemat dalam Menggunakan Variabel

Dahulu kita tidak bisa mengambil satu item dari string yang dipisahkan oleh fungsi explode() secara langsung karena cara kerja pengurai token PHP yang masih sangat terbatas. Jadilah kita menggunakan ekstra variabel untuk mengatasi masalah tersebut:

$tags = explode(', ', 'Foo, Bar, Baz');

echo $tags[0];

Tapi masalah tersebut sudah berhasil diatasi sejak PHP versi 5.4, sehingga kita bisa mengambil item tertentu secara langsung dari pengembalian fungsi explode() seperti ini:

echo explode(', ', 'Foo, Bar, Baz')[0];

Ringkas juga pemanggilan metode-metode kelas atau data-data dari pengembalian fungsi jika memang memungkinkan dan tidak merusak keterbacaan:

echo (new Page('.\path\to\page.md'))->get('title');
echo q2o('?a=b&c=d&e=f')['c'] ?? false;

Keterbacaan PHP di dalam Berkas HTML

Usahakan untuk menghindari kebiasaan mencampurkan antara kode PHP sebagai program dengan kode PHP sebagai bagian dari kode HTML seperti ini:

<h2>
  <?php if ($page->link) { ?>
  <a href="<?php echo $page->link; ?>" target="_blank">
    <?php echo $page->title; ?>
  </a>
  <?php } else if ($page->url) { ?>
  <a href="<?php echo $page->url; ?>">
    <?php echo $page->title; ?>
  </a>
  <?php } else { ?>
  <span>
    <?php echo $page->title; ?>
  </span>
  <?php } ?>
</h2>

PHP pada dasarnya memiliki peran ganda, selain sebagai scripting language juga sebagai templating language, yang berarti bahwa kode PHP dapat disatukan dengan kode HTML. Jika yang ingin Anda tuliskan adalah kode untuk menampilkan data pada HTML, maka sintaks alternatif berikut ini akan lebih nyaman untuk dibaca, terutama oleh orang-orang yang ingin membuka berkas tersebut melalui editor kode berwarna:

<h2>
  <?php if ($page->link): ?>
  <a href="<?php echo $page->link; ?>" target="_blank">
    <?php echo $page->title; ?>
  </a>
  <?php elseif ($page->url): ?>
  <a href="<?php echo $page->url; ?>">
    <?php echo $page->title; ?>
  </a>
  <?php else: ?>
  <span>
    <?php echo $page->title; ?>
  </span>
  <?php endif; ?>
</h2>

Jangan juga mencampurkan kode PHP sebagai program dengan kode HTML mentah seperti ini, ini sangat kacau!

<?php

class Article {

    public function getHTML(string $source) {
        $data = fetch($source)->then(function($response) {
            return $response->json;
        })->then(function($json) {

?>
<article>
<h2><?php echo $json->title; ?></h2>
<div><?php echo $json->content; ?></div>
</article>
<?php

        })->catch(function() {
?>
<article>
<h2>Error</h2>
<div>Not found.</div>
</article>
<?php

        });
    }

}

Coba gunakan echo, atau return sebagai alternatif yang lebih baik. Atau pelajari konsep MVC.

Jangan Takut Menggunakan “Magic Methods”

Metode ajaib atau magic method dalam PHP dikenal sebagai metode yang berpotensi memperlambat proses eksekusi program PHP Anda karena dalam satu kali pemanggilan metode kelas yang dilengkapi dengan metode-metode ajaib, sebuah kelas perlu untuk mencari metode yang dimaksud terlebih dahulu untuk dieksekusi sebelum pada akhirnya kelas tersebut mengeksekusi metode ajaib yang diberikan ketika metode yang dimaksud ternyata tidak ditemukan. Tapi sekarang ini arsitektur PHP sudah makin berkembang menjadi lebih baik lagi termasuk juga dari segi kecepatannya. Satu-satunya kendala mengapa PHP tidak bisa secepat bahasa-bahasa pemrograman lain adalah karena kode PHP dibaca oleh mesin secara langsung, bukannya dikompil terlebih dahulu sebelum dibaca oleh mesin. Saya sudah menggunakan metode ajaib ini pada Mecha sejak versi 2.0.0 dan justru terdapat banyak manfaat yang Saya peroleh dari fitur yang hina ini. Salah satu manfaat besar yang Saya rasakan adalah Saya jadi bisa lebih hemat dalam menuliskan metode kelas. Tanpa metode ajaib, mungkin Saya perlu membuat kelas dengan metode-metode yang tak terhitung jumlahnya seperti ini:

<?php

class Page {
    protected $data = [];
    public function __construct(array $data) {
        $this->data = $data;
    }
    public function title() {
        return $this->data['title'] ?? null;
    }
    public function description() {
        return $this->data['description'] ?? null;
    }
    public function content() {
        return $this->data['content'] ?? null;
    }
    public function tags() {
        return $this->data['tags'] ?? null;
    }
    // …
}

Dengan metode ajaib, Saya hanya perlu fokus pada data-data yang masuk ke instansi kelas tersebut saja:

<?php

class Page {
    protected $data = [];
    public function __call(string $key, array $lot = []) {
        return $this->data[$key] ?? null;
    }
    public function __construct(array $data) {
        $this->data = $data;
    }
}

Setiap orang punya pendapat dan idealisme masing-masing, dan kebetulan Saya adalah salah satu orang yang mendapatkan keuntungan dari fitur tersebut. Sehingga Saya hanya bisa menyarankan Anda untuk berpikir lebih terbuka kepada keputusan-keputusan yang telah diambil oleh para pengembang. Dan sekedar informasi tambahan saja, __construct() dan __toString() pada PHP juga termasuk metode ajaib. Dan jika Anda bisa menerima kedua hal tersebut, kenapa Anda tidak bisa mencoba untuk menerima mereka juga?

Dan sebagai informasi tambahan juga, dalam JavaScript terdapat metode sejenis bernama constructor() dan toString(), serta kelas Proxy untuk memberikan efek yang sama. Python juga punya __init__() dan __str__().

Lain-Lain

IIFE (Immediately Invoked Function Expression)

Dahulu kita memerlukan call_user_func() untuk mengeksekusi fungsi anonim:

call_user_func(function($foo, $bar, $baz) {
    // Definisikan variabel pribadi di sini…
}, 'A', 0, true);

Sekarang kita bisa menggunakan gaya IIFE seperti pada JavaScript:

(function($foo, $bar, $baz) {
    // Definisikan variabel pribadi di sini…
})('A', 0, true);

Alternatif if..elseif..else dan switch..case..default

Para pengguna PHP pada umumnya akan menggunakan cara ini:

if ($var === 'true') {
    $value = true;
} else if ($var === 'false') {
    $value = false;
} else {
    $value = null; // Bawaan
}

echo $value;
switch ($var) {
    case 'true':
        $value = true;
        break;
    case 'false':
        $value = false;
        break;
    default:
        $value = null; // Bawaan
}

echo $value;

Tapi belakangan ini Saya lebih suka menggunakan cara seperti berikut:

$value = ([
    'true' => true,
    'false' => false
])[$var] ?? null;

echo $value;

Labels: ,

Sunday, July 7, 2019

Cara Memberlakukan Kode CSS Hanya pada Tampilan Tata Letak

Warna khusus pada tampilan blok widget di halaman Tata Letak.

Cara Lama

Cara pertama ini adalah cara yang paling tua, yaitu dengan membuat selektor CSS yang diinginkan menjadi khusus untuk anak-anak elemen pada induk <body id="layout"> saja. Kekurangan dari metode ini adalah, kode CSS khusus yang seharusnya hanya dimuat pada tampilan Tata Letak akan tampil juga pada kode sumber di halaman versi publik. Kode CSS juga akan diterapkan pada halaman versi publik apabila Anda menambahkan atribut id="layout" pada elemen <body> di dalam kode XML tema:

body#layout div.section {
  background: #ff0;
  border: 4px solid #f00;
}

Cara Baru

Cara yang paling baru untuk memberlakukan kode CSS hanya pada tampilan Tata Letak adalah dengan menuliskan kode CSS di dalam elemen <b:template-skin> seperti ini:

<b:template-skin>
<![CDATA[
div.section {
  background: #ff0;
  border: 4px solid #f00;
}
]]>
</b:template-skin>

Kita mungkin bisa menggunakan tag kondisional ini sebagai cara alternatif, hanya saja Saya belum sempat mengujinya jadi Saya tidak tahu apakah cara ini bisa bekerja atau tidak. Secara logika harusnya bisa:

<b:if cond='data:view.isLayoutMode'>
<style>
div.section {
  background: #ff0;
  border: 4px solid #f00;
}
</style>
</b:if>

Anda bisa menggunakan fitur ini untuk menandai beberapa widget yang Anda anggap khusus atau penting, misalnya dengan memberikan warna yang spesial pada widget-widget tertentu sehingga Anda sebagai pengembang tema akan lebih mudah untuk membimbing pengguna tema Anda dengan cara mengarahkan mereka untuk menyunting widget tertentu berdasarkan warna atau pola yang Anda berikan. Sebagai contoh, di sini Saya memberikan warna gradiasi biru pada widget pencarian dan gradiasi kuning pada widget iklan:

body#layout div.widget.BlogSearch div.widget-content {
  background: linear-gradient(#fff, #e8ffff);
}

body#layout div.widget.AdSense div.widget-content {
  background: linear-gradient(#fff, #ffffce);
}

Kode yang Saya beri tanda adalah nama kelas HTML yang akan diterapkan secara otomatis pada widget sesuai dengan jenisnya. Anda juga bisa membuatnya menjadi lebih spesifik lagi dengan memanfaatkan ID widget seperti ini:

body#layout div.widget#BlogSearch1 div.widget-content {
  background: linear-gradient(#fff, #e8ffff);
}

Labels: , , ,

Saturday, July 6, 2019

Kreatif dengan Tag Kondisional pada Widget HTML Blogger

Sebuah widget berjenis HTML pada tema Blogger dapat Anda gunakan sebagai acuan dasar untuk memahami bagaimana cara kerja XML widget, terutama untuk para pengembang yang ingin membuat widget kustom dari kode sumber secara langsung. Berikut ini adalah sebuah contoh:

<b:section class='section-1' id='section-1' name='Bagian 1'>
  <b:widget id='HTML1' title='Judul Widget' type='HTML'>
    <b:widget-settings>
      <b:widget-setting name='content'>Konten widget.</b:widget-setting>
    </b:widget-settings>
    <b:includable id='main'>
      <h3><data:title/></h3>
      <div><data:content/></div>
    </b:includable>
  </b:widget>
</b:section>

Karena elemen <b:widget> harus berada di dalam elemen <b:section>, maka di sini Saya mengilustrasikannya sebagai sebuah widget HTML yang tersemat di dalam seksi bernama Bagian 1. Ketika kode tersebut diterjemahkan sebagai HTML, maka hasilnya akan menjadi tampak seperti ini:

<div class='section-1 section' id='section-1' name='Bagian 1'>
  <div class='widget HTML' data-version='2' id='HTML1'>
    <h3>Judul Widget</h3>
    <div>Konten widget.</div>
  </div>
</div>

Sedangkan pada halaman Tata Letak akan menjadi tampak seperti ini:

Bagian dalam widget Blogger.
Blok dengan judul Bagian 1 telah ditambahkan.

Jika Anda sunting widget tersebut melalui ikon pensil, maka Anda akan menyadari bahwa nilai data:content akan menempati bidang Konten di dalam formulir pengaturan widget. Sedangkan nilai atribut title pada elemen <b:widget> akan menempati bidang Judul (yang nilainya dapat kita tampilkan melalui data:title). Anda bisa memanfaatkan kedua elemen tersebut untuk mengubah perilaku widget berdasarkan nilainya.

Kode di bawah ini akan menampilkan markup HTML judul apabila data judul tidak kosong, dan akan menampilkan markup HTML konten apabila data konten tidak kosong:

<b:if cond='data:title != ""'>
  <h3><data:title/></h3>
</b:if>
<b:if cond='data:content != ""'>
  <div><data:content/></div>
</b:if>

Kode di bawah ini akan menampilkan markup HTML judul dan konten apabila data judul dan data konten tidak kosong:

<b:if cond='data:title != "" && data:content != ""'>
  <h3><data:title/></h3>
  <div><data:content/></div>
</b:if>

Kode di bawah ini akan menambahkan kelas HTML khusus berdasarkan kosong atau tidaknya data judul dan konten:

<div class='container'>

  <b:class cond='data:title != ""' name='has-title'/>
  <b:class cond='data:content != ""' name='has-content'/>

  <h3><data:title/></h3>
  <div><data:content/></div>

</div>

Kode di bawah ini akan menambahkan kelas HTML khusus berdasarkan nilai dari data judul secara spesifik:

<!-- https://getbootstrap.com/docs/4.3/components/alerts -->
<div class='alert' role='alert'>

  <b:comment>language: en</b:comment>
  <b:class cond='data:title in ["Danger", "Error"]' name='alert-danger'/>
  <b:class cond='data:title == "Info"' name='alert-info'/>
  <b:class cond='data:title == "Warning"' name='alert-warning'/>

  <b:comment>language: id</b:comment>
  <b:class cond='data:title in ["Bahaya", "Kesalahan"]' name='alert-danger'/>
  <b:class cond='data:title == "Informasi"' name='alert-info'/>
  <b:class cond='data:title == "Peringatan"' name='alert-warning'/>

  <h4 class='alert-heading'><data:title/></h4>
  <p class='mb-0'><data:content/></p>

</div>

Untuk manipulasi data yang lebih kompleks, Anda bisa memanfaatkan elemen <b:with> untuk mengubah sintaks objek valid yang tertulis di dalam bidang Konten sehingga nilainya dapat diperlakukan sebagai objek nyata di dalam XML widget dengan metode seperti ini.

Labels: , ,