Parce que tu dois tenter d'insérer la chaîne vide ('') au lieu d'un "vrai" NULL (rappelons notamment, en PHP, que la cast de NULL en string donne la chaîne vide) que MySQL recaste ensuite en TIME '00:00:00' (et encore, si ça passe, c'est probablement parce que le sql_mode n'est pas strict). Problème qui ne se présenterait pas avec une requête correctement préparée en principe.
Effectivement, si la valeur provient d'un input laissé vide par l'utilisateur, la variable $_POST correspondante ne vaudra pas NULL mais la chaîne vide (que MySQL caste en 00:00:00).
Par conséquent, il faut procéder comme tu l'as fait (voir if ('' === $_POST['heure_fin'])) pour "rectifier" cette chaîne vide en NULL comme désiré.
Tu peux même éventuellement le faire directement lors du bind par une ternaire : 'heure_fin' => '' === $_POST['heure_fin'] ? NULL : $_POST['heure_fin'],
julp.fr ~ PHP < 8.0.0 : activer les erreurs PDO/SQL ~ PHP < 8.1.0 : activer les erreurs mysqli
julp.fr ~ PHP < 8.0.0 : activer les erreurs PDO/SQL ~ PHP < 8.1.0 : activer les erreurs mysqli