Here is some code I wrote to add, remove, and change user names and passwords in .htpasswd with PHP:

    function adduser($user, $pass) {
    
    	try {
    
    		$htpasswd = '.htpasswd';
    
    		//$hash = '{SHA}'.crypt($pass, base64_encode($pass));
    
    		$hash = crypt_apr1_md5($pass); //APR1-MD5
    
    		$contents = $user . ':' . $hash;
    
    		$lines = explode(PHP_EOL, file_get_contents($htpasswd)); // get .htpasswd
    
    		print('<h4>input:</h4><pre>'.print_r(implode(PHP_EOL, $lines),true).'</pre>');
    
    		$exists = false;
    
    		foreach($lines as $line){
    
    			$existing_user = explode( ':', $line );
    
    			if ($existing_user[0] == $user) { //checks if user exists
    
    				$contents = str_replace($line, $contents, $lines); //changes password for user
    
    				$contents = implode(PHP_EOL, $contents);
    
    				$exists = true;
    
    				if ($pass == '') { // removes user if password is empty
    
    					$contents = str_replace($line, '', $lines); //removes user
    
    					$contents = array_filter($contents); // cleans empty space in array
    
    					$contents = implode(PHP_EOL, $contents);
    
    					$exists = true;
    
    				}
    
    			}
    
    		}
    
    
    		if ($exists == false) {
    
    			$contents = implode(PHP_EOL, $lines) . PHP_EOL . $contents;
    		}
    
    		file_put_contents($htpasswd, $contents);
    
    		print('<h4>output:</h4><pre>'.print_r($contents,true).'</pre>');
    
    	}catch(Exception $e) {
    
    		echo '<h3>fail: </h3>' . $e->getMessage();
    	
    	}
    
    }
    
    if(isset($_GET['user'])){
    
    	adduser($_GET['user'], $_GET['pass']);
    
    	echo '<h3>success</h3>';
    
    }else{
    
    	$htpasswd = '.htpasswd';
    
    	$lines = explode(PHP_EOL, file_get_contents($htpasswd)); // get .htpasswd
    
    	print('<h4>.htpasswd:</h4><pre>'.print_r(implode(PHP_EOL, $lines),true).'</pre>');
    
    	echo '<h3>no user set</h3>';
    }

I would love to streamline it and clean it up.