The problem is that the referrer header is set by the browser, not by the referring site, and different browsers have different behavior around referrer headers -- and some browsers' behavior is actually inconsistent.
PHP redirects are 301/302 redirects, and thus should preserve referrer -- i.e. if Google sends the user to a 301, and it sends the user to the destination site, the destination site will receive Google as the referrer. I say "should" because, once again, browsers are different and inconsistent, but that's the spec behavior.
Meta refresh behaves differently between browers. On IE, I believe a meta refresh with a time of <3 seconds will preserve the referrer, while >3 seconds will send the refreshing site as the referrer. On Firefox, Meta refresh will usually send a blank referrer -- on one hand, this doesn't give away the source, but on the other hand, it may be suspicious (it made CJ ask what the hell I was doing.) To the Googlebot, a meta refresh <1 sec is considered a 301, while >1 sec is a 302, but both are followed.
JavaScript refresh should send the site with the script as the referrer, but sometimes sends blank, once again, depending on the browser. It shouldn't ever leak the true referrer, though. On the other hand, though, JS refresh is invisible to crawlers, so this is a bad idea in any SEO scenario -- the linking page will not pass the link to the real destination.
If site X iframes a destination, that will always pass site X as the referrer. Of course, the landing page will sometimes break the frame, but that usually doesn't matter overmuch.