PHP E-posta Listesi Kaydı
Orijinal gönderime bakın: burada .
Kullanıcıların bir e-posta listesine kaydolmasına izin veren bir html sayfam ve dört php dosyam var. Php betiklerinden biri, 24 saatten eski doğrulanmamış satırları silen bir cronjob'dur ve gönderi uzunluğu açısından aşağıya dahil edilmemiştir. Hazırladığım ifadeler için PDO kullanıyorum. Her şey canlı olarak test edildi ve bildiğim kadarıyla tamamen işlevsel. Her türlü geri bildirime açığız. Snippet'lerin altında bazı soruları listeledim. :)
email.html --- Kullanıcılar buradan kaydolur
<form action="signup.php" method="POST" autocomplete="off">
<input type="text" autocomplete="off" placeholder="Email address" name="email" required>
<br/>
<input type="submit" autocomplete="off" value="Subscribe">
</form>
signup.php --- Kullanıcı girdisini veritabanına filtreler ve gönderir
<?php
//1---DATABASE CONNECTION---
$dbHost = "HOST"; $dbName = "DATABASE";
$dbUser = "USER"; $dbPassword = "PASSWORD";
$port = "PORT"; $charset = 'utf8mb4';
$options = [ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, \PDO::ATTR_EMULATE_PREPARES => false, ]; $dsn = "mysql:host=$dbHost;dbname=$dbName;charset=$charset;port=$port";
try {
$pdo = new \PDO($dsn, $dbUser, $dbPassword, $options); } catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
//1---END---
//2---Add to table: IPv4 ADDRESS, EMAIL, DATETIME, and ACODE---
//prevent direct url access of .php from users, routes to starting page
if(($_SERVER['REQUEST_METHOD'] == 'POST') == NULL) { header("Location: email.html"); exit (0); } //trim spaces on ends of user email input $Temail = trim($_POST['email']); //(on mobile, auto-complete often leaves a space at the end) //allow international characters if(preg_match("/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$^/", $Temail)) { //prevents invalid email addresses header("Location: invalid.html"); exit (0); } //Check Email Domain MX Record $email_host = strtolower(substr(strrchr($Temail, "@"), 1)); if (!checkdnsrr($email_host, "MX")) {
header("Location: invalid.html");
exit (0);
}
//Prevent users from inputting a specific domain...like mine
$notallowed = [ 'mydomain.com', ]; if (!in_array($email_host, $notallowed) == NULL) { header("Location: notallowed.html"); exit (0); } //checks database to make sure the email is not a duplicate $stmt1 = $pdo->prepare("SELECT email FROM emailTable WHERE email = ?"); $stmt1->execute([$Temail]); if($stmt1->fetch()) { //prevents adding a duplicate email
header("Location: duplicate.html");
exit (0);
}
//send verification email using seperate php file
include_once 'vEmail.php';
//check to see if email could be put together
if(include_once 'vEmail' == NULL) {
header("Location: failure.html");
exit (0);
}
//set date and time
date_default_timezone_set('America/Los_Angeles');
$dateTime = date('Ymd-His', strtotime('NOW')); // ('Ymd-His' format and LA timezone are preferred) //variable to store ipv4 address $euserIP4 = $_SERVER['REMOTE_ADDR']; //add all data to the database $stmt2 = $pdo->prepare("INSERT INTO emailTable (IP4, datetime, email, acode) VALUES (:IP4, :datetime, :email, :acode)"); $stmt2->execute(['IP4' => $euserIP4, 'datetime' => $dateTime, 'email' => $Temail, 'acode' => $Acode]);
header("Location: success.html");
exit (0);
//2---END---
?>
vEmail.php ---include_oncesignup.php'de doğrulama e-postası gönderir
<?php
//generate verification code
$Acode = bin2hex(random_bytes(30)); //send verification email w/ code $emailbody =
"<html>
<body>
<table>
<tr>
<td>
<button><a href='https://www.MYDOMAIN.com/status/verify.php?acode=$Acode'>VERIFY</a></button> </td> </tr> </table> </body> </html>"; $headers = "Reply-To: MY NAME <[email protected]>\r\n";
$headers .= "Return-Path: MY NAME <[email protected]>\r\n"; $headers .= "From: MY NAME <[email protected]>\r\n";
$headers .= "MIME-Version: 1.0\r\n"; $headers .= "Content-type: text/html; charset=UTF-8\r\n";
$headers .= "X-Priority: 3\r\n"; //send email mail($Temail, 'Confirm Your Email Subscription', $emailbody, $headers, '-f ' . '[email protected]');
?>
valid.php - Doğrulama e-postasında gönderilen bağlantıya eklendi
<?php
//1---DATABASE CONNECTION---
$vHost = ""; $vName = "";
$vUser = ""; $vPassword = "";
$vPort = ""; $vCharset = "";
$vOptions = [ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, \PDO::ATTR_EMULATE_PREPARES => false, ]; $vdsn = "mysql:host=$vHost;dbname=$vName;charset=$vCharset;port=$vPort";
try {
$vpdo = new \PDO($vdsn, $vUser, $vPassword, $vOptions); } catch (\PDOException $ve) {
throw new \PDOException($ve->getMessage(), (int)$ve->getCode());
}
//1---END---
//2---VERIFICATION LINK---
//set timezone
date_default_timezone_set('America/Los_Angeles');
//prevent direct url access of .php from users, routes to starting page
if(isset($_GET['acode']) == NULL) { header("Location: email.html"); exit (0); } //set verification code variable $vAcode = $_GET['acode']; //check if row still exists $vStmt1 = $vpdo->prepare("SELECT verified, acode FROM emailTable WHERE acode = '$vAcode' LIMIT 1");
$vStmt1->execute(); if($vStmt1->rowCount() == NULL) {
//EXPIRED
header("Location: expired1.html");
exit (0);
}
//check if row is verified ('verified' set to 0)
$vStmt2 = $vpdo->prepare("SELECT verified, acode FROM emailTable WHERE verified = 0 AND acode = '$vAcode' LIMIT 1"); $vStmt2->execute();
if($vStmt2->rowCount() == NULL) { //if 'verified' is set to 1 already header("Location: expired2.html"); exit (0); } //since 'verified' is set to 0,update verification status to 1 $vStmt3 = $vpdo->prepare("UPDATE emailTable SET verified = 1 WHERE acode = '$vAcode' LIMIT 1");
$vStmt3->execute(); //check if the 'verified' was updated correctly if($vStmt3->fetch()) {
header("Location: failure.html");
exit (0);
}
//SUCCESS
header("Location: verified.html");
exit (0);
//2---END---
?>
Sorular ve Yorumlar:
- Her php dosyası için veritabanı bağlantılarını ekledim, ancak bazılarının bağlantıları için global bir yapılandırma dosyasına sahip olmayı tercih ettiğini gördüm. Neden? Daha verimli mi?
- Orijinal gönderide birisi signup.php'de bulunan normal ifadenin unicode bayrağının eksik olduğundan bahsetti. Birisi bunu açıklayabilir mi, çünkü üzerinde hiçbir şey bulamadım.
- IPv4 depolamak iyi çalışıyor olsa da, IPv6'yı (bildiğim kadarıyla) nasıl doğru şekilde depolayacağımı bulamadım. Bunu denedim:
bin2hex(inet_pton($_SERVER['REMOTE_ADDR']));ancak çıktının doğru olup olmadığını anlayamadım çünkü ipv6 adresi gibi görünmüyordu. Kullanılabilir görünüyorsa beni düzeltin. - Yerel mail () işlevini kullanmak yerine PHPMailer'a bakıyorum. Yukarıdaki komut dosyaları söz konusu olduğunda, bu önerilir mi yoksa toplu e-posta gönderimi için daha mı fazla?
Düzenleme 1:
signup.phpİçinde bulunan e-posta doğrulamasının boşluklu bir e-posta adresine izin verdiğini yeni fark ettim (yani: boş[email protected]). Yazının en üstündeki bağlantıda bulunan komut dosyasının önceki sürümü bu tür adresleri engelliyordu. Bunun neden olabileceği veya bunun nasıl önleneceği hakkında bir fikriniz var mı? Uluslararası karakterleri iptal ettiği için filter_var'dan (FILTER_VALIDATE_EMAIL) uzak durmak istiyorum. Onunla denemeye devam edecek ...
Yanıtlar
Global bir konfigürasyon dosyası eklemek verimlilikle ilgili değildir, sadece uygulama konfigürasyon verilerini merkezileştirmekle ilgilidir.
Örneğin, uygulamanız için yeni bir özellik geliştirdiğinizi, üretim veritabanına müdahale etmemek için özellikle bu amaç için bir veritabanı kullanmak istediğinizi varsayalım.
Her bir komut dosyasına veritabanı yapılandırmasını dahil ediyorsanız, geliştirme eylemlerinizden hiçbirinin üretim veritabanına yayılmadığından emin olmak için her komut dosyasını kontrol ettiğinizden (ve umarım iki kez kontrol ettiğinizden) emin olmanız gerekir. (Üretim veritabanına gönderildiğinde değişikliklerinizin hiçbirinin uygulamanızda görünmeyeceğinden emin olsanız bile, tamamen farklı bir veritabanı kullanmak, üretim veritabanınızı ve uygulamanızı hem normal kullanıcıların hem de geliştirme ekibinin yükünü işlemek zorunda kalmadan kurtarır.)
Öte yandan, bunu yapabilirsin. Dizi değerli sabitlerin tanımlanmasının PHP 7.0.0'dan itibaren geçerli olduğuna dikkat edin, ancak basitçe ini define( 'DEV_HOSTNAME', ... )tarzı sabit tanımları kullanabilseniz de, bu kadar süslü değildir.
define( 'CONFIGURATION', 'PRODUCTION' );
define( 'ENVIRONMENT', [
'DEVELOPMENT' => [
'hostname' => 'localhost',
'database' => 'devdb',
'username' => 'username',
'password' => 'password'
],
'PRODUCTION' => [
'hostname' => 'hostname',
'database' => 'proddb',
'username' => 'username',
'password' => 'password'
]
]);
Şimdi uygulamanızdaki ortamları değiştirmek için yapmanız gereken tek şey CONFIGURATIONsabiti değiştirmektir , çünkü uygulamanız bunun gibi bir şey kullanarak yapılandırma değerlerine erişebilir.
$hostname = ENVIRONMENT[CONFIGURATION]['hostname']; $database = ENVIRONMENT[CONFIGURATION]['database'];
$username = ENVIRONMENT[CONFIGURATION]['username']; $password = ENVIRONMENT[CONFIGURATION]['password'];
Bazı üretim uygulamalarının yaptığı şey Database, türetilmiş sınıfları onlara nasıl erişilmesi gerektiğini tanımlayan bir arayüz uygulayan soyut bir sınıf tanımlamaktır , böylece yukarıdaki gibi bir şey yaparak mesela MySQL ve Postgres arasında geçiş yapabilirsiniz. PDO'nun bu kadar kullanışlı olmasının sebeplerinden biri de budur; bir uygulamaya değil, bir arayüze kodlama yapmamızı sağlar.
Verimlilik hakkındaki sorunuzu yanıtlamak için , yapılandırma verilerini içeren ayrı bir dosya eklemek aslında daha az etkilidir.
Bir dosyayı dahil etmek için önce tercümanın onu alması gerekir. Uygulama yapılandırma dosyaları (tabii ki) uygulamaya özgüdür ve bu nedenle geçerli dizinin içinde veya çevresinde bir yerde bulunur. Bununla birlikte, içerme yolu mutlak değilse veya bir "." İle başlamıyorsa veya '..', PHP ilk olarakinclude_path .
Dosyanın nihayetinde bulunduğunu varsayarsak, depolandığı yerden okumak disk erişimini gerektirir, bu gerçekten yavaştır , ancak SSD'ler kullanılarak veya hatta tempfsönbelleğe alma ile etki azaltılabilir .
Daha da kötüsü, veya *_oncesürümlerinden birini kullanırsanız, yalnızca dosyayı içermez (dosya sonunda bulunursa ve bulunursa), yorumlayıcı dosyanın henüz eklenmediğini doğrulamak için ek bir sorun yaşar.requireinclude
Verimsiz yapılandırma değişikliği yayılımı ve yürütme hızı arasındaki ödünleşmeyi, geliştirme zamanı verimliliği ile yürütme süresi verimliliği arasındaki bir değiş tokuş olarak düşünebilirsiniz ve kesinlikle harcadığınız zamana değer (kelime anlamı).
Daha büyük uygulamalar, esasen bu global değişkenleri, konfigürasyon parametrelerinin alınmasını ve ayarlanmasını denetleyen bir tekil sınıfta kapsülleyerek, konfigürasyona daha nesneye yönelik bir yaklaşımı tercih etme eğilimindedir. Muhtemelen buna Kayıt kalıbı adı verildiğini göreceksiniz .
Evet, DB bağlantınız için bir içerme dosyası kullanın. DB şifresini veya sunucuyu değiştirirseniz, birkaç sayfayı güncellemeniz ve hepsinin eşleştiğinden emin olmanız gerekir. Bu sıkıcı ve bugün sadece 4 sayfanız olsa bile, bu gelecekte 20 veya daha fazla olabilir.
Gereksiz tekrarlardan kaçının . Tekrar, her programcının düşmanıdır. Ayrıca diğer sayfalarda tekrarladığınız başka şeyler de var, örneğin:
date_default_timezone_set('America/Los_Angeles');
Bunun amacı ne ? Her neyse, ayarları merkezileştirmek için bir çeşit yapılandırma dosyanız olmalıdır. Ve sonra require_once yapmalı.
DNS kayıtları
Bunun erdeminin tartışmalı olduğunu düşünüyorum:
//Check Email Domain MX Record
$email_host = strtolower(substr(strrchr($Temail, "@"), 1));
if (!checkdnsrr($email_host, "MX")) {
header("Location: invalid.html");
exit (0);
}
Etki alanında en az bir MX kaydı olup olmadığını kontrol ediyorsunuz. Muhtemelen spam gönderenler veya her neyse, rastgele gönderimleri engellemek istersiniz. Ancak bu kendi başına çok az kanıtlar ve teslimin iyi niyetle yapıldığını göstermez. Testi geçtiği sürece herhangi birinin E-posta adresini kullanabilirim. Bu yüzden kişisel olarak bunda çok fazla katma değer görmüyorum.
Ayrıca, geçici DNS hataları meydana gelebilir. Bu işlev, yasal alan adlarında bile zaman zaman başarısız olabilir. Normalde posta doğrudan teslim edilmez, ancak yerel bir kuyruğa gider ve DNS araması başarısız olursa veya MTA'nız postayı düzenli aralıklarla göndermeyi deneyecekse.
Ayrıca, yanılmıyorsam, RFC, bir MX kaydının yokluğunda, MTA'nın bir A kaydını yedek olarak kullanabileceğini söylüyor (birçok DNS bölgesinin kötü yapılandırıldığını unutmayın).
Etki alanı adları büyük / küçük harf duyarlı olmadığından strtolower gerekli değildir. Yine de girişi normalleştirmek ve tüm E-posta adresini küçük ve büyük harflerin bir karışımını içermesi durumunda küçük harfe zorlamak isteyebilirsiniz. Tamamen kozmetik nedenlerle.
İçerir
Bunu yapma:
//send verification email using seperate php file
include_once 'vEmail.php';
Bu, kodu takip etmeyi ve anlamayı daha da zorlaştırır. Unutmayın, okunması / anlaşılması zor olan kod daha hatalı ve daha az güvenli olma eğilimindedir.
Bunun yerine, bir boole değeri veya istediğiniz herhangi bir şeyi döndüren özel bir işlev yazın. Ancak, elbette işlevlerinizi içe aktarmak için kodunuzun en üstünde bir ekleme olabilir. Bunun gibi bir şey olabilir:
if (!send_verification_email($email, $verification_code)) {
// an error occured, do something
}
// proceed normally
Çeşitli
Bence çok fazla yönlendirme sayfanız var, sadece şunu düşünün:
- süresi dolmuş1.html
- expired2.html
- email.html
- geçersiz.html
- notallowed.html
- duplicate.html
- fail.html
- success.html
- doğrulanmış.html
Ve tabii ki hepsi çok benzer olmalı. Kodlama uygulamalarınızı geliştirmenizi şiddetle tavsiye ederim - bir çerçeve veya en azından bir şablon oluşturma çözümü kullanın. Bir sayfa yeterli olacaktır ve özel bir mesaj gibi bazı parametreleri iletirsiniz, böylece sayfa birçok farklı şekilde oluşturulabilir. Her olası senaryo için bir sayfaya ihtiyacınız yok. Sadece bakım ve şişkinliği düşünün.
Postalarınız için de şablonlar kullanabileceğinizi unutmayın . İhtiyacınız olan tek şey, bazı sınırlandırılmış etiketlere sahip metin dosyalarıdır, örneğin% VERIFICATION_CODE%, sonra bu etiketleri değişkenlerinizdeki değerlerle değiştirirsiniz.
IPv6
IPv6 ile sorununuz nedir? Değeri bir varchar alanında mı depoluyorsunuz? Sonra en az 45 karakter ayırın, ancak bu konuda özel bir şey yok. Elbette IPv6 adresleri daha uzun olma eğilimindedir ve belirgin şekilde farklı bir modele sahiptir. Soru şu ki, bu bilgiyi saklamanın amacı nedir ve onu nasıl kullanmayı düşünüyorsunuz.