L'héritage casse le principe d'encapsulation. Une sous classe dépend du fonctionnement de la classe mère (en l'occurrence la classe PDO ici). Il faut au maximum éviter la dépendance à l'implémentation et travailler sur le concept de contrat (programmer par interface).
Sur un plan purement logique, un point m'échappe dans ce raisonnement.
Je pars du principe de base que les fonctions natives d'une version stable d'un langage n'évolueront à priori pas de façon régressives, l'extension ne posera pas de problème dans le temps. Mais comme tu l'as souligné, si on suppose que l'évolution du langage déclare la classe PDO final et donc non extensible, tu n'auras pas le choix de faire une composition. En revanche, pour une modification dans son fonctionnement, que ce soit par héritage ou composition, tu vas quand même te heurter à un problème. L'objet instancié n'aura pas le même comportement et du coup son utilisation dans ta propre classe, qu'elle étende PDO ou en utilise une instance de composition, en sera modifiée également parce que le comportement des méthodes ou des propriétés en aura été modifié... Le résultat de ta propre classe sera donc obligatoirement différent aussi, non ?
Notez que je ne suis pas un gourou de la POO loin s'en faut, je pars peut-être d'une erreur d'interprétation

Tout à fait
Cyrano, l'exemple que j'ai donné n'est pas bon. Je voulais dire que tu es dépendant de l'implémentation de la classe mère pas de son contrat (puisque si son contrat change, tu es impacté dans tous les cas).
Prenons un exemple plus concret. Imaginons que tu aies besoin d'étendre PDOStatement pour des raisons diverses, il faut alors l'indiquer à PDO (exemple issu des commentaires de la doc) :
class PDO {
class Database extends PDO {
function __construct($dsn, $username="", $password="", $driver_options=array()) {
parent::__construct($dsn,$username,$password, $driver_options);
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('DBStatement', array($this)));
}
}
class DBStatement extends PDOStatement {
public $dbh;
protected function __construct($dbh) {
$this->dbh = $dbh;
}
}
L'implémentation de PDO fait que la connexion sera fermée seulement à la fin du script. Cela peut poser un problème car le comportement est changé car tu monopolises une connexion inutile à la base. Si tu es dans un environnement fortement sollicité ou si ton traitement dure longtemps, tu pourrais donc saturer le nombre de connexions possibles.
Le code est donc clairement dépendant de l'implémentation de PDO. Si au contraire, tu utilises la composition, tu évites ce problème.
Je ne sais pas si c'est très clair...
Par expérience, j'ai développé un framework basé sur Zend Framework et j'ai massivement utilisé l'héritage plutôt que le composition. A chaque nouveau besoin où je suis obligé de modifier le comportement de classe mères, les applications qui utilisent les versions précédentes risquent de ne plus fonctionner, ou du patir d'effets de bords.
Cela ne pose pas de problème dans le cadre d'application standalone, mais dès que tu développes du code censé être réutilisable, ca devient un problème. Car si un de nos clients décident d'ajouter des fonctionnalités à l'application, soit il faut adapter cette application au nouveau framework (ce qui peut être couteux), soit maintenir plusieurs versions du framework (ce qui n'est pas terrible non plus)