PageRenderTime 41ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/_posts/2015-12-13-php-empty-keys.md

https://github.com/robertopedroso/robertopedroso.github.com
Markdown | 45 lines | 33 code | 12 blank | 0 comment | 0 complexity | 647626a0d65cf245be72700f5ab44ce4 MD5 | raw file
  1. ---
  2. layout: post
  3. title: PHP Gotchas: Null Array Keys
  4. ---
  5. At my current job, we provide an Account API through which customers can modify their account settings, including date and timezone settings. One morning, a bug report comes in saying that the timezone setting cannot be changed. Every other field appears to work, but the timezone setting never updates.
  6. After a few hours of digging, I had ruled out every bit of code between the API endpoint and the moment the SQL update query is generated. CodeIgniter provides a simple ActiveRecord implementation for generating queries from associative arrays:
  7. ```php
  8. $table_id = 12345;
  9. $data = ['name' => 'Roberto Pedroso', 'timezone' => 'EST'];
  10. $this->db->where('id', $table_id);
  11. $this->db->update('mytable', $fields);
  12. ```
  13. Their QueryBuilder class is well tested. I was doubtful that a CI bug was to blame. So I inspected the output, which was producing T-SQL like this:
  14. ```sql
  15. UPDATE mytable
  16. SET name = 'Roberto Pedroso'
  17. WHERE id = 12345
  18. ```
  19. Why isn't timezone in there? Is this somehow a CI bug? That's when I inspected the array being used to generate the query.
  20. ```php
  21. $data = [
  22. 'name' => 'Roberto Pedroso',
  23. '' => 'New York',
  24. 'timezone' => 'EST'
  25. ];
  26. ```
  27. I was dumbstruck -- I had no idea PHP allows array keys to be empty strings. I eventually discovered the cause, which only doubled my bewilderment. See, all our Account API input passes through a customized input sanitizer. It eventually performs an operation like this:
  28. ```php
  29. $fields[$key] = $value;
  30. ```
  31. Simple enough, right? Except there was a bug related to an edge case that caused $key to never get set. PHP threw a warning but happily chugged along, substuting NULL for our key. It then cast null to an empty string and added that element to the array, resulting in the bizarre array we saw before.
  32. And what did CodeIgniter do? The best it could: rather than produce invalid SQL, it simply stopped transcending that array upon seeing an empty key and spit out a query.
  33. 15 minutes later I had a patch, but the bitter taste of PHP's betrayal persisted...