Tumgik
khoinv · 3 years
Text
[PHP] Null coalescing operator in old versions.
When I find "Null coalescing operator" in php 5.4, I got the bellow function. It is great!
function get(&$var, $default=null) { return isset($var) ? $var : $default; }
$test = array('foo'=>'bar'); get($test['foo'],'nope'); // bar get($test['baz'],'nope'); // nope get($test['spam']['eggs'],'nope'); // nope get($undefined,'nope'); // nope
Passing a previously undefined variable per reference doesn't cause a NOTICE error. Instead, passing $var by reference will define it and set it to null. The default value will also be returned if the passed variable is null.
Note that even though $var is passed by reference, the result of get($var) will be a copy of $var, not a reference.
I copy it from stackoverflow. Ref: https://stackoverflow.com/a/25205195/13217191
0 notes
khoinv · 3 years
Text
TIL CSS
Hiển thị source-map với scss
if ( ! mix.inProduction()) { mix.webpackConfig({ devtool: 'inline-source-map' }) }
Dùng flex có thể bị shrink or grow lại
Vấn đề: ví dụ muốn set cứng cho left-sidebar width là 40px, nhưng có thể bị tính lại thành 32px.
Lý do: Tổng basis của các element con lớn hơn hoặc nhỏ hơn element cha, và flex tự tính lại.
Cách fix: + Dùng shrink-0 or grow-0 cho thành phần không muốn bị tự động tính toán lại. + Tìm ra lý do tổng basis của các con khác cha, rồi sửa lại cho đúng.
Width của thành phần cha luôn bằng 0 khi dùng float
Vấn đề: sẽ không dùng được margin, padding, background-color cho element cha.
Lý do: Các element bị float/position(absolute, static) sẽ không được render theo đúng flow, nên element cha sẽ luôn có width bằng 0.
Cách fix: Cần clear float + Thêm <div class="clearfix"></div> vào cuối cùng của div cha. + Hoặc <div class="parent clearfix"></div> cho div cha luôn(self clear fix)(cách này semantic hơn)
Với clearfix như dưới
.clearfix { &::after { display: block; content: ""; clear: both; } }
0 notes
khoinv · 3 years
Text
[Laravel][Testing] Display clearly array not equals error messages
Problem
When assertEqual asserting was failed, I can’t not known where the path of properies is not match.
Example: Assume I have many configs(contains inputs and expected result) With bellow test, I will receive the bellow message.
private const CONFIGS = [ [ 'params' => [ 'level' => JlptRank::N4, 'parts_score' => [ JlptCategoryConstant::JLPT_VOCABULARY__VALUE => 50, JlptCategoryConstant::JLPT_READING__VALUE => 50, JlptCategoryConstant::JLPT_LISTENING__VALUE => 50, ] ], 'expect_return_value' => JlptRank::RANK_A ], // ... ];
Test code:
/** @test */ public function it_returns_expect_results() { foreach (self::CONFIGS as $config) { $this->assertEquals( $config['expect_return_value'], JlptRank::calculate_Jlpt_rank(...array_values($config['params'])) ); } }
Test result:
Failed asserting that two strings are equal. Expected :'C' Actual :'D'
It is hard to find where is the *config that happen don’t match.*
Solution
Merge inputs and expected results in to array, and comparsion it.
/** @test */ public function it_returns_expect_results() { foreach (self::CONFIGS as $config) { $this->assertEquals( json_encode($config), json_encode(array_merge($config, ['expect_return_value' => JlptRank::calculate_Jlpt_rank(...array_values($config['params']))])) ); } }
Test result will be:
Failed asserting that two strings are equal. Expected :'{"params":{"level":4,"parts_score":{"91":10,"93":60,"94":10}},"expect_return_value":"C"}' Actual :'{"params":{"level":4,"parts_score":{"91":10,"93":60,"94":10}},"expect_return_value":"D"}'
Now you can easily see which properties don’t match.
0 notes
khoinv · 3 years
Text
[Laravel] Write better code
After reviewing the code for the new team members, I found long/wrong codes. I write it down here.
Content
1. Use default params when getting value from request
2. Use Eloquent when function
3. Use Eloquent updateOrCreate
4. Use map instead of bundle if/else
5. Use collection when you can
6. Stop calling query in loop
7. Stop printing raw user inputted values in blade.
8. Be careful when running javascript with user input.
9. Stop abusing Morph
1. Use default params when getting value from request
The original code
$search_type = SearchTypeConstant::TITLE; if($request->has('search_type')){ $search_type = $request->get('search_type'); }
It is verbose
Refactoring
$search_type = $request->get('search_type', SearchTypeConstant::TITLE);
2. Use Eloquent when function
The original code
$query = User::active(); if($first_name = $request->get('first_name')){ $query = $query->where('first_name', $first_name); } $users = $query->get();
We can remove temp $query variable
Refactoring
$users = User::active()->when($first_name = $request->get('first_name'), function($first_name){ return $q->where('first_name', $first_name); })->get();
3. Use Eloquent updateOrCreate
The original code
if($user->profile){ $user->profile->update($request->get('profile')->only('phone', 'address')); } else { $user->profile()->create($request->get('profile')->only('phone', 'address')); }
It is verbose
Refactoring
$user->profile()->updateOrCreate([], $request->get('profile')->only('phone', 'address'));
4. Use map instead of bundle if/else
The original code
function get_status_i18n($status){ if($status == STATUS::COMING){ return 'coming'; } if($status == STATUS::PUBLISH){ return 'publish'; } return 'draft'; }
It is long
Refactoring
private const MAP_STATUS_I18N = [ STATUS::COMING => 'coming', STATUS::PUBLISH => 'publish' ]; function get_status_i18n($status){ return self::MAP_STATUS_I18N[$status] ?? 'draft'; }
5. Use collection when you can
The original code
function get_car_equipment_names($car){ $names = []; foreach($car->equipments as $equipment){ if($equipment->name){ $names[] = $equipment->name; } } return $names; }
It is long
Refactoring
function get_car_equipment_names($car){ return $car->equipments()->pluck('name')->filter(); }
6. Stop calling query in loop
Ex1:
The original code
$books = Book::all(); // *1 query* foreach ($books as $book) { echo $book->author->name; // Make one query like *select \* from authors where id = ${book.author_id}* every time it is called }
N + 1 queries problem
Refactoring
$books = Book::with('author')->get(); // Only two queries will be called. // select * from books // select * from authors where id in (1, 2, 3, 4, 5, ...) foreach ($books as $book) { echo $book->author->name; }
Simple implementation of $books = Book::with('author')->get() code as the bellow.
$books = Book::all(); //*1 query* $books_authors = Author::whereIn('id', $books->pluck('author_id'))->get()->keyBy('author_id'); // *1 query* foreach($books as $book){ $books->author = $books_authors[$book->author_id] ?? null; }
Ex2;
The original code
$books = Book::all(); // *1 query* foreach($books as $book){ echo $book->comments()->count(); // Make one query like *select count(1) from comments where book_id = ${book.id}* every time it is called }
N + 1 queries problem
Refactoring
=>
$books = Book::withCount('comments')->get(); // Only two queries will be called. foreach($books as $book){ echo $book->comments_count; }
Simple implementation of $books = Book::withCount('comments')->get() code as the bellow.
$books = Book::all(); // *1 query* $books_counts= Comment::whereIn('book_id', $books->pluck('id'))->groupBy('book_id')->select(['count(1) as cnt', 'book_id'] )->pluck('book_id', cnt); // *1 query* foreach($books as $book){ $book->comments_count = $likes[$book->id] ?? 0; }
Total: 2 queries
Note: Read more about eager-loading In some frameworks, like phoenix, lazy-load is disabled by default to stop incorrect usage.
7. Stop printing raw user inputted values in blade.
The original code
<div>{!! nl2br($post->comment) !!}</div>
There is a XSS security issue
Refactoring
<div>{{ $post->comment }}</div>
Note: if you want to keep new line in div, you can use white-space: pre-wrap Rules: Don't use printing raw({!! !}}) with user input values.
8. Be careful when running javascript with user input.
The original code
function linkify(string){ return string.replace(/((http|https)?:\/\/[^\s]+)/g, "<a href="%241">$1</a>") } const $post_content = $("#content_post"); $post_content.html(linkify($post_content.text()));
There is a XSS security issue. Easy hack with input is http:deptrai.com<a href="javascript:alert('hacked!');">stackoverflow.com</a> or http:deptrai.com<img src="1" alt="image">
Refactoring
function linkify(string){ return string.replace(/((http|https)?:\/\/[^\s]+)/g, "<a href="%241">$1</a>") } const post = $("#content_post").get(0); post.innerHTML = linkify(post.innerHTML)
Bonus: simple sanitize function Note: Don't use unescaped user input to innerHTML. Almost javascript frameworks will sanitize input before print it into Dom by default. So, be careful when using some functions like react.dangerouslySetInnerHTML or jquery.append() for user inputted values.
In my test results, WAF(Using our provider's default rules) can prevent server-side XSS quite well, but not good with Dom-Base XSS.
Rules: Be careful when exec scripts that contains user input values.
9. Stop abusing Morph
When using morph, we cannot use foreign key relationship and when the table is grow big we will face performance issues.
The original code
posts id - integer name - string videos id - integer name - string tags id - integer name - string taggables tag_id - integer taggable_id - integer taggable_type - string
Refactoring
posts id - integer name - string videos id - integer name - string tags id - integer name - string posts_tags tag_id - integer post_id - integer videos_tags tag_id - integer video_id - integer
0 notes
khoinv · 3 years
Text
[Testing]Re-wirte test-case with magic numbers 1, 2, 4
Note 1, 2, and 4 are the magic numbers of the bellow test because the expected result of the test is 3, but there is only one way to get 3 by combining (1 + 2).
Pros: Only one test can cover three independent test cases, and a combination of three conditions can be tested in the same test case.
Cons: If the code is wrong, because it is magic, so it will take longer to debug.
The original code
private client_fixture(){ return Client::firstOrFail(); } private message_fixture($client, $count = 1, $adjust_min_version = 0, $adjust_max_version = 0){ return factory(Notification::class, $count)->create([ 'app_version_min' => $client->version + $adjust_min_version, 'app_version_max' => $client->version + $adjust_max_version, ]); } public function it_returns_only_messages_for_correct_app_version(){ $client = $this->client_fixture(); $this->message_fixture($client) $this->assertEqual(1, Notification::forAppVersion($client->version)->count()); } public function it_returns_only_messages_for_valid_range_app_version(){ $client = $this->client_fixture(); $this->message_fixture($client, 2, -rand(0, 1), rand(0, 1) $this->assertEqual(2, Notification::forAppVersion($client->version)->count()); } public function it_does_not_return_messages_for_invalid_range_app_version(){ $client = $this->client_fixture(); $this->message_fixture($client, 4, -rand(2, 3), -rand(1, 2) $this->assertEqual(0, Notification::forAppVersion($client->version)->count()); }
Refacoring code
public function it_returns_only_messages_for_correct_app_version(){ $client = $this->client_fixture(); $this->message_fixture($client, $correct_version_message_count = 1) $this->message_fixture($client, $valid_range_message_count = 2, -rand(0, 1), rand(0, 1) $this->message_fixture($client, $_invalid_range_message_count = 4, -rand(2, 3), -rand(1, 2) $this->assertEqual($correct_version_message_count + $valid_range_message_count, Notification::forAppVersion($client->version)->count()); }
0 notes
khoinv · 3 years
Text
[Laravel] Uses clear types
The origin code
public function get_user_badge(array $user_ids, array $course_ids, Carbon $from, Carbon $end, $direction = 'desc'){}
Refactoring 1: clarify array type
Util Classes
use Illuminate\Support\Collection; class IntegerCollection extends Collection { public function __construct(int ...$values) { parent::__construct($values); } } class UserIds extends IntegerCollection {} class CourseIds extends IntegerCollection {}
The code will rewrite as bellow:
public function get_user_badge(UserIds $userIds, CourseIds $courseIds, Carbon $from, Carbon $end, $direction = 'desc'){} // Usage $userIds = new UserIds($user_ids); $courseIds = new CourseIds($course_ids); get_user_badge($userIds, $courseIds, ...)
Refactoring 2: Combine $from/$end to InclusiveRange class
Util Classes
abstract class BaseRange { public $begin; public $end; public function __construct(Carbon $begin, Carbon $end) { $this->begin = $begin; $this->end = $end; } //...some ultil functions in here } class InclusiveRange extends BaseRange{}
The code will rewrite as bellow:
public function get_user_badge(UserIds $userIds, CourseIds $courseIds, InclusiveRange $range, $direction = 'desc'){} // Usage $inclusiveRange = new InclusiveRange($from, $end) get_user_badge($userIds, $courseIds, $inclusiveRange...)
0 notes
khoinv · 3 years
Text
[IntelliJ IDEA][Snippet] Snippetを活用する方法を紹介します。
開発部のコイです。オルトプラスに働いています。 IntelliJ IDEAでのsnippetを活用する方法を紹介します。
背景:
繰り返し同じコードパターンを書いたら、時間がかかっているので、どのように解決できるかを探してみて、snippet活用の方法を見つけました。 最近���ームでIntelliJ IDEAで使っているので、IntelliJ IDEAでの方法を紹介します。
snippetとは
snippet(英語: snippet)とは、「断片」という意味である。情報処理の分野ではよく使う短いプログラムコードを統合開発環境から呼び出す機能の事である。 また、呼び出される短いコードの事をsnippetと呼ぶ場合もある。ja.wikipedia.org
言い換えれば、snippetはテンプレートコードに似ている。コードテンプレート化して、そのあと再利用できるものです。 mainとif/elseなどのsnippetを用意してくれるEditorが多いと思います。
またEditorでは任意でsnippetを登録することができます。 開発中にsnippetを登録して、どんどん増えていくと開発速度が上がるのではないでしょうか?
ItenlliJ IDEAでsnippetを活用する
登録してみる
例:仮に下記のパターンを繰り返し書かないとき、snippetを登録してみます。
for (var i = 0, l = users.length; i < l; i++) { var v = users[i]; // some code here } for (var i = 0, l = books.length; i < l; i++) { var v = books[i]; // some code here }
https://www.jetbrains.com/help/idea/2016.3/creating-and-editing-live-templates.html
登録できたら下記のように使えるようになります。 js-forのsnippetです。
更にSnippetを書くときは、IntelliJ IDEAがサポートしてくれる機能を使うことで、表現力が上がります。 詳細的に下記のリンクを御覧ください。
https://www.jetbrains.com/help/idea/2016.3/live-templates.html
https://www.jetbrains.com/help/idea/2016.3/live-template-variables.html
snippetを共有する方法
snippetを共有する方法は2つあります。
SnippetをExportする
Snippetを手動でコピーする
参考:https://www.jetbrains.com/help/idea/2016.3/sharing-live-templates.html
Vim-snippetsをIntelliJ IDEAのフォマットに変換する
Vim-snippetsのCommuinityが強いので、たくさん良いSnippetが用意されています。 なので、Vim-snippetsをIntelliJ IDEAsnippet化してみました。 下記の例を御覧ください。
jq-ajaxのsnippet
bootstrapのsnippet
詳しい使い方はこれを見intellij-snippetsてください。
まとめ
自分で開発するときに、自分のsnippetをもっと活用してみてください!
Origin post: https://qiita.com/khoinv/items/c7341d462f148681d109
0 notes
khoinv · 3 years
Text
[Laravel][Testing] Display clearly the not found in the database error message
Problem
When seeInDatabase asserting was failed, I can’t not known where the path of properies is not match.
Example: With bellow test, I will receive the bellow message.
Test code:
$this->seeInDatabase('les_knowledge', [ 'lesson_id' => $lesson->id, 'number' => 1, 'content_en' => '~ years old DIFFFFFFFFFF', 'explanation_en' => 'In Japan, you are born as a 0 year-old and turn 1 on your first birthday.', ]);
Test result:
Unable to find row in database table [les_knowledge] that matched attributes
[{ "lesson_id":98,"number":1, "content_en":"~ years old DIFFFFFFFFFF", "explanation_en":"In Japan, you are born as a 0 year-old and turn 1 on your first birthday." }]
It is hard to find where is the path don’t match.
Solution
Create a function likes a bellow.
function seeInDatabaseAndHasProperties($table, array $filter, array $properties, $connection = null){ $this->seeInDatabase($table, $filter, $connection); $model = (array)DB::table($table)->where($filter)->first(); $this->assertEquals($properties, Arr::only($model, array_keys($properties))); }
Test code will be:
$this->seeInDatabaseAndHasProperties('les_knowledge', [ 'lesson_id' => $lesson->id, 'number' => 1, ], [ 'content_en' => '~ years old DIFFFFFFFFFF', 'explanation_en' => 'In Japan, you are born as a 0 year-old and turn 1 on your first birthday.', ]);
Test result will be:
Failed asserting that two arrays are equal. --- Expected +++ Actual @@ @@ Array ( - 'content_en' => '~ years old DIFFFFFFFFFF' + 'content_en' => '~ years old' 'explanation_en' => 'In Japan, you are born as a 0...thday.' )
Now you can easily see which properties don’t match.
1 note · View note